From 4521976923488913608ba84de05658fff29020ff Mon Sep 17 00:00:00 2001 From: yeahzxnn Date: Tue, 13 Feb 2024 20:00:26 +0900 Subject: [PATCH] =?UTF-8?q?feat=20:=20ON=20=EC=97=85=EB=AC=B4=EC=9D=BC?= =?UTF-8?q?=EC=A7=80=20=EB=A1=9C=EC=A7=81=20=EC=A7=9C=EB=8A=94=20=EC=A4=91?= =?UTF-8?q?=20,=20fix=20->=2010=EB=B2=88=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=B8=8C=EB=9E=9C=EC=B9=98=20=EB=A8=B8=EC=A7=80=20=ED=9B=84=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=98=88=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../On_off_iOS.xcodeproj/project.pbxproj | 189 ++++++-- .../xcshareddata/swiftpm/Package.resolved | 9 + On_off_iOS/On_off_iOS/Apps/AppDelegate.swift | 18 +- .../On_off_iOS/Apps/SceneDelegate.swift | 19 +- .../On_off_iOS/Constant/CellIdentifier.swift | 14 - .../On_off_iOS/Constants/API/Domain.swift | 12 + .../On_off_iOS/Constants/API/EndPoint.swift | 43 ++ .../On_off_iOS/Constants/CellIdentifier.swift | 24 + On_off_iOS/On_off_iOS/Constants/Header.swift | 19 + .../On_off_iOS/Extension/Ext + UIColor.swift | 8 +- .../Home/Base/View/DimmedViewController.swift | 90 ++++ .../Home/Base/View/HomeViewController.swift | 99 +++- On_off_iOS/On_off_iOS/Home/On/Model/On.swift | 16 - .../Model/TodayResolution.swift | 20 + .../View/AddWriteViewController.swift | 0 .../View/MenuModalViewController.swift | 0 .../View/ResolutionWriteViewController.swift | 0 .../View/TodayResolutionViewController.swift | 0 .../ViewModel/AddWriteViewModel.swift | 0 .../ViewModel/MenuModalViewModel.swift | 0 .../ViewModel/ResolutionWriteViewModel.swift | 0 .../ViewModel/TodayResolutionViewModel.swift | 0 .../Home/On/View/OnViewController.swift | 258 ---------- .../Home/On/ViewModel/OnViewModel.swift | 176 ------- .../Home/On/Worklog/Model/Worklog.swift | 19 + .../Worklog/Service/ClickWorklogService.swift | 65 +++ .../Service/InsertWorkLogService.swift | 74 +++ .../On/Worklog/Service/OnUIViewService.swift | 69 +++ .../On/Worklog/View/ClickWorklogView.swift | 359 ++++++++++++++ .../On/Worklog/View/InsertWorkLogView.swift | 205 ++++++++ .../On/Worklog/View/LogOptionButton.swift | 71 +++ .../Home/On/Worklog/View/OnUIView.swift | 455 ++++++++++++++++++ .../Worklog/View/WorkLogTableViewCell.swift | 84 ++++ .../ViewModel/ClickWorklogViewModel.swift | 103 ++++ .../ViewModel/InsertWorkLogViewModel.swift | 125 +++++ .../On/Worklog/ViewModel/OnUIViewModel.swift | 128 +++++ .../Model/TodayResolution.swift | 14 - .../On_off_iOS/KeyChain/KeyChainWrapper.swift | 62 +++ 38 files changed, 2306 insertions(+), 541 deletions(-) delete mode 100644 On_off_iOS/On_off_iOS/Constant/CellIdentifier.swift create mode 100644 On_off_iOS/On_off_iOS/Constants/API/Domain.swift create mode 100644 On_off_iOS/On_off_iOS/Constants/API/EndPoint.swift create mode 100644 On_off_iOS/On_off_iOS/Constants/CellIdentifier.swift create mode 100644 On_off_iOS/On_off_iOS/Constants/Header.swift create mode 100644 On_off_iOS/On_off_iOS/Home/Base/View/DimmedViewController.swift delete mode 100644 On_off_iOS/On_off_iOS/Home/On/Model/On.swift create mode 100644 On_off_iOS/On_off_iOS/Home/On/TodayResolution/Model/TodayResolution.swift rename On_off_iOS/On_off_iOS/Home/{ => On}/TodayResolution/View/AddWriteViewController.swift (100%) rename On_off_iOS/On_off_iOS/Home/{ => On}/TodayResolution/View/MenuModalViewController.swift (100%) rename On_off_iOS/On_off_iOS/Home/{ => On}/TodayResolution/View/ResolutionWriteViewController.swift (100%) rename On_off_iOS/On_off_iOS/Home/{ => On}/TodayResolution/View/TodayResolutionViewController.swift (100%) rename On_off_iOS/On_off_iOS/Home/{ => On}/TodayResolution/ViewModel/AddWriteViewModel.swift (100%) rename On_off_iOS/On_off_iOS/Home/{ => On}/TodayResolution/ViewModel/MenuModalViewModel.swift (100%) rename On_off_iOS/On_off_iOS/Home/{ => On}/TodayResolution/ViewModel/ResolutionWriteViewModel.swift (100%) rename On_off_iOS/On_off_iOS/Home/{ => On}/TodayResolution/ViewModel/TodayResolutionViewModel.swift (100%) delete mode 100644 On_off_iOS/On_off_iOS/Home/On/View/OnViewController.swift delete mode 100644 On_off_iOS/On_off_iOS/Home/On/ViewModel/OnViewModel.swift create mode 100644 On_off_iOS/On_off_iOS/Home/On/Worklog/Model/Worklog.swift create mode 100644 On_off_iOS/On_off_iOS/Home/On/Worklog/Service/ClickWorklogService.swift create mode 100644 On_off_iOS/On_off_iOS/Home/On/Worklog/Service/InsertWorkLogService.swift create mode 100644 On_off_iOS/On_off_iOS/Home/On/Worklog/Service/OnUIViewService.swift create mode 100644 On_off_iOS/On_off_iOS/Home/On/Worklog/View/ClickWorklogView.swift create mode 100644 On_off_iOS/On_off_iOS/Home/On/Worklog/View/InsertWorkLogView.swift create mode 100644 On_off_iOS/On_off_iOS/Home/On/Worklog/View/LogOptionButton.swift create mode 100644 On_off_iOS/On_off_iOS/Home/On/Worklog/View/OnUIView.swift create mode 100644 On_off_iOS/On_off_iOS/Home/On/Worklog/View/WorkLogTableViewCell.swift create mode 100644 On_off_iOS/On_off_iOS/Home/On/Worklog/ViewModel/ClickWorklogViewModel.swift create mode 100644 On_off_iOS/On_off_iOS/Home/On/Worklog/ViewModel/InsertWorkLogViewModel.swift create mode 100644 On_off_iOS/On_off_iOS/Home/On/Worklog/ViewModel/OnUIViewModel.swift delete mode 100644 On_off_iOS/On_off_iOS/Home/TodayResolution/Model/TodayResolution.swift create mode 100644 On_off_iOS/On_off_iOS/KeyChain/KeyChainWrapper.swift diff --git a/On_off_iOS/On_off_iOS.xcodeproj/project.pbxproj b/On_off_iOS/On_off_iOS.xcodeproj/project.pbxproj index f6b2f61..87005b2 100644 --- a/On_off_iOS/On_off_iOS.xcodeproj/project.pbxproj +++ b/On_off_iOS/On_off_iOS.xcodeproj/project.pbxproj @@ -7,9 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 3543D0DF2B6D26E2008BC111 /* On.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3543D0DE2B6D26E2008BC111 /* On.swift */; }; - 3543D0E12B6D2727008BC111 /* OnViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3543D0E02B6D2727008BC111 /* OnViewModel.swift */; }; - 3543D0E32B6D274C008BC111 /* OnViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3543D0E22B6D274C008BC111 /* OnViewController.swift */; }; 3543D0E52B6D27FB008BC111 /* DayCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3543D0E42B6D27FB008BC111 /* DayCollectionViewCell.swift */; }; 3543D0EB2B6D2AB4008BC111 /* TodayResolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3543D0EA2B6D2AB4008BC111 /* TodayResolution.swift */; }; 3543D0ED2B6D2AD4008BC111 /* TodayResolutionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3543D0EC2B6D2AD4008BC111 /* TodayResolutionViewModel.swift */; }; @@ -22,10 +19,29 @@ 355283142B68CC94002BBFFD /* Ext + UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 355283132B68CC94002BBFFD /* Ext + UIColor.swift */; }; 3584F3AC2B5A5CC7007ACB57 /* (null) in Sources */ = {isa = PBXBuildFile; }; 3584F3AE2B5A5FEB007ACB57 /* (null) in Sources */ = {isa = PBXBuildFile; }; + 358CD9E42B7B6F1800B6EF85 /* Domain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358CD9E32B7B6F1800B6EF85 /* Domain.swift */; }; + 358CD9E62B7B6F3300B6EF85 /* EndPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358CD9E52B7B6F3300B6EF85 /* EndPoint.swift */; }; + 358CD9E82B7B716700B6EF85 /* Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358CD9E72B7B716700B6EF85 /* Header.swift */; }; + 358CD9EA2B7B718D00B6EF85 /* CellIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358CD9E92B7B718D00B6EF85 /* CellIdentifier.swift */; }; + 358CD9ED2B7B722800B6EF85 /* KeyChainWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358CD9EC2B7B722800B6EF85 /* KeyChainWrapper.swift */; }; + 358CD9F02B7B81BB00B6EF85 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 358CD9EF2B7B81BB00B6EF85 /* Alamofire */; }; 359395F32B70A60A005C706C /* MenuModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 359395F22B70A60A005C706C /* MenuModalViewController.swift */; }; 359395F72B70AD07005C706C /* MenuModalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 359395F62B70AD07005C706C /* MenuModalViewModel.swift */; }; 35AC0F502B64025300AB0A6B /* TabItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35AC0F4F2B64025300AB0A6B /* TabItem.swift */; }; 35AC0F522B64030400AB0A6B /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35AC0F512B64030400AB0A6B /* TabBarController.swift */; }; + 35E9BE772B7B0E7000ECAF80 /* DimmedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E9BE762B7B0E7000ECAF80 /* DimmedViewController.swift */; }; + 35E9BE7D2B7B0F5700ECAF80 /* Worklog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E9BE7C2B7B0F5700ECAF80 /* Worklog.swift */; }; + 35E9BE7F2B7B124800ECAF80 /* OnUIViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E9BE7E2B7B124800ECAF80 /* OnUIViewModel.swift */; }; + 35E9BE812B7B132400ECAF80 /* InsertWorkLogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E9BE802B7B132400ECAF80 /* InsertWorkLogViewModel.swift */; }; + 35E9BE832B7B136000ECAF80 /* ClickWorklogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E9BE822B7B136000ECAF80 /* ClickWorklogViewModel.swift */; }; + 35E9BE852B7B13A700ECAF80 /* InsertWorkLogService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E9BE842B7B13A700ECAF80 /* InsertWorkLogService.swift */; }; + 35E9BE872B7B13ED00ECAF80 /* OnUIViewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E9BE862B7B13EC00ECAF80 /* OnUIViewService.swift */; }; + 35E9BE892B7B149900ECAF80 /* ClickWorklogService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E9BE882B7B149900ECAF80 /* ClickWorklogService.swift */; }; + 35E9BE8B2B7B14F400ECAF80 /* OnUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E9BE8A2B7B14F400ECAF80 /* OnUIView.swift */; }; + 35E9BE8D2B7B154300ECAF80 /* ClickWorklogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E9BE8C2B7B154300ECAF80 /* ClickWorklogView.swift */; }; + 35E9BE8F2B7B156E00ECAF80 /* LogOptionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E9BE8E2B7B156E00ECAF80 /* LogOptionButton.swift */; }; + 35E9BE912B7B159400ECAF80 /* InsertWorkLogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E9BE902B7B159400ECAF80 /* InsertWorkLogView.swift */; }; + 35E9BE932B7B15C300ECAF80 /* WorkLogTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E9BE922B7B15C300ECAF80 /* WorkLogTableViewCell.swift */; }; 374FD4932B4268EC00F2E645 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 374FD4922B4268EC00F2E645 /* RxCocoa */; }; 374FD4952B4268EC00F2E645 /* RxRelay in Frameworks */ = {isa = PBXBuildFile; productRef = 374FD4942B4268EC00F2E645 /* RxRelay */; }; 374FD4972B4268EC00F2E645 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 374FD4962B4268EC00F2E645 /* RxSwift */; }; @@ -59,7 +75,6 @@ 3B4230102B41572200D0B038 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B42300F2B41572200D0B038 /* SceneDelegate.swift */; }; 3B4230172B41572400D0B038 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3B4230162B41572400D0B038 /* Assets.xcassets */; }; 3B42301A2B41572400D0B038 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3B4230182B41572400D0B038 /* LaunchScreen.storyboard */; }; - 3B51B5272B63DF9500E5FEF9 /* CellIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B51B5262B63DF9500E5FEF9 /* CellIdentifier.swift */; }; 3B8570CB2B582B4E000BC503 /* FSCalendar in Frameworks */ = {isa = PBXBuildFile; productRef = 3B8570CA2B582B4E000BC503 /* FSCalendar */; }; 3B9C89D12B4AED7C0083CF44 /* StatisticsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B9C89D02B4AED7C0083CF44 /* StatisticsViewController.swift */; }; 3BB806D02B5012F400555E58 /* DayChartCustomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BB806CF2B5012F400555E58 /* DayChartCustomView.swift */; }; @@ -72,9 +87,6 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 3543D0DE2B6D26E2008BC111 /* On.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = On.swift; sourceTree = ""; }; - 3543D0E02B6D2727008BC111 /* OnViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnViewModel.swift; sourceTree = ""; }; - 3543D0E22B6D274C008BC111 /* OnViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnViewController.swift; sourceTree = ""; }; 3543D0E42B6D27FB008BC111 /* DayCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayCollectionViewCell.swift; sourceTree = ""; }; 3543D0EA2B6D2AB4008BC111 /* TodayResolution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayResolution.swift; sourceTree = ""; }; 3543D0EC2B6D2AD4008BC111 /* TodayResolutionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayResolutionViewModel.swift; sourceTree = ""; }; @@ -85,6 +97,11 @@ 354FC0F32B72373B00F4ADEB /* AddWriteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWriteViewModel.swift; sourceTree = ""; }; 355283112B68CB70002BBFFD /* Ext + UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Ext + UIFont.swift"; sourceTree = ""; }; 355283132B68CC94002BBFFD /* Ext + UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Ext + UIColor.swift"; sourceTree = ""; }; + 358CD9E32B7B6F1800B6EF85 /* Domain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Domain.swift; sourceTree = ""; }; + 358CD9E52B7B6F3300B6EF85 /* EndPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndPoint.swift; sourceTree = ""; }; + 358CD9E72B7B716700B6EF85 /* Header.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Header.swift; sourceTree = ""; }; + 358CD9E92B7B718D00B6EF85 /* CellIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellIdentifier.swift; sourceTree = ""; }; + 358CD9EC2B7B722800B6EF85 /* KeyChainWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyChainWrapper.swift; sourceTree = ""; }; 359395F22B70A60A005C706C /* MenuModalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuModalViewController.swift; sourceTree = ""; }; 359395F62B70AD07005C706C /* MenuModalViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuModalViewModel.swift; sourceTree = ""; }; 35AC0F4F2B64025300AB0A6B /* TabItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabItem.swift; sourceTree = ""; }; @@ -98,6 +115,19 @@ 35E0C72F2B68C7E3006BF427 /* Pretendard-Thin.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Thin.otf"; sourceTree = ""; }; 35E0C7302B68C7E3006BF427 /* Pretendard-SemiBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-SemiBold.otf"; sourceTree = ""; }; 35E0C7312B68C7E3006BF427 /* Pretendard-Medium.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Medium.otf"; sourceTree = ""; }; + 35E9BE762B7B0E7000ECAF80 /* DimmedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DimmedViewController.swift; sourceTree = ""; }; + 35E9BE7C2B7B0F5700ECAF80 /* Worklog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Worklog.swift; sourceTree = ""; }; + 35E9BE7E2B7B124800ECAF80 /* OnUIViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnUIViewModel.swift; sourceTree = ""; }; + 35E9BE802B7B132400ECAF80 /* InsertWorkLogViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertWorkLogViewModel.swift; sourceTree = ""; }; + 35E9BE822B7B136000ECAF80 /* ClickWorklogViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClickWorklogViewModel.swift; sourceTree = ""; }; + 35E9BE842B7B13A700ECAF80 /* InsertWorkLogService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertWorkLogService.swift; sourceTree = ""; }; + 35E9BE862B7B13EC00ECAF80 /* OnUIViewService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnUIViewService.swift; sourceTree = ""; }; + 35E9BE882B7B149900ECAF80 /* ClickWorklogService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClickWorklogService.swift; sourceTree = ""; }; + 35E9BE8A2B7B14F400ECAF80 /* OnUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnUIView.swift; sourceTree = ""; }; + 35E9BE8C2B7B154300ECAF80 /* ClickWorklogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClickWorklogView.swift; sourceTree = ""; }; + 35E9BE8E2B7B156E00ECAF80 /* LogOptionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogOptionButton.swift; sourceTree = ""; }; + 35E9BE902B7B159400ECAF80 /* InsertWorkLogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertWorkLogView.swift; sourceTree = ""; }; + 35E9BE922B7B15C300ECAF80 /* WorkLogTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkLogTableViewCell.swift; sourceTree = ""; }; 374FD49C2B4281E100F2E645 /* OnboardingCustomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingCustomView.swift; sourceTree = ""; }; 374FD49E2B42825B00F2E645 /* CustomPageControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPageControl.swift; sourceTree = ""; }; 374FD4A12B4294F100F2E645 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; @@ -128,7 +158,6 @@ 3B4230162B41572400D0B038 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 3B4230192B41572400D0B038 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 3B42301B2B41572400D0B038 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3B51B5262B63DF9500E5FEF9 /* CellIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellIdentifier.swift; sourceTree = ""; }; 3B9C89D02B4AED7C0083CF44 /* StatisticsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsViewController.swift; sourceTree = ""; }; 3BB806CF2B5012F400555E58 /* DayChartCustomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayChartCustomView.swift; sourceTree = ""; }; 3BB806D22B5021BC00555E58 /* StatisticsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsViewModel.swift; sourceTree = ""; }; @@ -145,6 +174,7 @@ buildActionMask = 2147483647; files = ( 376A4B5C2B5506D7004FBB56 /* Lottie in Frameworks */, + 358CD9F02B7B81BB00B6EF85 /* Alamofire in Frameworks */, 374FD4972B4268EC00F2E645 /* RxSwift in Frameworks */, 374FD49A2B42690D00F2E645 /* SnapKit in Frameworks */, 374FD4952B4268EC00F2E645 /* RxRelay in Frameworks */, @@ -196,38 +226,31 @@ path = View; sourceTree = ""; }; - 355283152B6A0BFE002BBFFD /* On */ = { + 358CD9E12B7B6EF200B6EF85 /* Constants */ = { isa = PBXGroup; children = ( - 355283162B6A0C12002BBFFD /* Model */, - 355283182B6A0C38002BBFFD /* ViewModel */, - 355283172B6A0C18002BBFFD /* View */, + 358CD9E22B7B6F0700B6EF85 /* API */, + 358CD9E72B7B716700B6EF85 /* Header.swift */, + 358CD9E92B7B718D00B6EF85 /* CellIdentifier.swift */, ); - path = On; + path = Constants; sourceTree = ""; }; - 355283162B6A0C12002BBFFD /* Model */ = { + 358CD9E22B7B6F0700B6EF85 /* API */ = { isa = PBXGroup; children = ( - 3543D0DE2B6D26E2008BC111 /* On.swift */, + 358CD9E32B7B6F1800B6EF85 /* Domain.swift */, + 358CD9E52B7B6F3300B6EF85 /* EndPoint.swift */, ); - path = Model; + path = API; sourceTree = ""; }; - 355283172B6A0C18002BBFFD /* View */ = { + 358CD9EB2B7B721600B6EF85 /* KeyChain */ = { isa = PBXGroup; children = ( - 3543D0E22B6D274C008BC111 /* OnViewController.swift */, + 358CD9EC2B7B722800B6EF85 /* KeyChainWrapper.swift */, ); - path = View; - sourceTree = ""; - }; - 355283182B6A0C38002BBFFD /* ViewModel */ = { - isa = PBXGroup; - children = ( - 3543D0E02B6D2727008BC111 /* OnViewModel.swift */, - ); - path = ViewModel; + path = KeyChain; sourceTree = ""; }; 35984F222B63EE8000A5F6F2 /* TabBar */ = { @@ -271,6 +294,66 @@ path = Fonts; sourceTree = ""; }; + 35E9BE742B7B07D300ECAF80 /* On */ = { + isa = PBXGroup; + children = ( + 35E9BE752B7B07E200ECAF80 /* Worklog */, + 3543D0E62B6D29FA008BC111 /* TodayResolution */, + ); + path = On; + sourceTree = ""; + }; + 35E9BE752B7B07E200ECAF80 /* Worklog */ = { + isa = PBXGroup; + children = ( + 35E9BE782B7B0EC400ECAF80 /* Model */, + 35E9BE7A2B7B0EDA00ECAF80 /* ViewModel */, + 35E9BE7B2B7B0EE800ECAF80 /* Service */, + 35E9BE792B7B0ECA00ECAF80 /* View */, + ); + path = Worklog; + sourceTree = ""; + }; + 35E9BE782B7B0EC400ECAF80 /* Model */ = { + isa = PBXGroup; + children = ( + 35E9BE7C2B7B0F5700ECAF80 /* Worklog.swift */, + ); + path = Model; + sourceTree = ""; + }; + 35E9BE792B7B0ECA00ECAF80 /* View */ = { + isa = PBXGroup; + children = ( + 35E9BE8A2B7B14F400ECAF80 /* OnUIView.swift */, + 35E9BE8C2B7B154300ECAF80 /* ClickWorklogView.swift */, + 35E9BE8E2B7B156E00ECAF80 /* LogOptionButton.swift */, + 35E9BE902B7B159400ECAF80 /* InsertWorkLogView.swift */, + 35E9BE922B7B15C300ECAF80 /* WorkLogTableViewCell.swift */, + ); + path = View; + sourceTree = ""; + }; + 35E9BE7A2B7B0EDA00ECAF80 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 35E9BE7E2B7B124800ECAF80 /* OnUIViewModel.swift */, + 35E9BE802B7B132400ECAF80 /* InsertWorkLogViewModel.swift */, + 35E9BE822B7B136000ECAF80 /* ClickWorklogViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 35E9BE7B2B7B0EE800ECAF80 /* Service */ = { + isa = PBXGroup; + children = ( + 35E9BE842B7B13A700ECAF80 /* InsertWorkLogService.swift */, + 35E9BE862B7B13EC00ECAF80 /* OnUIViewService.swift */, + 35E9BE882B7B149900ECAF80 /* ClickWorklogService.swift */, + ); + path = Service; + sourceTree = ""; + }; 374FD49B2B4281CA00F2E645 /* OnBoarding */ = { isa = PBXGroup; children = ( @@ -438,8 +521,7 @@ 3B0129C12B623BA900B191AD /* Home */ = { isa = PBXGroup; children = ( - 3543D0E62B6D29FA008BC111 /* TodayResolution */, - 355283152B6A0BFE002BBFFD /* On */, + 35E9BE742B7B07D300ECAF80 /* On */, 3B0129CF2B62BA8100B191AD /* Base */, ); path = Home; @@ -471,6 +553,7 @@ children = ( 3B0129C22B623BB900B191AD /* HomeViewController.swift */, 3543D0E42B6D27FB008BC111 /* DayCollectionViewCell.swift */, + 35E9BE762B7B0E7000ECAF80 /* DimmedViewController.swift */, ); path = View; sourceTree = ""; @@ -510,6 +593,8 @@ 3B42300C2B41572200D0B038 /* On_off_iOS */ = { isa = PBXGroup; children = ( + 358CD9EB2B7B721600B6EF85 /* KeyChain */, + 358CD9E12B7B6EF200B6EF85 /* Constants */, 35984F222B63EE8000A5F6F2 /* TabBar */, 3B0129C12B623BA900B191AD /* Home */, 3B9C89CF2B4AED640083CF44 /* Statistics */, @@ -521,7 +606,6 @@ 374FD49B2B4281CA00F2E645 /* OnBoarding */, 3B4230212B41584300D0B038 /* Apps */, 376A4B552B5504AB004FBB56 /* Assets */, - 3B51B5252B63DF8000E5FEF9 /* Constant */, 3B0129C42B6242D400B191AD /* Extension */, 35E0C7282B68C7AC006BF427 /* Fonts */, 3B4230182B41572400D0B038 /* LaunchScreen.storyboard */, @@ -539,14 +623,6 @@ path = Apps; sourceTree = ""; }; - 3B51B5252B63DF8000E5FEF9 /* Constant */ = { - isa = PBXGroup; - children = ( - 3B51B5262B63DF9500E5FEF9 /* CellIdentifier.swift */, - ); - path = Constant; - sourceTree = ""; - }; 3B9C89CF2B4AED640083CF44 /* Statistics */ = { isa = PBXGroup; children = ( @@ -610,6 +686,7 @@ 374FD4992B42690D00F2E645 /* SnapKit */, 376A4B5B2B5506D7004FBB56 /* Lottie */, 3B8570CA2B582B4E000BC503 /* FSCalendar */, + 358CD9EF2B7B81BB00B6EF85 /* Alamofire */, ); productName = On_off_iOS; productReference = 3B42300A2B41572200D0B038 /* On_off_iOS.app */; @@ -644,6 +721,7 @@ 374FD4982B42690D00F2E645 /* XCRemoteSwiftPackageReference "SnapKit" */, 376A4B5A2B5506D7004FBB56 /* XCRemoteSwiftPackageReference "lottie-ios" */, 3B8570C92B582B4E000BC503 /* XCRemoteSwiftPackageReference "FSCalendar" */, + 358CD9EE2B7B81BB00B6EF85 /* XCRemoteSwiftPackageReference "Alamofire" */, ); productRefGroup = 3B42300B2B41572200D0B038 /* Products */; projectDirPath = ""; @@ -673,23 +751,24 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3B51B5272B63DF9500E5FEF9 /* CellIdentifier.swift in Sources */, + 35E9BE772B7B0E7000ECAF80 /* DimmedViewController.swift in Sources */, 354FC0F22B72370B00F4ADEB /* AddWriteViewController.swift in Sources */, - 3543D0E32B6D274C008BC111 /* OnViewController.swift in Sources */, 378140612B42E83100F2AA5A /* NickNameViewModel.swift in Sources */, 3787D0132B42E0F000F054DD /* NickNameViewController.swift in Sources */, 3543D0F42B6F6C86008BC111 /* ResolutionWriteViewModel.swift in Sources */, 3787D00F2B42AD6F00F054DD /* OnBoardingViewModel.swift in Sources */, + 35E9BE832B7B136000ECAF80 /* ClickWorklogViewModel.swift in Sources */, 374FD4A22B4294F100F2E645 /* LoginViewController.swift in Sources */, + 35E9BE892B7B149900ECAF80 /* ClickWorklogService.swift in Sources */, + 358CD9E62B7B6F3300B6EF85 /* EndPoint.swift in Sources */, 374FD49F2B42825B00F2E645 /* CustomPageControl.swift in Sources */, 3543D0E52B6D27FB008BC111 /* DayCollectionViewCell.swift in Sources */, 355283122B68CB70002BBFFD /* Ext + UIFont.swift in Sources */, 355283142B68CC94002BBFFD /* Ext + UIColor.swift in Sources */, 359395F72B70AD07005C706C /* MenuModalViewModel.swift in Sources */, - 3543D0DF2B6D26E2008BC111 /* On.swift in Sources */, - 3543D0E12B6D2727008BC111 /* OnViewModel.swift in Sources */, 3769A6862B58563000D79C33 /* SelectTimeViewModel.swift in Sources */, 374FD4A42B4297BE00F2E645 /* OnBoardingViewController.swift in Sources */, + 35E9BE8B2B7B14F400ECAF80 /* OnUIView.swift in Sources */, 3769A6882B58563C00D79C33 /* SelectTimeViewController.swift in Sources */, 3787D0082B429C1100F054DD /* OnboardingModel.swift in Sources */, 378140662B42F0AB00F2AA5A /* ProfileSettingViewModel.swift in Sources */, @@ -697,34 +776,47 @@ 3B42300E2B41572200D0B038 /* AppDelegate.swift in Sources */, 3543D0EB2B6D2AB4008BC111 /* TodayResolution.swift in Sources */, 3769A68E2B585B6600D79C33 /* DayButton.swift in Sources */, + 35E9BE872B7B13ED00ECAF80 /* OnUIViewService.swift in Sources */, 3769A6912B585BDE00D79C33 /* DayModel.swift in Sources */, + 35E9BE932B7B15C300ECAF80 /* WorkLogTableViewCell.swift in Sources */, 3B0129CC2B624D6000B191AD /* Home.swift in Sources */, 374FD49D2B4281E100F2E645 /* OnboardingCustomView.swift in Sources */, 3BB806D82B5024BC00555E58 /* MonthStatistics.swift in Sources */, 3584F3AE2B5A5FEB007ACB57 /* (null) in Sources */, 3BB806D32B5021BC00555E58 /* StatisticsViewModel.swift in Sources */, 3543D0F22B6F5FF0008BC111 /* ResolutionWriteViewController.swift in Sources */, + 35E9BE7F2B7B124800ECAF80 /* OnUIViewModel.swift in Sources */, 3B0129C82B6246E900B191AD /* Ext + UIImage.swift in Sources */, 3B4230102B41572200D0B038 /* SceneDelegate.swift in Sources */, 3584F3AC2B5A5CC7007ACB57 /* (null) in Sources */, 354FC0F42B72373B00F4ADEB /* AddWriteViewModel.swift in Sources */, + 358CD9EA2B7B718D00B6EF85 /* CellIdentifier.swift in Sources */, + 358CD9E82B7B716700B6EF85 /* Header.swift in Sources */, + 35E9BE8F2B7B156E00ECAF80 /* LogOptionButton.swift in Sources */, 378140632B42F07A00F2AA5A /* ProfileSettingViewController.swift in Sources */, 3B0129C32B623BB900B191AD /* HomeViewController.swift in Sources */, 3787D0112B42E0B100F054DD /* LoginViewModel.swift in Sources */, + 358CD9ED2B7B722800B6EF85 /* KeyChainWrapper.swift in Sources */, 3787D00A2B429F1700F054DD /* LaunchViewController.swift in Sources */, 3BD552682B58FCF70043920E /* CalendarCell.swift in Sources */, 359395F32B70A60A005C706C /* MenuModalViewController.swift in Sources */, 35AC0F522B64030400AB0A6B /* TabBarController.swift in Sources */, + 35E9BE8D2B7B154300ECAF80 /* ClickWorklogView.swift in Sources */, 3543D0EF2B6D2AE4008BC111 /* TodayResolutionViewController.swift in Sources */, 3BD552662B58FA8C0043920E /* RateFillView.swift in Sources */, 35AC0F502B64025300AB0A6B /* TabItem.swift in Sources */, 3BD5526A2B5914B10043920E /* CalendarStatistics.swift in Sources */, 3B9C89D12B4AED7C0083CF44 /* StatisticsViewController.swift in Sources */, 3B0129C62B6242F800B191AD /* Ext + ViewController.swift in Sources */, + 35E9BE7D2B7B0F5700ECAF80 /* Worklog.swift in Sources */, 3BB806D02B5012F400555E58 /* DayChartCustomView.swift in Sources */, 3B0129CA2B624CAE00B191AD /* HomeViewModel.swift in Sources */, + 35E9BE852B7B13A700ECAF80 /* InsertWorkLogService.swift in Sources */, + 35E9BE912B7B159400ECAF80 /* InsertWorkLogView.swift in Sources */, 3BB806D62B50228100555E58 /* DayStatistics.swift in Sources */, 3543D0ED2B6D2AD4008BC111 /* TodayResolutionViewModel.swift in Sources */, + 35E9BE812B7B132400ECAF80 /* InsertWorkLogViewModel.swift in Sources */, + 358CD9E42B7B6F1800B6EF85 /* Domain.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -948,6 +1040,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 358CD9EE2B7B81BB00B6EF85 /* XCRemoteSwiftPackageReference "Alamofire" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Alamofire/Alamofire.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.8.1; + }; + }; 374FD4912B4268EC00F2E645 /* XCRemoteSwiftPackageReference "RxSwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ReactiveX/RxSwift.git"; @@ -983,6 +1083,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 358CD9EF2B7B81BB00B6EF85 /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = 358CD9EE2B7B81BB00B6EF85 /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; 374FD4922B4268EC00F2E645 /* RxCocoa */ = { isa = XCSwiftPackageProductDependency; package = 374FD4912B4268EC00F2E645 /* XCRemoteSwiftPackageReference "RxSwift" */; diff --git a/On_off_iOS/On_off_iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/On_off_iOS/On_off_iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index fa25e4c..e78159c 100644 --- a/On_off_iOS/On_off_iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/On_off_iOS/On_off_iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "3dc6a42c7727c49bf26508e29b0a0b35f9c7e1ad", + "version" : "5.8.1" + } + }, { "identity" : "fscalendar", "kind" : "remoteSourceControl", diff --git a/On_off_iOS/On_off_iOS/Apps/AppDelegate.swift b/On_off_iOS/On_off_iOS/Apps/AppDelegate.swift index 64e3deb..44f0800 100644 --- a/On_off_iOS/On_off_iOS/Apps/AppDelegate.swift +++ b/On_off_iOS/On_off_iOS/Apps/AppDelegate.swift @@ -6,15 +6,28 @@ // import UIKit +import KakaoSDKCommon +import KakaoSDKAuth +import KakaoSDKUser @main class AppDelegate: UIResponder, UIApplicationDelegate { - + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. + let nativeAppKey = Bundle.main.infoDictionary?["KAKAO_NATIVE_APP_KEY"] ?? "" + KakaoSDK.initSDK(appKey: nativeAppKey as! String) return true } - + + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + if (AuthApi.isKakaoTalkLoginUrl(url)) { + return AuthController.rx.handleOpenUrl(url: url) + } + + return false + } + // MARK: UISceneSession Lifecycle func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { @@ -31,4 +44,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } - diff --git a/On_off_iOS/On_off_iOS/Apps/SceneDelegate.swift b/On_off_iOS/On_off_iOS/Apps/SceneDelegate.swift index 542d84c..85adaf3 100644 --- a/On_off_iOS/On_off_iOS/Apps/SceneDelegate.swift +++ b/On_off_iOS/On_off_iOS/Apps/SceneDelegate.swift @@ -6,26 +6,34 @@ // import UIKit +import KakaoSDKAuth class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) let navigationController = UINavigationController() + let launchViewModel = LaunchViewModel(loginService: LoginService()) + let launchViewController = LaunchViewController(viewModel: launchViewModel) - let viewModel = TodayResolutionViewModel(navigationController: navigationController) // 인스턴스를 생성해서 전달 - let todayResolutionViewController = TodayResolutionViewController(viewModel: viewModel) - navigationController.viewControllers = [todayResolutionViewController] - + navigationController.viewControllers = [launchViewController] + window?.rootViewController = navigationController window?.makeKeyAndVisible() } + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + if let url = URLContexts.first?.url { + if (AuthApi.isKakaoTalkLoginUrl(url)) { + _ = AuthController.rx.handleOpenUrl(url: url) + } + } + } + func sceneDidDisconnect(_ scene: UIScene) { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. @@ -56,4 +64,3 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } - diff --git a/On_off_iOS/On_off_iOS/Constant/CellIdentifier.swift b/On_off_iOS/On_off_iOS/Constant/CellIdentifier.swift deleted file mode 100644 index a21084b..0000000 --- a/On_off_iOS/On_off_iOS/Constant/CellIdentifier.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// CellIdentifier.swift -// On_off_iOS -// -// Created by 정호진 on 1/26/24. -// - -import Foundation - -/// Cell Indeitifier들 작성!!!! -enum CellIdentifier: String { - case DayCollectionViewCell - -} diff --git a/On_off_iOS/On_off_iOS/Constants/API/Domain.swift b/On_off_iOS/On_off_iOS/Constants/API/Domain.swift new file mode 100644 index 0000000..c232041 --- /dev/null +++ b/On_off_iOS/On_off_iOS/Constants/API/Domain.swift @@ -0,0 +1,12 @@ +// +// Domain.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation + +struct Domain { + static let RESTAPI = "http://" + (Bundle.main.object(forInfoDictionaryKey: "IP") as? String ?? "") + ":" + (Bundle.main.object(forInfoDictionaryKey: "PORT") as? String ?? "") +} diff --git a/On_off_iOS/On_off_iOS/Constants/API/EndPoint.swift b/On_off_iOS/On_off_iOS/Constants/API/EndPoint.swift new file mode 100644 index 0000000..f68047e --- /dev/null +++ b/On_off_iOS/On_off_iOS/Constants/API/EndPoint.swift @@ -0,0 +1,43 @@ +// +// EndPoint.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation + +/// Login 경로 +enum LoginPath: String { + + /// 유효성 검사 + case checkValidation = "/token/validate" + case kakaoLogin = "/oauth2/kakao/token/validate" + case appleLogin = "/oauth2/apple/token/validate" + + /// 직업 + case job = "/enums/field-of-works" + case experienceYear = "/enums/experience-years" +} + +/// 회고록 +enum MemoirsPath: String { + case memoirsSave = "/Memoirs" + case getEmoticon = "/emoticons" +} + +enum FeedPath: String { + case feedImage = "/feed-images" + case workLifeBalacne = "/feeds?date=DATE" + case checkWLB = "/feeds/FEEDID/check" + case delayTomorrow = "/feeds/FEEDID/delay" + case delete = "/feeds/FEEDID" +} + +///ON-업무일지 +enum WorklogPath: String { + case worklog = "/on/worklog/worklogid" +// case checkWLP = "/on/worklog/worklogid" +// case delaylogTommorow = "/on/worklog/worklogId" +// case deletelog = "/on/worklog/worklogId" +} diff --git a/On_off_iOS/On_off_iOS/Constants/CellIdentifier.swift b/On_off_iOS/On_off_iOS/Constants/CellIdentifier.swift new file mode 100644 index 0000000..e24cb1d --- /dev/null +++ b/On_off_iOS/On_off_iOS/Constants/CellIdentifier.swift @@ -0,0 +1,24 @@ +// +// CellIdentifier.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation + +/// Cell Indeitifier들 작성!!!! +enum CellIdentifier: String { + + // MARK: CollectionViewCell + case DayCollectionViewCell + case EmoticonCollectionViewCell + + // MARK: TableViewCell + case BookmarkTableViewCell + case ModalSelectProfileTableViewCell // 프로필 설정 + + // MARK: - On UIView + case ImageCollectionView + case WorkLifeBalanceTableViewCell +} diff --git a/On_off_iOS/On_off_iOS/Constants/Header.swift b/On_off_iOS/On_off_iOS/Constants/Header.swift new file mode 100644 index 0000000..647a79f --- /dev/null +++ b/On_off_iOS/On_off_iOS/Constants/Header.swift @@ -0,0 +1,19 @@ +// +// Header.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Alamofire +import Foundation + +enum Header { + case header + + func getHeader() -> HTTPHeaders { + let accessToken = KeychainWrapper.loadItem(forKey: LoginKeyChain.accessToken.rawValue) ?? "" + print(accessToken) + return ["Authorization": "Bearer \(accessToken)"] + } +} diff --git a/On_off_iOS/On_off_iOS/Extension/Ext + UIColor.swift b/On_off_iOS/On_off_iOS/Extension/Ext + UIColor.swift index 7b86026..232f941 100644 --- a/On_off_iOS/On_off_iOS/Extension/Ext + UIColor.swift +++ b/On_off_iOS/On_off_iOS/Extension/Ext + UIColor.swift @@ -40,11 +40,13 @@ extension UIColor { extension UIColor { static let OnOffMain = UIColor(hex: "7D4BFF") // OnOff 메인컬러 - static let OnOffYellow = UIColor(hex: "#FFD572") //StatisticsView 그래프 컬러 + static let OnOffYellow = UIColor(hex: "#FFD572") + static let OnOffLightMain = UIColor(hex: "#FCF8FF") + + + //StatisticsView 그래프 컬러 static let OnOffPurple = UIColor(hex: "#7D4BFF") - static let OnOffBackButton = UIColor(hex: "#585858") //On화면 백버튼 static var OnOffLightPurple: UIColor { //날짜 선택 안되었을 때의 컬러 return OnOffPurple.withAlphaComponent(0.3) } } - diff --git a/On_off_iOS/On_off_iOS/Home/Base/View/DimmedViewController.swift b/On_off_iOS/On_off_iOS/Home/Base/View/DimmedViewController.swift new file mode 100644 index 0000000..6028131 --- /dev/null +++ b/On_off_iOS/On_off_iOS/Home/Base/View/DimmedViewController.swift @@ -0,0 +1,90 @@ +// +// DimmedViewController.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation +import UIKit + +// MARK: 팝업 창이 뜰 때 뒷 배경이 흐릿하게 변하게 되는 클래스 +class DimmedViewController: UIViewController { + + /// Animate time + private let durationTime: TimeInterval + + /// view's alpha value + private let alpha: CGFloat + + /// Dimmed View + private lazy var dimmedView: UIView = { + let view = UIView() + view.backgroundColor = .black + view.alpha = 0 + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + init(durationTime: TimeInterval, alpha: CGFloat) { + self.durationTime = durationTime + self.alpha = alpha + super.init(nibName: nil, bundle: nil) + + modalTransitionStyle = .coverVertical + modalPresentationStyle = .overFullScreen + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + addDimmedView() + appearAnimation() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + disappearAnimation() + } + + // MARK: - Function + + /// Add Dimmed View + private func addDimmedView(){ + guard let presentingViewController else { return } + presentingViewController.view.addSubview(dimmedView) + + // Get the superview of the dimmedView + guard let superview = dimmedView.superview else { + fatalError("dimmedView should have a superview.") + } + + NSLayoutConstraint.activate([ + NSLayoutConstraint(item: dimmedView, attribute: .leading, relatedBy: .equal, toItem: superview, attribute: .leading, multiplier: 1.0, constant: 0.0), + NSLayoutConstraint(item: dimmedView, attribute: .trailing, relatedBy: .equal, toItem: superview, attribute: .trailing, multiplier: 1.0, constant: 0.0), + NSLayoutConstraint(item: dimmedView, attribute: .top, relatedBy: .equal, toItem: superview, attribute: .top, multiplier: 1.0, constant: 0.0), + NSLayoutConstraint(item: dimmedView, attribute: .bottom, relatedBy: .equal, toItem: superview, attribute: .bottom, multiplier: 1.0, constant: 0.0), + ]) + } + + /// move appear animation + private func appearAnimation(){ + UIView.animate(withDuration: durationTime) { + self.dimmedView.alpha = self.alpha + } + } + + /// move disappear animation + private func disappearAnimation(){ + UIView.animate(withDuration: durationTime) { + self.dimmedView.alpha = 0 + } completion: { _ in + self.dimmedView.removeFromSuperview() + } + } + +} diff --git a/On_off_iOS/On_off_iOS/Home/Base/View/HomeViewController.swift b/On_off_iOS/On_off_iOS/Home/Base/View/HomeViewController.swift index d66917f..8621de5 100644 --- a/On_off_iOS/On_off_iOS/Home/Base/View/HomeViewController.swift +++ b/On_off_iOS/On_off_iOS/Home/Base/View/HomeViewController.swift @@ -63,8 +63,8 @@ final class HomeViewController: UIViewController { return view }() - /// On - Off 될때 바뀌는 UIView - private lazy var blankOnOffUIView: UIView = { + /// On UIView + private lazy var onUIView: UIView = { let view = UIView() view.backgroundColor = .white view.layer.maskedCorners = CACornerMask(arrayLiteral: .layerMinXMinYCorner, .layerMaxXMinYCorner) @@ -83,16 +83,16 @@ final class HomeViewController: UIViewController { // MARK: - View Did Load override func viewDidLoad() { super.viewDidLoad() - + addBaseSubViews() bind() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - blankOnOffUIView.layer.shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, - width: blankOnOffUIView.frame.width, - height: blankOnOffUIView.frame.height - 50)).cgPath + onUIView.layer.shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, + width: onUIView.frame.width, + height: onUIView.frame.height - 50)).cgPath } @@ -104,7 +104,6 @@ final class HomeViewController: UIViewController { view.addSubview(dayImageView) view.addSubview(monthLabel) view.addSubview(dayCollectionView) - view.addSubview(blankOnOffUIView) baseConstraints() } @@ -144,8 +143,18 @@ final class HomeViewController: UIViewController { make.trailing.equalToSuperview().offset(-6) make.height.equalTo(70) } + } + + /// On UI Add View + private func addOnSubViews() { + view.addSubview(onUIView) - blankOnOffUIView.snp.makeConstraints { make in + onConstraints() + } + + /// On UI Constraints + private func onConstraints() { + onUIView.snp.makeConstraints { make in make.top.equalTo(dayCollectionView.snp.bottom).offset(20) make.horizontalEdges.equalToSuperview() make.bottom.equalToSuperview() @@ -164,6 +173,11 @@ final class HomeViewController: UIViewController { bindOnOffButton(output: output) bindBackGroundColor(output: output) bindBlankViewShadowColor(output: output) + bindToggleOnOffButton(output: output) + bindAddWorkLifeBalanceFeedButton() + bindSelectedFeedTableViewCell() + + } /// Binding Day CollectionView Cell @@ -236,11 +250,78 @@ final class HomeViewController: UIViewController { output.blankUIViewShadowColorRelay .bind { [weak self] color in guard let self = self else { return } - blankOnOffUIView.layer.shadowColor = color.cgColor + onUIView.layer.shadowColor = color.cgColor + } + .disposed(by: disposeBag) + } + + /// Binding toggle On - Off Button + private func bindToggleOnOffButton(output: HomeViewModel.Output) { + output.toggleOnOffButtonRelay + .bind { [weak self] check in + guard let self = self else { return } + if check { + onUIView.removeFromSuperview() + addOnSubViews() + return + } + onUIView.removeFromSuperview() + addOnSubViews() + } + .disposed(by: disposeBag) + } + + /// 워라벨 피드 추가 버튼 + private func bindAddWorkLifeBalanceFeedButton() { + onUIView.clickedAddfeedButton + .bind { [weak self] in + guard let self = self else { return } + presentInsertWLBFeedView(insertFeed: nil) + } + .disposed(by: disposeBag) + } + + /// 워라벨 피드 클릭한 경우 + private func bindSelectedFeedTableViewCell() { + offUIView.selectedFeedTableViewCell + .bind { [weak self] feed in + guard let self = self else { return } + let clickWorkLifeBalanceFeedView = ClickWorkLifeBalanceFeedView() + clickWorkLifeBalanceFeedView.feedSubject.onNext(feed) + clickWorkLifeBalanceFeedView.successConnect + .bind { [weak self] in + guard let self = self else { return } + onUIView.successAddFeed.onNext(()) + } + .disposed(by: disposeBag) + + clickWorkLifeBalanceFeedView.insertFeedSubject + .bind { [weak self] feed in + guard let self = self else { return } + presentInsertWLBFeedView(insertFeed: feed) + } + .disposed(by: disposeBag) + present(clickWorkLifeBalanceFeedView, animated: true) } .disposed(by: disposeBag) } + /// Present Insert W.L.B Feed View + private func presentInsertWLBFeedView(insertFeed: Feed?) { + let insertWorkLifeBalanceFeedView = InsertWorkLifeBalanceFeedView() + if let insertFeed = insertFeed { + insertWorkLifeBalanceFeedView.insertFeed.onNext(insertFeed) + } + insertWorkLifeBalanceFeedView.successAddFeedSubject + .bind { [weak self] in + guard let self = self else { return } + onUIView.successAddFeed.onNext(()) + } + .disposed(by: disposeBag) + present(insertWorkLifeBalanceFeedView, animated: true) + } + + } extension HomeViewController: UICollectionViewDelegateFlowLayout { diff --git a/On_off_iOS/On_off_iOS/Home/On/Model/On.swift b/On_off_iOS/On_off_iOS/Home/On/Model/On.swift deleted file mode 100644 index 7889d90..0000000 --- a/On_off_iOS/On_off_iOS/Home/On/Model/On.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// On.swift -// On_off_iOS -// -// Created by 신예진 on 2/2/24. -// - -import Foundation - -struct OnDayInfo { - /// 일, 숫자 - let date: String? - - /// 요일, 문자 (월, 화, 수, 목, 금) - let day: String? -} diff --git a/On_off_iOS/On_off_iOS/Home/On/TodayResolution/Model/TodayResolution.swift b/On_off_iOS/On_off_iOS/Home/On/TodayResolution/Model/TodayResolution.swift new file mode 100644 index 0000000..c0c1bc8 --- /dev/null +++ b/On_off_iOS/On_off_iOS/Home/On/TodayResolution/Model/TodayResolution.swift @@ -0,0 +1,20 @@ +// +// TodayResolution.swift +// On_off_iOS +// +// Created by 신예진 on 2/2/24. +// + +import Foundation + +struct Resolution: Codable { + let resolutionID: Int? + let order: Int? + let content: String? +} + +//추가 +struct AddResolution: Codable { + let date: String? + let content: String? +} diff --git a/On_off_iOS/On_off_iOS/Home/TodayResolution/View/AddWriteViewController.swift b/On_off_iOS/On_off_iOS/Home/On/TodayResolution/View/AddWriteViewController.swift similarity index 100% rename from On_off_iOS/On_off_iOS/Home/TodayResolution/View/AddWriteViewController.swift rename to On_off_iOS/On_off_iOS/Home/On/TodayResolution/View/AddWriteViewController.swift diff --git a/On_off_iOS/On_off_iOS/Home/TodayResolution/View/MenuModalViewController.swift b/On_off_iOS/On_off_iOS/Home/On/TodayResolution/View/MenuModalViewController.swift similarity index 100% rename from On_off_iOS/On_off_iOS/Home/TodayResolution/View/MenuModalViewController.swift rename to On_off_iOS/On_off_iOS/Home/On/TodayResolution/View/MenuModalViewController.swift diff --git a/On_off_iOS/On_off_iOS/Home/TodayResolution/View/ResolutionWriteViewController.swift b/On_off_iOS/On_off_iOS/Home/On/TodayResolution/View/ResolutionWriteViewController.swift similarity index 100% rename from On_off_iOS/On_off_iOS/Home/TodayResolution/View/ResolutionWriteViewController.swift rename to On_off_iOS/On_off_iOS/Home/On/TodayResolution/View/ResolutionWriteViewController.swift diff --git a/On_off_iOS/On_off_iOS/Home/TodayResolution/View/TodayResolutionViewController.swift b/On_off_iOS/On_off_iOS/Home/On/TodayResolution/View/TodayResolutionViewController.swift similarity index 100% rename from On_off_iOS/On_off_iOS/Home/TodayResolution/View/TodayResolutionViewController.swift rename to On_off_iOS/On_off_iOS/Home/On/TodayResolution/View/TodayResolutionViewController.swift diff --git a/On_off_iOS/On_off_iOS/Home/TodayResolution/ViewModel/AddWriteViewModel.swift b/On_off_iOS/On_off_iOS/Home/On/TodayResolution/ViewModel/AddWriteViewModel.swift similarity index 100% rename from On_off_iOS/On_off_iOS/Home/TodayResolution/ViewModel/AddWriteViewModel.swift rename to On_off_iOS/On_off_iOS/Home/On/TodayResolution/ViewModel/AddWriteViewModel.swift diff --git a/On_off_iOS/On_off_iOS/Home/TodayResolution/ViewModel/MenuModalViewModel.swift b/On_off_iOS/On_off_iOS/Home/On/TodayResolution/ViewModel/MenuModalViewModel.swift similarity index 100% rename from On_off_iOS/On_off_iOS/Home/TodayResolution/ViewModel/MenuModalViewModel.swift rename to On_off_iOS/On_off_iOS/Home/On/TodayResolution/ViewModel/MenuModalViewModel.swift diff --git a/On_off_iOS/On_off_iOS/Home/TodayResolution/ViewModel/ResolutionWriteViewModel.swift b/On_off_iOS/On_off_iOS/Home/On/TodayResolution/ViewModel/ResolutionWriteViewModel.swift similarity index 100% rename from On_off_iOS/On_off_iOS/Home/TodayResolution/ViewModel/ResolutionWriteViewModel.swift rename to On_off_iOS/On_off_iOS/Home/On/TodayResolution/ViewModel/ResolutionWriteViewModel.swift diff --git a/On_off_iOS/On_off_iOS/Home/TodayResolution/ViewModel/TodayResolutionViewModel.swift b/On_off_iOS/On_off_iOS/Home/On/TodayResolution/ViewModel/TodayResolutionViewModel.swift similarity index 100% rename from On_off_iOS/On_off_iOS/Home/TodayResolution/ViewModel/TodayResolutionViewModel.swift rename to On_off_iOS/On_off_iOS/Home/On/TodayResolution/ViewModel/TodayResolutionViewModel.swift diff --git a/On_off_iOS/On_off_iOS/Home/On/View/OnViewController.swift b/On_off_iOS/On_off_iOS/Home/On/View/OnViewController.swift deleted file mode 100644 index 8576816..0000000 --- a/On_off_iOS/On_off_iOS/Home/On/View/OnViewController.swift +++ /dev/null @@ -1,258 +0,0 @@ -// -// OnViewController.swift -// On_off_iOS -// -// Created by 신예진 on 2/2/24. -// - -import Foundation -import RxCocoa -import RxSwift -import SnapKit -import UIKit - -final class OnViewController: UIViewController { - - /// Safe Area Top Layout UIView - private lazy var safeAreaTopUIView: UIView = { - let view = UIView() - return view - }() - - /// On - Off Button - private lazy var onOffButton: UIButton = { - let btn = UIButton() - btn.backgroundColor = .clear - return btn - }() - - /// Title Label - private lazy var titleLabel: UILabel = { - let label = UILabel() - label.numberOfLines = 2 - label.font = .systemFont(ofSize: 24) - label.backgroundColor = .clear - return label - }() - - /// On - Off 에 따른 이미지 뷰 - private lazy var dayImageView: UIImageView = { - let view = UIImageView() - view.contentMode = .scaleAspectFill - view.backgroundColor = .clear - return view - }() - - /// 현재 달, 연도 - private lazy var monthLabel: UILabel = { - let label = UILabel() - label.font = .systemFont(ofSize: 16, weight: .bold) - label.textColor = .purple - label.backgroundColor = .clear - return label - }() - - /// "일" 스크롤 뷰 - private lazy var dayCollectionView: UICollectionView = { - let layout = UICollectionViewFlowLayout() - layout.scrollDirection = .horizontal - let view = UICollectionView(frame: .zero, collectionViewLayout: layout) - view.backgroundColor = .clear - view.isScrollEnabled = false - view.register(DayCollectionViewCell.self, forCellWithReuseIdentifier: CellIdentifier.DayCollectionViewCell.rawValue) - return view - }() - - /// On - Off 될때 바뀌는 UIView - private lazy var blankOnOffUIView: UIView = { - let view = UIView() - view.backgroundColor = .white - view.layer.maskedCorners = CACornerMask(arrayLiteral: .layerMinXMinYCorner, .layerMaxXMinYCorner) - view.layer.cornerRadius = 25 - - view.layer.shadowRadius = 10 - view.layer.shadowOffset = CGSize(width: 0, height: -10) - view.layer.shadowOpacity = 0.5 - - return view - }() - - private let disposeBag = DisposeBag() - - private let viewModel = OnViewModel() - - // MARK: - View Did Load - override func viewDidLoad() { - super.viewDidLoad() - - addOnSubViews() - bind() - } - - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - blankOnOffUIView.layer.shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, - width: blankOnOffUIView.frame.width, - height: blankOnOffUIView.frame.height - 50)).cgPath - - } - - /// On - Off 공통 UI Add View - private func addOnSubViews() { - view.addSubview(safeAreaTopUIView) - view.addSubview(onOffButton) - view.addSubview(titleLabel) - view.addSubview(dayImageView) - view.addSubview(monthLabel) - view.addSubview(dayCollectionView) - view.addSubview(blankOnOffUIView) - - OnConstraints() - } - - /// On - Off 공통 UI Constraints - private func OnConstraints() { - onOffButton.snp.makeConstraints { make in - make.top.equalTo(view.safeAreaLayoutGuide).offset(5) - make.trailing.equalTo(view.safeAreaLayoutGuide).offset(-10) - } - - safeAreaTopUIView.snp.makeConstraints { make in - make.horizontalEdges.equalToSuperview() - make.bottom.equalTo(onOffButton.snp.top) - make.height.equalTo(200) - } - - titleLabel.snp.makeConstraints { make in - make.top.equalTo(onOffButton.snp.bottom).offset(10) - make.leading.equalTo(view.safeAreaLayoutGuide).offset(10) - } - - dayImageView.snp.makeConstraints { make in - make.top.equalTo(titleLabel.snp.top) - make.leading.equalTo(titleLabel.snp.trailing).offset(30) - make.bottom.equalTo(titleLabel.snp.bottom) - } - - monthLabel.snp.makeConstraints { make in - make.leading.equalTo(titleLabel.snp.leading) - make.top.equalTo(titleLabel.snp.bottom).offset(10) - } - dayCollectionView.snp.makeConstraints { make in - make.top.equalTo(monthLabel.snp.bottom).offset(10) - make.leading.equalToSuperview().offset(10) - make.trailing.equalToSuperview().offset(-6) - make.height.equalTo(70) - } - - blankOnOffUIView.snp.makeConstraints { make in - make.top.equalTo(dayCollectionView.snp.bottom).offset(20) - make.horizontalEdges.equalToSuperview() - make.bottom.equalToSuperview() - } - } - - /// Binding - private func bind() { - let input = OnViewModel.Input(onOffButtonEvents: onOffButton.rx.tap) - let output = viewModel.createOutput(input: input) - - bindDayCollectionView(output: output) - bindMonthLabel(output: output) - bindTitleLabel(output: output) - bindDayImageView(output: output) - bindOnOffButton(output: output) - bindBackGroundColor(output: output) - bindBlankViewShadowColor(output: output) - } - - /// Binding Day CollectionView Cell - private func bindDayCollectionView(output: OnViewModel.Output) { - output.dayListRelay - .bind(to: dayCollectionView.rx - .items(cellIdentifier: CellIdentifier.DayCollectionViewCell.rawValue, - cellType: DayCollectionViewCell.self)) - { row, element, cell in - cell.backgroundColor = .clear - cell.inputData(info: element, color: output.dayCollectionViewBackgroundColorRelay.value) - } - .disposed(by: disposeBag) - - dayCollectionView.rx.setDelegate(self) - .disposed(by: disposeBag) - } - - /// Binding Month Label - private func bindMonthLabel(output: OnViewModel.Output) { - output.monthRelay - .bind { [weak self] month in - guard let self = self else { return } - monthLabel.attributedText = month - dayCollectionView.reloadData() - } - .disposed(by: disposeBag) - } - - /// Binding Title Label - private func bindTitleLabel(output: OnViewModel.Output) { - output.titleRelay - .bind { [weak self] title in - guard let self = self else { return } - titleLabel.attributedText = title - } - .disposed(by: disposeBag) - } - - /// Binding Day Image View - private func bindDayImageView(output: OnViewModel.Output) { - output.dayImageRelay - .bind(to: dayImageView.rx.image) - .disposed(by: disposeBag) - } - - /// Binding On Off Button - private func bindOnOffButton(output: OnViewModel.Output) { - output.buttonOnOffRelay - .bind { [weak self] image in - guard let self = self else { return } - onOffButton.setImage(image?.resize(newWidth: 50), for: .normal) - } - .disposed(by: disposeBag) - } - - /// Binding BackGround Color - private func bindBackGroundColor(output: OnViewModel.Output) { - output.backgroundColorRelay - .bind { [weak self] color in - guard let self = self else { return } - view.backgroundColor = color - safeAreaTopUIView.backgroundColor = color - } - .disposed(by: disposeBag) - } - - /// Binding BlankView Shadow Color - private func bindBlankViewShadowColor(output: OnViewModel.Output) { - output.blankUIViewShadowColorRelay - .bind { [weak self] color in - guard let self = self else { return } - blankOnOffUIView.layer.shadowColor = color.cgColor - } - .disposed(by: disposeBag) - } - -} - -extension OnViewController: UICollectionViewDelegateFlowLayout { - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - sizeForItemAt indexPath: IndexPath) -> CGSize { - let interval: CGFloat = 10 - let width: CGFloat = (collectionView.frame.width - interval * 2) / 8 - return CGSize(width: width, height: collectionView.frame.height) - } - - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - minimumLineSpacingForSectionAt section: Int) -> CGFloat { 10 } -} diff --git a/On_off_iOS/On_off_iOS/Home/On/ViewModel/OnViewModel.swift b/On_off_iOS/On_off_iOS/Home/On/ViewModel/OnViewModel.swift deleted file mode 100644 index 410093c..0000000 --- a/On_off_iOS/On_off_iOS/Home/On/ViewModel/OnViewModel.swift +++ /dev/null @@ -1,176 +0,0 @@ -// -// OnViewModel.swift -// On_off_iOS -// -// Created by 신예진 on 2/2/24. -// - -import Foundation -import RxSwift -import RxRelay -import RxCocoa -import UIKit - -final class OnViewModel { - private let disposeBag = DisposeBag() - - struct Input { - let onOffButtonEvents: ControlEvent - } - - struct Output { - /// On-Off 에 따른 Button Image - /// On or Off - var buttonOnOffRelay: BehaviorRelay = BehaviorRelay(value: nil) - - /// Title(제목) Relay - var titleRelay: BehaviorRelay = BehaviorRelay(value: nil) - - /// On-Off 에 따른 Title Image - /// moon or sun - var dayImageRelay: BehaviorRelay = BehaviorRelay(value: nil) - - /// 현재 달, 연도 - var monthRelay: BehaviorRelay = BehaviorRelay(value: nil) - - /// 날짜(일) 리스트 - var dayListRelay: BehaviorRelay<[DayInfo]> = BehaviorRelay(value: []) - - /// 전체 View 배경 색 - var backgroundColorRelay: BehaviorRelay = BehaviorRelay(value: UIColor.white) - - /// Day Collection 배경 색 - var dayCollectionViewBackgroundColorRelay: BehaviorRelay = BehaviorRelay(value: UIColor.cyan) - - /// On - Off 변하는 UIView 그림자 색 - var blankUIViewShadowColorRelay: BehaviorRelay = BehaviorRelay(value: UIColor.purple) - - /// On Off 버튼 터치에 따른 값 변환 - /// True: On - /// False: Off - var toggleOnOffButtonRelay: BehaviorRelay = BehaviorRelay(value: true) - } - - /// Create Output - /// - Parameter input: Input - /// - Returns: Output - func createOutput(input: Input) -> Output{ - let output = Output() - - input.onOffButtonEvents - .bind { - output.toggleOnOffButtonRelay.accept(!output.toggleOnOffButtonRelay.value) - } - .disposed(by: disposeBag) - - bindToggleOnOffButtonRelay(output: output) - dummyDayListRelay(output: output) - return output - } - - /// Bind Toggle When OnOffButton Touches - private func bindToggleOnOffButtonRelay(output: Output) { - output.toggleOnOffButtonRelay - .bind { [weak self] check in - guard let self = self else { return } - - if check { // On 인경우 - setUpWhenOn(output: output) - return - } - - // Off인 경우 - setUpWhenOff(output: output) - } - .disposed(by: disposeBag) - } - - /// Setting When On - private func setUpWhenOn(output: Output) { - output.dayImageRelay.accept(UIImage(named: "sun")) - output.buttonOnOffRelay.accept(UIImage(named: "on")) - output.backgroundColorRelay.accept(.white) - output.titleRelay.accept(setTitleOptions(nickName: "조디조디조디조디조디", nickNameColor: .purple, - subTitle: "님,\n오늘 하루도 파이팅!", subTitleColor: .black, - output: output)) - output.monthRelay.accept(setMonthOptions(month: "2023년 11월", - monthColor: .purple, - output: output)) - output.dayCollectionViewBackgroundColorRelay.accept(.cyan) - output.blankUIViewShadowColorRelay.accept(.purple) - } - - /// Setting When Off - private func setUpWhenOff(output: Output) { - output.dayImageRelay.accept(UIImage(named: "moon")) - output.buttonOnOffRelay.accept(UIImage(named: "off")) - output.backgroundColorRelay.accept(.blue) - output.titleRelay.accept(setTitleOptions(nickName: "조디조디조디조디조디", nickNameColor: .cyan, - subTitle: "님,\n오늘 하루도 고생하셨어요", subTitleColor: .white, - output: output)) - output.monthRelay.accept(setMonthOptions(month: "2023년 11월", - monthColor: .white, - output: output)) - output.dayCollectionViewBackgroundColorRelay.accept(.purple) - output.blankUIViewShadowColorRelay.accept(.white) - } - - /// Month Label On Off에 따라 설정 변경 - /// - Parameters: - /// - month: Month - /// - monthColor: Month Color - /// - output: Output - /// - Returns: 변경된 NSMutableAttributedString - private func setMonthOptions(month: String, - monthColor: UIColor, - output: Output) -> NSMutableAttributedString { - let attributedString = NSMutableAttributedString(string: month) - attributedString.addAttribute(.foregroundColor, value: monthColor, - range: (month as NSString).range(of: month)) - attributedString.addAttribute(.font, value: UIFont.systemFont(ofSize: 18, weight: .bold), - range: (month as NSString).range(of: month)) - return attributedString - } - - - /// Title Label On Off에 따라 설정 변경 - /// - Parameters: - /// - nickName: 사용자 NickName - /// - nickNameColor: NickName Color - /// - subTitle: NickName 뒤에 쓰는 붙임 말 - /// - subTitleColor: SubTitle Color - /// - output: Output - /// - Returns: 변경된 NSMutableAttributedString - private func setTitleOptions(nickName: String, - nickNameColor: UIColor, - subTitle: String, - subTitleColor: UIColor, - output: Output) -> NSMutableAttributedString { - let nickNameAttributedString = NSMutableAttributedString(string: nickName) - nickNameAttributedString.addAttribute(.foregroundColor, value: nickNameColor, - range: (nickName as NSString).range(of: nickName)) - nickNameAttributedString.addAttribute(.font, value: UIFont.systemFont(ofSize: 24, weight: .bold), - range: (nickName as NSString).range(of: nickName)) - - let subTitleAttributedString = NSMutableAttributedString(string: subTitle) - subTitleAttributedString.addAttribute(.foregroundColor, value: subTitleColor, - range: (subTitle as NSString).range(of: subTitle)) - subTitleAttributedString.addAttribute(.font, value: UIFont.systemFont(ofSize: 24, weight: .bold), - range: (subTitle as NSString).range(of: subTitle)) - - nickNameAttributedString.append(subTitleAttributedString) - return nickNameAttributedString - } - - /// Dummy About DayListRelay - private func dummyDayListRelay(output: Output) { - let list = [DayInfo(date: "20", day: "Mon"), - DayInfo(date: "21", day: "Tue"), - DayInfo(date: "22", day: "Wed"), - DayInfo(date: "23", day: "Thr"), - DayInfo(date: "24", day: "Fri"), - DayInfo(date: "25", day: "Sat"), - DayInfo(date: "26", day: "Sun")] - output.dayListRelay.accept(list) - } -} diff --git a/On_off_iOS/On_off_iOS/Home/On/Worklog/Model/Worklog.swift b/On_off_iOS/On_off_iOS/Home/On/Worklog/Model/Worklog.swift new file mode 100644 index 0000000..3d2634c --- /dev/null +++ b/On_off_iOS/On_off_iOS/Home/On/Worklog/Model/Worklog.swift @@ -0,0 +1,19 @@ +// +// Worklog.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation + +struct Worklog: Codable { + let worklogId: Int? + let content: String? + let isChecked: Bool? +} + +struct AddWorklog: Codable { + let date: String? + let content: String? +} diff --git a/On_off_iOS/On_off_iOS/Home/On/Worklog/Service/ClickWorklogService.swift b/On_off_iOS/On_off_iOS/Home/On/Worklog/Service/ClickWorklogService.swift new file mode 100644 index 0000000..f4b4059 --- /dev/null +++ b/On_off_iOS/On_off_iOS/Home/On/Worklog/Service/ClickWorklogService.swift @@ -0,0 +1,65 @@ +// +// ClickWorklogService.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation +import Alamofire +import RxSwift + +final class ClickWorklogService { + + /// 내일로 미루기 + /// - Parameter worklogid: worklog id + /// - Returns: true false + func delayTomorrow(worklogid: Int) -> Observable { + let url = Domain.RESTAPI + WorklogPath.delayTomorrow.rawValue + .replacingOccurrences(of: "worklogid", with: "\(worklogid)") + let header = Header.header.getHeader() + + return Observable.create { observer in + AF.request(url, + method: .patch, + headers: header) + .responseDecodable(of: Response.self) { response in + print(#function, response) + switch response.result { + case .success(let data): + observer.onNext(data.isSuccess) + case .failure(let error): + observer.onError(error) + } + } + return Disposables.create() + } + } + + + /// 피드 삭제 + /// - Parameter WorklogId: Worklog Id + /// - Returns: true false + func deletelog(worklogid: Int) -> Observable { + let url = Domain.RESTAPI + WorklogPath.delete.rawValue + .replacingOccurrences(of: "worklogid", with: "\(worklogid)") + let header = Header.header.getHeader() + + return Observable.create { observer in + AF.request(url, + method: .delete, + headers: header) + .responseDecodable(of: Response.self) { response in + print(#function, response) + switch response.result { + case .success(let data): + observer.onNext(data.isSuccess) + case .failure(let error): + observer.onError(error) + } + } + return Disposables.create() + } + } + +} diff --git a/On_off_iOS/On_off_iOS/Home/On/Worklog/Service/InsertWorkLogService.swift b/On_off_iOS/On_off_iOS/Home/On/Worklog/Service/InsertWorkLogService.swift new file mode 100644 index 0000000..c70273f --- /dev/null +++ b/On_off_iOS/On_off_iOS/Home/On/Worklog/Service/InsertWorkLogService.swift @@ -0,0 +1,74 @@ +// +// InsertWorklogService.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation +import Alamofire +import RxSwift +import Network + +final class InsertWorkLogService { + + /// Add Worklog + /// - Parameter Worklog: 추가할 log + /// - Returns: Worklog + func addWorklog(Worklog: AddWorklog) -> Observable { + let url = Domain.RESTAPI + WorklogPath.worklog.rawValue + let header = Header.header.getHeader() + print(url) + + return Observable.create { observer in + AF.request(url, + method: .post, + parameters: Worklog, + encoder: JSONParameterEncoder.default, + headers: header) + .validate(statusCode: 200..<201) + .responseDecodable(of: Response.self) { response in + print(#function, response) + switch response.result { + case .success(let data): + observer.onNext(data.isSuccess) + case .failure(let error): + observer.onError(error) + } + } + + return Disposables.create() + } + } + + /// Insert Worklog + /// - Parameter WorklogID: 수정할 Worklog ID + /// - Returns: true false + func insertlog(worklogid: Int, content: String) -> Observable { + let url = Domain.RESTAPI + WorklogPath.delete.rawValue + .replacingOccurrences(of: "worklogid", with: "\(worklogid)") + let body: Parameters = [ "content": content ] + let header = Header.header.getHeader() + print(url) + + return Observable.create { observer in + AF.request(url, + method: .patch, + parameters: body, + encoding: JSONEncoding.default, + headers: header) + .validate(statusCode: 200..<201) + .responseDecodable(of: Response.self) { response in + print(#function, response) + switch response.result { + case .success(let data): + observer.onNext(data.isSuccess) + case .failure(let error): + observer.onError(error) + } + } + + return Disposables.create() + } + } +} diff --git a/On_off_iOS/On_off_iOS/Home/On/Worklog/Service/OnUIViewService.swift b/On_off_iOS/On_off_iOS/Home/On/Worklog/Service/OnUIViewService.swift new file mode 100644 index 0000000..37355aa --- /dev/null +++ b/On_off_iOS/On_off_iOS/Home/On/Worklog/Service/OnUIViewService.swift @@ -0,0 +1,69 @@ +// +// OnUIViewService.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation +import Alamofire +import RxSwift +import UIKit +import Network + +final class OnUIViewService { + private let disposeBag = DisposeBag() + + /// Worklog 목록 불러오기 + /// - Parameter date: 선택한 날짜 + /// - Returns: Feed List + func getWLList(date: String) -> Observable<[Worklog]> { + let url = Domain.RESTAPI + WorklogPath.worklog.rawValue + .replacingOccurrences(of: "DATE", with: "\(date)") + let header = Header.header.getHeader() + print(url) + + return Observable.create { observer in + AF.request(url, + method: .get, + headers: header) + .validate(statusCode: 200..<201) + .responseDecodable(of: Response<[Worklog]>.self) { response in + print(#function, response) + switch response.result { + case .success(let data): + observer.onNext(data.result) + case .failure(let error): + observer.onError(error) + } + } + return Disposables.create() + } + } + + /// Check Worklog + /// - Parameter worklogId: worklog Id + /// - Returns: 성공 여부 + func checkWLFeed(worklogid: Int) -> Observable { + let url = Domain.RESTAPI + WorklogPath.checkWL.rawValue + .replacingOccurrences(of: "worklogid", with: "\(worklogid)") + let header = Header.header.getHeader() + print(url) + return Observable.create { observer in + AF.request(url, + method: .patch, + headers: header) + .validate(statusCode: 200..<201) + .responseDecodable(of: Response.self) { response in + print(#function, response) + switch response.result { + case .success(let data): + observer.onNext(data.isSuccess) + case .failure(let error): + observer.onError(error) + } + } + return Disposables.create() + } + } +} diff --git a/On_off_iOS/On_off_iOS/Home/On/Worklog/View/ClickWorklogView.swift b/On_off_iOS/On_off_iOS/Home/On/Worklog/View/ClickWorklogView.swift new file mode 100644 index 0000000..5fdbdd9 --- /dev/null +++ b/On_off_iOS/On_off_iOS/Home/On/Worklog/View/ClickWorklogView.swift @@ -0,0 +1,359 @@ +// +// ClickWorklogView.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation +import UIKit +import SnapKit + +final class ClickWorklogView: DimmedViewController { + + /// 배경 뷰 + private lazy var baseUIView: UIView = { + let view = UIView() + view.backgroundColor = .white + view.layer.cornerRadius = 20 + return view + }() + + /// 제목 라벨 + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.text = "워라벨 피드" + label.textColor = .OnOffMain + label.font = .pretendard(size: 18, weight: .bold) + label.backgroundColor = .clear + return label + }() + + /// 부제목 라벨 + private lazy var subTitleLabel: UILabel = { + let label = UILabel() + label.textColor = .black + label.font = .pretendard(size: 18, weight: .bold) + label.backgroundColor = .clear + return label + }() + + /// 구분 선 + private lazy var divideLineView: UIView = { + let view = UIView() + view.backgroundColor = .OnOffMain + return view + }() + + /// 완료 버튼 + private lazy var doneButton: UIButton = { + let button = UIButton() + button.backgroundColor = .OnOffMain + button.setTitle("완료", for: .normal) + button.setTitleColor(.white, for: .normal) + button.titleLabel?.font = .pretendard(size: 18, weight: .bold) + button.layer.cornerRadius = 15 + return button + }() + + /// 미루기 버튼 + private lazy var delayButton: FeedOptionButton = { + let button = FeedOptionButton() + button.inputData(image: "calendar", + title: "내일로 미루기") + button.backgroundColor = .clear + return button + }() + + /// Insert Button + private lazy var insertButton: FeedOptionButton = { + let button = FeedOptionButton() + button.inputData(image: "insert", + title: "수정하기") + button.backgroundColor = .clear + return button + }() + + /// Delete 버튼 + private lazy var deleteButton: FeedOptionButton = { + let button = FeedOptionButton() + button.inputData(image: "delete", + title: "삭제하기") + button.backgroundColor = .clear + return button + }() + + /// 옵션 StackView + private lazy var optionStackView: UIStackView = { + let view = UIStackView(arrangedSubviews: [delayButton, insertButton, deleteButton]) + view.backgroundColor = .clear + view.axis = .vertical + view.distribution = .fillEqually + view.spacing = 10 + return view + }() + + /// 내일로 미루기 body 라벨 + private lazy var contentLabel: UILabel = { + let label = UILabel() + label.text = "'asfasfsfsfs'를\n다음날인 로 미루시겠어요?" + label.textColor = .black + label.textAlignment = .center + label.numberOfLines = 2 + label.font = .pretendard(size: 18, weight: .bold) + label.backgroundColor = .clear + return label + }() + + /// 완료 버튼 + private lazy var cancelButton: UIButton = { + let button = UIButton() + button.backgroundColor = .white + button.setTitle("취소", for: .normal) + button.setTitleColor(.OnOffMain, for: .normal) + button.titleLabel?.font = .pretendard(size: 18, weight: .bold) + button.layer.cornerRadius = 15 + button.layer.borderWidth = 1 + button.layer.borderColor = UIColor.OnOffMain.cgColor + return button + }() + + /// 최종 미루기 버튼 + private lazy var completeDelayButton: UIButton = { + let button = UIButton() + button.backgroundColor = .OnOffMain + button.setTitleColor(.white, for: .normal) + button.titleLabel?.font = .pretendard(size: 18, weight: .bold) + button.layer.cornerRadius = 15 + return button + }() + + /// 최종 삭제 버튼 + private lazy var completeDeleteButton: UIButton = { + let button = UIButton() + button.backgroundColor = .OnOffMain + button.setTitleColor(.white, for: .normal) + button.titleLabel?.font = .pretendard(size: 18, weight: .bold) + button.layer.cornerRadius = 15 + return button + }() + + /// 버튼 StackView + private lazy var buttonStackView: UIStackView = { + let view = UIStackView() + view.backgroundColor = .clear + view.axis = .horizontal + view.distribution = .fillEqually + view.spacing = 15 + return view + }() + + var feedSubject: PublishSubject = PublishSubject() + var successConnect: PublishSubject = PublishSubject() + var insertFeedSubject: PublishSubject = PublishSubject() + private let disposeBag = DisposeBag() + private let viewModel = ClickWorkLifeBalanceFeedViewModel() + + // MARK: - Init + init() { + super.init(durationTime: 0.3, alpha: 0.7) + addSubviews() + bind() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Add Subviews + private func addSubviews() { + view.addSubview(baseUIView) + baseUIView.addSubview(titleLabel) + baseUIView.addSubview(subTitleLabel) + baseUIView.addSubview(divideLineView) + baseUIView.addSubview(doneButton) + baseUIView.addSubview(optionStackView) + + constraints() + } + + /// Constraints + private func constraints() { + baseUIView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.horizontalEdges.equalToSuperview().inset(20) + } + + titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview().offset(20) + make.centerX.equalToSuperview() + } + + subTitleLabel.snp.makeConstraints { make in + make.top.equalTo(titleLabel.snp.bottom) + make.centerX.equalToSuperview() + } + + divideLineView.snp.makeConstraints { make in + make.top.equalTo(subTitleLabel.snp.bottom).offset(10) + make.horizontalEdges.equalToSuperview().inset(20) + make.height.equalTo(1) + } + + optionStackView.snp.makeConstraints { make in + make.top.equalTo(divideLineView.snp.bottom).offset(20) + make.horizontalEdges.equalToSuperview().inset(20) + make.bottom.equalTo(doneButton.snp.top).offset(-10) + } + + doneButton.snp.makeConstraints { make in + make.bottom.equalToSuperview().offset(-10) + make.centerX.equalToSuperview() + make.width.equalToSuperview().multipliedBy(0.2) + } + } + + /// Delay AddSubViews + private func delayAddSubViews() { + optionStackView.removeFromSuperview() + doneButton.removeFromSuperview() + baseUIView.addSubview(contentLabel) + baseUIView.addSubview(buttonStackView) + + delayConstraints() + } + + /// Delay Constraints + private func delayConstraints() { + contentLabel.snp.makeConstraints { make in + make.top.equalTo(divideLineView.snp.bottom).offset(30) + make.bottom.equalTo(buttonStackView.snp.top).offset(-30) + make.centerX.equalToSuperview() + } + + buttonStackView.snp.makeConstraints { make in + make.bottom.equalToSuperview().offset(-10) + make.centerX.equalToSuperview() + make.width.equalToSuperview().multipliedBy(0.55) + } + } + + /// Binding + private func bind() { + let input = ClickWorkLifeBalanceFeedViewModel.Input(completeDelayButtonEvents: completeDelayButton.rx.tap, + deleteButtonEvents: completeDeleteButton.rx.tap, + selectedFeed: feedSubject) + + let output = viewModel.createOutput(input: input) + + bindCancelButton() + bindDoneButton() + bindInsertButton(output: output) + bindDeleteButton(output: output) + bindDelayButton(output: output) + bindSuccessDelay(output: output) + bindSelectedFeed(output: output) + } + + /// Bind Selected Feed + private func bindSelectedFeed(output: ClickWorkLifeBalanceFeedViewModel.Output) { + output.selectedFeedRelay + .bind { [weak self] feed in + guard let self = self, let content = feed?.content else { return } + subTitleLabel.text = content + } + .disposed(by: disposeBag) + } + + /// 내일로 미루기 버튼 + private func bindDelayButton(output: ClickWorkLifeBalanceFeedViewModel.Output) { + delayButton.rx.tap + .bind { [weak self] in + guard let self = self, + let content = output.selectedFeedRelay.value?.content else { return } + delayAddSubViews() + + buttonStackView.removeArrangedSubview(completeDeleteButton) + buttonStackView.addArrangedSubview(cancelButton) + buttonStackView.addArrangedSubview(completeDelayButton) + + completeDelayButton.setTitle("미루기", for: .normal) + contentLabel.text = "\(content)를\n다음날인 \(output.nextDay.value)로 미루시겠어요?" + } + .disposed(by: disposeBag) + } + + /// 확인 버튼 + private func bindDoneButton() { + doneButton.rx.tap + .bind { [weak self] _ in + guard let self = self else { return } + dismiss(animated: true) + } + .disposed(by: disposeBag) + } + + /// 취소 버튼 + private func bindCancelButton() { + cancelButton.rx.tap + .bind { [weak self] _ in + guard let self = self else { return } + dismiss(animated: true) + } + .disposed(by: disposeBag) + } + + /// 수정하기 버튼 + private func bindInsertButton(output: ClickWorkLifeBalanceFeedViewModel.Output) { + insertButton.rx.tap + .bind { [weak self] in + guard let self = self, let feed = output.selectedFeedRelay.value else { return } + dismiss(animated: true) + insertFeedSubject.onNext(feed) + } + .disposed(by: disposeBag) + } + + /// 내일로 미루기 버튼 + private func bindDeleteButton(output: ClickWorkLifeBalanceFeedViewModel.Output) { + deleteButton.rx.tap + .bind { [weak self] in + guard let self = self, + let content = output.selectedFeedRelay.value?.content else { return } + delayAddSubViews() + buttonStackView.removeArrangedSubview(completeDelayButton) + buttonStackView.addArrangedSubview(cancelButton) + buttonStackView.addArrangedSubview(completeDeleteButton) + completeDeleteButton.setTitle("삭제", for: .normal) + contentLabel.text = "\(content)를\n 삭제하시겠어요?" + } + .disposed(by: disposeBag) + } + + /// Bind Success Delay + private func bindSuccessDelay(output: ClickWorkLifeBalanceFeedViewModel.Output) { + output.successConnectRelay + .bind { [weak self] _ in + guard let self = self else { return } + dismiss(animated: true) + successConnect.onNext(()) + } + .disposed(by: disposeBag) + } + +} + +import SwiftUI +import RxSwift +struct VCPreViewClickWorkLifeBalanceFeedView: PreviewProvider { + static var previews: some View { + ClickWorkLifeBalanceFeedView().toPreview().previewDevice("iPhone 14 Pro") + // 실행할 ViewController이름 구분해서 잘 지정하기 + } +} +struct VCPreViewClickWorkLifeBalanceFeedView2: PreviewProvider { + static var previews: some View { + ClickWorkLifeBalanceFeedView().toPreview().previewDevice("iPhone 11") + // 실행할 ViewController이름 구분해서 잘 지정하기 + } +} diff --git a/On_off_iOS/On_off_iOS/Home/On/Worklog/View/InsertWorkLogView.swift b/On_off_iOS/On_off_iOS/Home/On/Worklog/View/InsertWorkLogView.swift new file mode 100644 index 0000000..38f2fef --- /dev/null +++ b/On_off_iOS/On_off_iOS/Home/On/Worklog/View/InsertWorkLogView.swift @@ -0,0 +1,205 @@ +// +// InsertWorkLogView.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + + +import Foundation +import SnapKit +import RxSwift +import UIKit + +/// 워라벨 피드 입력 +final class InsertWorkLogView: DimmedViewController { + + /// 배경 뷰 + private lazy var baseUIView: UIView = { + let view = UIView() + view.backgroundColor = .white + return view + }() + + /// TextField 배경 뷰 + private lazy var textFieldCornerUIView: UIView = { + let view = UIView() + view.backgroundColor = .clear + view.layer.borderWidth = 1 + view.layer.borderColor = UIColor.OnOffMain.cgColor + view.layer.cornerRadius = 10 + return view + }() + + /// 입력받는 TextField + private lazy var textField: UITextField = { + let view = UITextField() + view.attributedPlaceholder = NSAttributedString(string: "워라벨 피드를 적어주세요", + attributes: [NSAttributedString.Key.foregroundColor: UIColor.gray]) + view.textColor = .black + view.font = .pretendard(size: 14, weight: .medium) + return view + }() + + /// 글자수 세기 라벨 + private lazy var textCountLabel: UILabel = { + let label = UILabel() + label.text = "(0/30)" + label.backgroundColor = .clear + label.textColor = .lightGray + label.font = .pretendard(size: 12, weight: .light) + return label + }() + + /// 완료 버튼 + private lazy var doneButton: UIButton = { + let button = UIButton() + button.setImage(UIImage(named: "check"), for: .normal) + button.backgroundColor = .clear + return button + }() + + private let disposeBag = DisposeBag() + private let viewModel = InsertWorkLifeBalanceFeedViewModel() + var successAddFeedSubject: PublishSubject = PublishSubject() + var insertFeed: PublishSubject = PublishSubject() + + // MARK: - Init + init() { + super.init(durationTime: 0.3, alpha: 0.7) + addSubviews() + bind() + setupKeyboardEvent() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + dismiss(animated: true) + } + + /// Add Subviews + private func addSubviews() { + view.addSubview(baseUIView) + baseUIView.addSubview(textFieldCornerUIView) + baseUIView.addSubview(doneButton) + + textFieldCornerUIView.addSubview(textField) + baseUIView.addSubview(textCountLabel) + + constraints() + } + + /// Constraints + private func constraints() { + baseUIView.snp.makeConstraints { make in + make.bottom.equalToSuperview() + make.leading.equalToSuperview() + make.trailing.equalToSuperview() + make.height.equalTo(view.safeAreaLayoutGuide.layoutFrame.height/6) + } + + textFieldCornerUIView.snp.makeConstraints { make in + make.height.equalTo(baseUIView.snp.height).multipliedBy(0.25) + make.leading.equalToSuperview().offset(20) + make.trailing.equalTo(doneButton.snp.leading).offset(-10) + make.width.equalToSuperview().multipliedBy(0.8) + make.centerY.equalToSuperview().offset(-10) + } + + doneButton.snp.makeConstraints { make in + make.centerY.equalTo(textFieldCornerUIView.snp.centerY) + make.trailing.equalToSuperview().offset(-20) + } + + textField.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.leading.equalToSuperview().offset(10) + make.width.equalToSuperview().multipliedBy(0.8) + } + + textCountLabel.snp.makeConstraints { make in + make.trailing.equalTo(textFieldCornerUIView.snp.trailing).offset(-10) + make.centerY.equalTo(textFieldCornerUIView.snp.centerY) + } + } + + /// Binding + private func bind() { + let input = InsertWorkLifeBalanceFeedViewModel.Input(textFieldEvents: textField.rx.text.orEmpty, + doneButtonEvents: doneButton.rx.tap, + insertFeed: insertFeed) + + let output = viewModel.createOutput(input: input) + + bindTextRelay(output: output) + bindtextCountRelay(output: output) + bindSuccessAddFeedRelay(output: output) + } + + /// Binding TextField Relay + private func bindTextRelay(output: InsertWorkLifeBalanceFeedViewModel.Output) { + output.textRelay + .bind(to: textField.rx.text) + .disposed(by: disposeBag) + } + + /// Binding Text Count Relay + private func bindtextCountRelay(output: InsertWorkLifeBalanceFeedViewModel.Output) { + output.textCountRelay + .bind { [weak self] text in + guard let self = self else { return } + textCountLabel.text = "(\(text)/30)" + } + .disposed(by: disposeBag) + } + + /// Binding When Success Add Feed + private func bindSuccessAddFeedRelay(output: InsertWorkLifeBalanceFeedViewModel.Output) { + output.successAddFeedRelay + .bind { [weak self] check in + guard let self = self else { return } + dismiss(animated: true) { [weak self] in + guard let self = self else { return } + successAddFeedSubject.onNext(()) + } + } + .disposed(by: disposeBag) + } + + /// 키보드 크기 정보를 받기 위한 Notification 등록 + private func setupKeyboardEvent() { + NotificationCenter.default.addObserver(self, + selector: #selector(keyboardWillShow), + name: UIResponder.keyboardWillShowNotification, + object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(keyboardWillHide), + name: UIResponder.keyboardWillHideNotification, + object: nil) + } + + /// 키보드가 올라올 때 전체적인 뷰를 키보드 크기 만큼 올림 + @objc + private func keyboardWillShow(_ sender: Notification) { + // 현재 동작하고 있는 이벤트에서 키보드의 frame을 받아옴 + guard let keyboardFrame = sender.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return } + let keyboardHeight = keyboardFrame.cgRectValue.height + + if view.frame.origin.y == 0 { + view.frame.origin.y -= keyboardHeight + } + } + + /// 키보드가 내려올 때 전체적인 뷰를 키보드 크기 만큼 올림 + @objc + private func keyboardWillHide(_ sender: Notification) { + if view.frame.origin.y != 0 { + view.frame.origin.y = 0 + } + } + +} diff --git a/On_off_iOS/On_off_iOS/Home/On/Worklog/View/LogOptionButton.swift b/On_off_iOS/On_off_iOS/Home/On/Worklog/View/LogOptionButton.swift new file mode 100644 index 0000000..091f264 --- /dev/null +++ b/On_off_iOS/On_off_iOS/Home/On/Worklog/View/LogOptionButton.swift @@ -0,0 +1,71 @@ +// +// LogOptionButton.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation +import UIKit +import SnapKit + +final class LogOptionButton: UIButton { + + /// 아이콘 이미지 뷰 + private lazy var titleImageView: UIImageView = { + let view = UIImageView() + view.backgroundColor = .clear + return view + }() + + /// 옵션 제목 + private lazy var titleUILabel: UILabel = { + let label = UILabel() + label.textColor = .black + label.font = .pretendard(size: 18, weight: .bold) + label.backgroundColor = .clear + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + addSubviews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Add SubViews + private func addSubviews() { + addSubview(titleImageView) + addSubview(titleUILabel) + + constraints() + } + + /// Constraints + private func constraints() { + titleImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.leading.equalToSuperview().offset(10) + make.width.equalTo(titleImageView.snp.height) + } + + titleUILabel.snp.makeConstraints { make in + make.centerY.equalTo(titleImageView.snp.centerY) + make.leading.equalTo(titleImageView.snp.trailing).offset(10) + make.trailing.equalToSuperview().offset(-10) + + } + } + + /// Input Data + /// - Parameters: + /// - image: 아이콘 이미지 + /// - title: 제목 + func inputData(image: String, title: String) { + titleImageView.image = UIImage(named: image) + titleUILabel.text = title + } +} diff --git a/On_off_iOS/On_off_iOS/Home/On/Worklog/View/OnUIView.swift b/On_off_iOS/On_off_iOS/Home/On/Worklog/View/OnUIView.swift new file mode 100644 index 0000000..a10f824 --- /dev/null +++ b/On_off_iOS/On_off_iOS/Home/On/Worklog/View/OnUIView.swift @@ -0,0 +1,455 @@ +// +// OnUIView.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation +import SnapKit +import RxSwift +import RxCocoa +import UIKit + +/// On 상태일 때 표시되는 UIView +final class OnUIView: UIView { + + /// ScrollView + private lazy var scrollView: UIScrollView = { + let view = UIScrollView() + view.backgroundColor = .clear + view.layer.maskedCorners = CACornerMask(arrayLiteral: .layerMinXMinYCorner, .layerMaxXMinYCorner) + view.layer.cornerRadius = 25 + return view + }() + + /// ContentView + private lazy var contentView: UIView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + + /// 오늘의 회고 제목 버튼 + private lazy var todayMemoirsButton: UIButton = { + let button = UIButton() + button.setTitle("오늘의 회고", for: .normal) + button.backgroundColor = .clear + button.setTitleColor(.OnOffMain, for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 18, weight: .bold) + return button + }() + + /// 제목 옆 벡터 아이콘 배경 + private lazy var todayMemoirsLabelBackgroundUIView: UIView = { + let view = UIView() + view.backgroundColor = .OnOffPurple + view.alpha = 0.4 + return view + }() + + /// 오늘의 회고 제목 옆 벡터 아이콘 > + private lazy var todayMemoirsIconImageButton: UIButton = { + let button = UIButton() + button.setImage(UIImage(systemName: "greaterthan"), for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 18, weight: .bold) + button.tintColor = .black + button.backgroundColor = .clear + return button + }() + + /// 오늘의 회고 작성 했는지 안했는지 확인하는 UIView + private lazy var todayMemoirsUIView: UIView = { + let view = UIView() + view.backgroundColor = .OnOffLightMain + view.layer.cornerRadius = 20 + return view + }() + + /// 워라벨 피드 제목 + private lazy var feedTitleButton: UIButton = { + let button = UIButton() + button.setTitle("워라벨 피드", for: .normal) + button.backgroundColor = .clear + button.setTitleColor(.OnOffMain, for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 18, weight: .bold) + return button + }() + + /// 워라벨 피드 제목 옆 벡터 아이콘 배경 + private lazy var feedlabelBackgroundUIView: UIView = { + let view = UIView() + view.backgroundColor = .OnOffPurple + view.alpha = 0.4 + return view + }() + + /// 워라벨 피드 제목 옆 벡터 아이콘 + + private lazy var feedPlusIconImageButton: UIButton = { + let button = UIButton() + button.setImage(UIImage(systemName: "plus"), for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 18, weight: .bold) + button.tintColor = .black + button.backgroundColor = .clear + return button + }() + + /// 워라벨 피드 확인하는 UIView + private lazy var feedUITableView: UITableView = { + let view = UITableView() + view.backgroundColor = .OnOffLightMain + view.layer.cornerRadius = 20 + view.register(WorkLifeBalanceTableViewCell.self, + forCellReuseIdentifier: CellIdentifier.WorkLifeBalanceTableViewCell.rawValue) + return view + }() + + /// 날짜 라벨 + private lazy var dateLabel: UILabel = { + let label = UILabel() + label.text = "2023 - November" + label.backgroundColor = .clear + label.textColor = .OnOffMain + label.font = .systemFont(ofSize: 18, weight: .bold) + return label + }() + + /// 이미지 CollectionView + private lazy var imageCollectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + let view = UICollectionView(frame: .zero, collectionViewLayout: layout) + view.register(ImageCollectionViewCell.self, + forCellWithReuseIdentifier: CellIdentifier.ImageCollectionView.rawValue) + view.backgroundColor = .clear + view.isScrollEnabled = false + return view + }() + + private let disposeBag = DisposeBag() + private let viewModel = OnUIViewModel() + + /// 이미지 추가 버튼 누른 경우 + var clickedImagePlusButton: PublishSubject = PublishSubject() + + /// 앨범에서 선택한 이미지 전송 + var selectedImage: PublishSubject = PublishSubject() + + /// 이미지 선택한경우 + var clickedImageButton: PublishSubject = PublishSubject() + + var clickedAddfeedButton: PublishSubject = PublishSubject() + + /// 선택한 날짜 + var selectedDate: PublishSubject = PublishSubject() + private var successDeleteImage: PublishSubject = PublishSubject() + var successAddFeed: PublishSubject = PublishSubject() + var selectedFeedTableViewCell: PublishSubject = PublishSubject() + private var loadWLBFeed: PublishSubject = PublishSubject() + private var clickCheckMarkOfWLBFeed: PublishSubject = PublishSubject() + + // MARK: - Init + override init(frame: CGRect) { + super.init(frame: frame) + bind() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + override func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + print(#function) + successDeleteImage.onNext(()) + loadWLBFeed.onNext(()) + } + + /// Add View + private func addSubViews(output: OffUIViewModel.Output) { + addSubview(scrollView) + scrollView.addSubview(contentView) + contentView.addSubview(todayMemoirsButton) + contentView.addSubview(todayMemoirsLabelBackgroundUIView) + todayMemoirsLabelBackgroundUIView.addSubview(todayMemoirsIconImageButton) + contentView.addSubview(todayMemoirsUIView) + contentView.addSubview(feedTitleButton) + contentView.addSubview(feedlabelBackgroundUIView) + feedlabelBackgroundUIView.addSubview(feedPlusIconImageButton) + contentView.addSubview(feedUITableView) + contentView.addSubview(dateLabel) + contentView.addSubview(imageCollectionView) + + constraints(output: output) + } + + /// Constraints + private func constraints(output: OffUIViewModel.Output) { + scrollView.snp.makeConstraints { make in + make.top.equalToSuperview() + make.horizontalEdges.equalToSuperview() + make.bottom.equalToSuperview() + } + + contentView.snp.makeConstraints { make in + make.top.equalTo(scrollView.snp.top) + make.leading.equalTo(scrollView.snp.leading) + make.trailing.equalTo(scrollView.snp.trailing) + make.bottom.equalTo(scrollView.snp.bottom) + make.width.equalTo(scrollView.snp.width) + } + + todayMemoirsButton.snp.makeConstraints { make in + make.top.equalToSuperview().offset(20) + make.leading.equalToSuperview().offset(10) + } + + todayMemoirsLabelBackgroundUIView.snp.makeConstraints { make in + make.height.equalTo(todayMemoirsButton.snp.height).multipliedBy(0.5) + make.leading.equalTo(todayMemoirsButton.snp.trailing).offset(10) + make.centerY.equalTo(todayMemoirsButton.snp.centerY) + make.width.equalTo(todayMemoirsLabelBackgroundUIView.snp.height) + } + todayMemoirsLabelBackgroundUIView.layoutIfNeeded() + todayMemoirsLabelBackgroundUIView.layer.cornerRadius = todayMemoirsLabelBackgroundUIView.frame.height * 0.65 + + todayMemoirsIconImageButton.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.center.equalToSuperview() + } + + feedTitleButton.snp.makeConstraints { make in + make.top.equalTo(todayMemoirsUIView.snp.bottom).offset(20) + make.leading.equalTo(todayMemoirsButton.snp.leading) + } + + feedlabelBackgroundUIView.snp.makeConstraints { make in + make.height.equalTo(feedTitleButton.snp.height).multipliedBy(0.5) + make.leading.equalTo(feedTitleButton.snp.trailing).offset(10) + make.centerY.equalTo(feedTitleButton.snp.centerY) + make.width.equalTo(feedlabelBackgroundUIView.snp.height) + } + feedlabelBackgroundUIView.layoutIfNeeded() + feedlabelBackgroundUIView.layer.cornerRadius = feedlabelBackgroundUIView.frame.height * 0.65 + + feedPlusIconImageButton.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.center.equalToSuperview() + } + + todayMemoirsUIView.snp.makeConstraints { make in + make.top.equalTo(todayMemoirsButton.snp.bottom).offset(10) + make.leading.equalTo(todayMemoirsButton.snp.leading) + make.trailing.equalToSuperview().offset(-20) + make.height.equalTo(200) + } + + feedUITableView.snp.makeConstraints { make in + make.top.equalTo(feedTitleButton.snp.bottom).offset(10) + make.leading.equalTo(feedTitleButton.snp.leading) + make.trailing.equalToSuperview().offset(-20) + output.tableViewHeightConstraint.accept(make.height.equalTo(200).constraint) + } + + dateLabel.snp.makeConstraints { make in + make.top.equalTo(feedUITableView.snp.bottom).offset(20) + make.leading.equalToSuperview().offset(10) + } + + imageCollectionView.snp.makeConstraints { make in + make.top.equalTo(dateLabel.snp.bottom).offset(10) + make.horizontalEdges.equalToSuperview().inset(10) + make.bottom.equalToSuperview() + output.collectionViewHeightConstraint.accept(make.height.equalTo(0).constraint) + } + } + + /// Binding + private func bind() { + let output = viewModel.createOutput(input: OnUIViewModel.Input(todayMemoirsButtonEvents: todayMemoirsButton.rx.tap, + todayMemoirsIconImageButtonEvents: todayMemoirsIconImageButton.rx.tap, + collectionViewCellEvents: imageCollectionView.rx.itemSelected, + selectedImage: selectedImage, + successDeleteImage: successDeleteImage, + loadWLBFeed: loadWLBFeed, + clickCheckMarkOfWLBFeed: clickCheckMarkOfWLBFeed, + selectedDate: selectedDate, + successAddFeed: successAddFeed)) + addSubViews(output: output) + bindCollectionView(output: output) + bindTableView(output: output) + bindTableViewHeight(output: output) + bindClickTableViewCell(output: output) + bindClickPlusImageButton(output: output) + bindClickImageButton(output: output) + bindSelectedMonth(output: output) + bindFeedEvents() + } + + /// 워라벨 피드 제목 버튼 및 이미지 버튼 + private func bindFeedEvents() { + feedTitleButton.rx.tap + .bind { [weak self] in + guard let self = self else { return } + clickedAddfeedButton.onNext(()) + } + .disposed(by: disposeBag) + + feedPlusIconImageButton.rx.tap + .bind { [weak self] in + guard let self = self else { return } + clickedAddfeedButton.onNext(()) + } + .disposed(by: disposeBag) + } + + /// Binding Work Life Balance Table View + private func bindTableViewHeight(output: OffUIViewModel.Output) { + output.workLifeBalanceRelay + .bind { list in + if list.count > 4 { + output.tableViewHeightConstraint.value?.update(offset: 200 + (50 * (list.count - 4))) + return + } + output.tableViewHeightConstraint.value?.update(offset: 200) + } + .disposed(by: disposeBag) + } + + /// Binding Work Life Balance Table View + private func bindTableView(output: OffUIViewModel.Output) { + output.workLifeBalanceRelay + .bind(to: feedUITableView.rx.items(cellIdentifier: CellIdentifier.WorkLifeBalanceTableViewCell.rawValue, + cellType: WorkLifeBalanceTableViewCell.self)) + { [weak self] row, element, cell in + guard let self = self else { return } + cell.inputData(feed: element) + cell.backgroundColor = .clear + cell.selectionStyle = .none + cell.checkMarkButtonEvents + .bind { [weak self] in + guard let self = self else { return } + clickCheckMarkOfWLBFeed.onNext(element) + } + .disposed(by: cell.disposeBag) + } + .disposed(by: disposeBag) + + feedUITableView.rx.setDelegate(self) + .disposed(by: disposeBag) + } + + /// Binding Click Table View Cell + private func bindClickTableViewCell(output: OffUIViewModel.Output) { + feedUITableView.rx.itemSelected + .bind { [weak self] indexPath in + guard let self = self else { return } + let feed = output.workLifeBalanceRelay.value[indexPath.row] + selectedFeedTableViewCell.onNext(feed) + } + .disposed(by: disposeBag) + } + + /// Binding CollectionView + private func bindCollectionView(output: OffUIViewModel.Output) { + output.imageURLRelay + .bind { list in + var count = list.count - 1 + count = list.count < 9 ? count : 8 + let width: CGFloat = (self.frame.width - 20 - 10 * 2) / 3 + output.collectionViewHeightConstraint.value?.update(offset: CGFloat(count / 3 + 1) * width + CGFloat((count / 3 + 1) * 10)) + } + .disposed(by: disposeBag) + + output.imageURLRelay + .bind(to: imageCollectionView.rx + .items(cellIdentifier: CellIdentifier.ImageCollectionView.rawValue, + cellType: ImageCollectionViewCell.self)) + { row, element, cell in + cell.layer.cornerRadius = 20 + + if element.imageUrl == "plus.circle.fill" { + cell.layer.borderWidth = 1 + cell.layer.borderColor = UIColor.OnOffMain.cgColor + cell.lastData(image: element) + cell.backgroundColor = .clear + return + } + cell.inputData(image: element) + cell.backgroundColor = .clear + cell.layer.borderWidth = 0 + } + .disposed(by: disposeBag) + + imageCollectionView.rx.setDelegate(self) + .disposed(by: disposeBag) + } + + /// 이미지 추가 버튼 눌렀을 때 + /// 이미지 선택하는 화면으로 이동 + private func bindClickPlusImageButton(output: OffUIViewModel.Output) { + output.clickPlusImageButton + .bind { [weak self] in + guard let self = self else { return } + clickedImagePlusButton.onNext(()) + } + .disposed(by: disposeBag) + + } + + /// 이미지 선택했을 때 + /// 이미지 크게 보는 화면으로 이동 + private func bindClickImageButton(output: OffUIViewModel.Output) { + output.selectedImageRelay + .bind { [weak self] imageURL in + guard let self = self else { return } + clickedImageButton.onNext(imageURL) + } + .disposed(by: disposeBag) + } + + /// Bind Selected Month + private func bindSelectedMonth(output: OffUIViewModel.Output) { + output.selectedDate + .bind { [weak self] date in + guard let self = self else { return } + dateLabel.text = date + } + .disposed(by: disposeBag) + } +} + +extension OffUIView: UITableViewDelegate { + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 50 + } +} + +extension OffUIView: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath) -> CGSize { + let interval: CGFloat = 10 + let width: CGFloat = (collectionView.frame.width - interval * 2) / 3 + return CGSize(width: width, height: width) + } + + func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + minimumLineSpacingForSectionAt section: Int) -> CGFloat { 10 } +} + +import SwiftUI +struct VCPreViewHomeViewController:PreviewProvider { + static var previews: some View { + HomeViewController().toPreview().previewDevice("iPhone 15 Pro") + // 실행할 ViewController이름 구분해서 잘 지정하기 + } +} + +struct VCPreViewHomeViewController2:PreviewProvider { + static var previews: some View { + HomeViewController().toPreview().previewDevice("iPhone SE (3rd generation)") + // 실행할 ViewController이름 구분해서 잘 지정하기 + } +} diff --git a/On_off_iOS/On_off_iOS/Home/On/Worklog/View/WorkLogTableViewCell.swift b/On_off_iOS/On_off_iOS/Home/On/Worklog/View/WorkLogTableViewCell.swift new file mode 100644 index 0000000..23e38b6 --- /dev/null +++ b/On_off_iOS/On_off_iOS/Home/On/Worklog/View/WorkLogTableViewCell.swift @@ -0,0 +1,84 @@ +// +// WorkLogTableViewCell.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + + +import Foundation +import UIKit +import SnapKit +import RxSwift + +final class WorkLogTableViewCell: UITableViewCell { + /// 제목 라벨 + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.textColor = .black + label.backgroundColor = .clear + label.textAlignment = .left + label.font = .pretendard(size: 17, weight: .medium) + return label + }() + + /// 체크마크 버튼 + private lazy var checkMarkButton: UIButton = { + let button = UIButton() + button.backgroundColor = .clear + button.setImage(UIImage(named: "nonCheckMark"), for: .normal) + return button + }() + + lazy var disposeBag = DisposeBag() + var checkMarkButtonEvents: Observable { + return checkMarkButton.rx.tap.asObservable() + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + addSubViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func prepareForReuse() { + super.prepareForReuse() + disposeBag = DisposeBag() + } + + /// Add SubViews + private func addSubViews() { + contentView.addSubview(titleLabel) + contentView.addSubview(checkMarkButton) + + constraints() + } + + /// Constraints + private func constraints() { + checkMarkButton.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.leading.equalToSuperview().offset(10) + make.width.equalTo(checkMarkButton.snp.height) + } + + titleLabel.snp.makeConstraints { make in + make.centerY.equalTo(checkMarkButton.snp.centerY) + make.leading.equalTo(checkMarkButton.snp.trailing).offset(10) + make.trailing.equalToSuperview().offset(-10) + } + } + + /// Input Data + /// - Parameter feed: Feed + func inputData(feed: Feed) { + titleLabel.text = feed.content ?? "" + let image: String = feed.isChecked ?? false ? "checkMark" : "nonCheckMark" + checkMarkButton.setImage(UIImage(named: image), for: .normal) + } + + +} diff --git a/On_off_iOS/On_off_iOS/Home/On/Worklog/ViewModel/ClickWorklogViewModel.swift b/On_off_iOS/On_off_iOS/Home/On/Worklog/ViewModel/ClickWorklogViewModel.swift new file mode 100644 index 0000000..80080e2 --- /dev/null +++ b/On_off_iOS/On_off_iOS/Home/On/Worklog/ViewModel/ClickWorklogViewModel.swift @@ -0,0 +1,103 @@ +// +// ClickWorklogViewModel.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation +import RxSwift +import RxCocoa + +final class ClickWorklogViewModel { + private let disposeBag = DisposeBag() + private let service = ClickWorklogService() + + struct Input { + let completeDelayButtonEvents: ControlEvent? + let deleteButtonEvents: ControlEvent? + let selectedWorklog: Observable? + } + + struct Output { + var selectedWorkRelay: BehaviorRelay = BehaviorRelay(value: nil) + var successConnectRelay: PublishRelay = PublishRelay() + var nextDay: BehaviorRelay = BehaviorRelay(value: "") + } + + + /// Create Output + func createOutput(input: Input) -> Output { + let output = Output() + + bindSelectedWorkRelay(input: input, output: output) + bindCompleteDelayButtonEvents(input: input, output: output) + bindDeleteButtonEvents(input: input, output: output) + return output + } + + /// Bind Selected Work Relay + private func bindSelectedWorkRelay(input: Input, output: Output) { + input.selectedWorklog? + .bind { [weak self] Worklog in + guard let self = self else { return } + output.selectedWorkRelay.accept(Worklog) + output.nextDay.accept(calculateNextDay(date: Worklog.date ?? "")) + } + .disposed(by: disposeBag) + } + + /// Bind Complete Delay Button Events + private func bindCompleteDelayButtonEvents(input: Input, output: Output) { + input.completeDelayButtonEvents? + .bind { [weak self] in + guard let self = self else { return } + delayTomorrow(output: output) + } + .disposed(by: disposeBag) + } + + /// Bind Delete Button Events + private func bindDeleteButtonEvents(input: Input, output: Output) { + input.deleteButtonEvents? + .bind { [weak self] in + guard let self = self, let id = output.selectedWorkRelay.value?.worklogId else { return } + service.delete(feedId: worklogId) + .subscribe(onNext: { check in + if check { + output.successConnectRelay.accept(check) + } + }, onError: { error in + print(#function, error) + }) + .disposed(by: disposeBag) + } + .disposed(by: disposeBag) + } + + /// 내일로 미루기 + private func delayTomorrow(output: Output) { + guard let id = output.selectedWorkRelay.value?.worklogId else { return } + service.delayTomorrow(feedId: id) + .subscribe(onNext: { check in + if check { + output.successConnectRelay.accept(check) + } + }, onError: { error in + print(#function, error) + }) + .disposed(by: disposeBag) + } + + /// Format Date To String + /// - Parameter date: Date + /// - Returns: String Type Date + private func calculateNextDay(date: String) -> String { + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "ko_KR") + dateFormatter.dateFormat = "MM.dd" + return dateFormatter.string(from: Calendar.current.date(byAdding: .day, + value: 1, + to: dateFormatter.date(from: date) ?? Date()) ?? Date()) + } +} diff --git a/On_off_iOS/On_off_iOS/Home/On/Worklog/ViewModel/InsertWorkLogViewModel.swift b/On_off_iOS/On_off_iOS/Home/On/Worklog/ViewModel/InsertWorkLogViewModel.swift new file mode 100644 index 0000000..ee17d36 --- /dev/null +++ b/On_off_iOS/On_off_iOS/Home/On/Worklog/ViewModel/InsertWorkLogViewModel.swift @@ -0,0 +1,125 @@ +// +// InsertWorkLogViewModel.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation +import RxSwift +import RxRelay +import RxCocoa + +final class InsertWorkLogViewModel { + private let disposeBag = DisposeBag() + private let service = InsertWorkLogService() + final private let maxLength = 30 + + struct Input { + let textFieldEvents: ControlProperty? + let doneButtonEvents: ControlEvent? + let insertFeed: Observable + } + + struct Output { + var textRelay: BehaviorRelay = BehaviorRelay(value: "") + var textCountRelay: BehaviorRelay = BehaviorRelay(value: 0) + var successAddWorklogRelay: PublishRelay = PublishRelay() + var insertRelay: BehaviorRelay = BehaviorRelay(value: nil) + } + + /// Create Output + func createOutput(input: Input) -> Output { + let output = Output() + + bindTextFieldEvents(input: input, output: output) + bindDoneButtonEvents(input: input, output: output) + bindInsertWorklog(input: input, output: output) + + return output + } + + /// Bind Insert log + private func bindInsertWorklog(input: Input, output: Output) { + input.insertWorklog + .bind { [weak self] Worklog in + guard let self = self else { return } + checkTextLimitCount(text: Worklog.content ?? "", output: output) + output.insertRelay.accept(Worklog) + } + .disposed(by: disposeBag) + } + + /// Binding Textfield Events + private func bindTextFieldEvents(input: Input, output: Output) { + input.textFieldEvents? + .bind { [weak self] text in + guard let self = self else { return } + checkTextLimitCount(text: text, output: output) + } + .disposed(by: disposeBag) + } + + /// Binding Done Button Events + private func bindDoneButtonEvents(input: Input, output: Output) { + input.doneButtonEvents? + .bind { [weak self] in + guard let self = self else { return } + if let Worklog = output.insertRelay.value, let id = Worklog.worklogId { + insertWorklog(worklogId: id, output: output) + return + } + AddWorklog(output: output) + } + .disposed(by: disposeBag) + } + + /// 글자 수 최대 판별 + private func checkTextLimitCount(text: String, output: Output) { + if text.count <= maxLength { + output.textRelay.accept(text) + output.textCountRelay.accept(text.count) + return + } + output.textRelay.accept(String(text.dropLast(text.count-maxLength))) + output.textCountRelay.accept(maxLength) + } + + /// Format Date To String + /// - Parameter date: Date + /// - Returns: String Type Date + private func formatDate(date: Date) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + return dateFormatter.string(from: date) + } + + /// Add Feed To Backend Server + private func addWorklog(output: Output) { + let Worklog: AddWorklog = AddWorklog(date: formatDate(date: Date()), + content: output.textRelay.value) + print(Worklog) + service.AddWorklog(Worklog: Worklog) + .subscribe(onNext: { check in + if check { + output.successAddWorklogRelay.accept(check) + } + }, onError: { error in + print(#function, error) + }) + .disposed(by: disposeBag) + } + + /// Insert Worklog + private func insertWorklog(worklogId: Int, output: Output) { + service.insertlog(worklogid: worklogId,content: output.textRelay.value) + .subscribe(onNext: { check in + if check { + output.successAddWorklogRelay.accept(check) + } + }, onError: { error in + print(#function, error) + }) + .disposed(by: disposeBag) + } +} diff --git a/On_off_iOS/On_off_iOS/Home/On/Worklog/ViewModel/OnUIViewModel.swift b/On_off_iOS/On_off_iOS/Home/On/Worklog/ViewModel/OnUIViewModel.swift new file mode 100644 index 0000000..db6f2fa --- /dev/null +++ b/On_off_iOS/On_off_iOS/Home/On/Worklog/ViewModel/OnUIViewModel.swift @@ -0,0 +1,128 @@ +// +// OnUIViewModel.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation +import RxSwift +import RxCocoa +import RxRelay +import UIKit +import SnapKit + +final class OnUIViewModel { + private let disposeBag = DisposeBag() + private let service = OnUIViewService() + + struct Input { + let collectionViewCellEvents: ControlEvent? + let loadWLFeed: Observable? + let clickCheckMarkOfWLFeed: Observable? + let selectedDate: Observable? + let successAddWorklog: Observable? + } + + struct Output { + var collectionViewHeightConstraint: BehaviorRelay = BehaviorRelay(value: nil) + var tableViewHeightConstraint: BehaviorRelay = BehaviorRelay(value: nil) + var workLogRelay: BehaviorRelay<[Worklog]> = BehaviorRelay(value: []) + var successCheckWLRelay: PublishRelay = PublishRelay() + var selectedDate: BehaviorRelay = BehaviorRelay(value: "") + } + + /// Create Output + /// - Parameter input: Input + /// - Returns: Output + func createOutput(input: Input) -> Output { + let output = Output() + + bindSelectedWLFeed(input: input, output: output) + bindLoadWLFeed(input: input, output: output) + bindSelectedDate(input: input, output: output) + bindSuccessAddWorklog(input: input, output: output) + + + return output + } + + /// Binding Load W.L.B Feed + private func bindLoadWLFeed(input: Input, output: Output) { + input.loadWLFeed? + .bind { [weak self] in + guard let self = self else { return } + getWorkLogList(output: output) + } + .disposed(by: disposeBag) + } + + /// Binding Selected Date + private func bindSelectedDate(input: Input, output: Output) { + input.selectedDate? + .bind(to: output.selectedDate) + .disposed(by: disposeBag) + } + + /// Binding Success Add Feed + private func bindSuccessAddWorklog(input: Input, output: Output) { + input.successAddWorklog? + .bind { [weak self] in + guard let self = self else { return } + getWorkLogList(output: output) + } + .disposed(by: disposeBag) + } + + /// Binding Selected W.L.B Feed + private func bindSelectedWLFeed(input: Input, output: Output) { + input.clickCheckMarkOfWLFeed? + .bind { [weak self] feed in + guard let self = self, let id = feed.worklogId else { return } + checkWLFeed(feedId: id, input: input, output: output) + } + .disposed(by: disposeBag) + } + + /// Format Date To String + /// - Parameter date: Date + /// - Returns: String Type Date + private func formatDate(date: Date) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + return dateFormatter.string(from: date) + } + + /// Get WorkLog List + /// - Parameters: + /// - selectedDate: Selected Date + private func getWorkLogList(output: Output) { + service.getWLList(date: output.selectedDate.value) + .subscribe(onNext: { list in + output.workLogRelay.accept(list) + }, onError: { error in + // 업로드 실패 + print(#function, error) + }) + .disposed(by: disposeBag) + + } + + /// 업무일지 체크 유무 + /// - Parameters: + /// - worklogId: Feed Id + /// - output: Output + private func checkWL(worklogId: Int, input: Input, output: Output) { + service.checkWL(worklogId: worklogId) + .subscribe(onNext: { [weak self] check in + guard let self = self else { return } + if check { + output.successCheckWLRelay.accept(check) + getWorkLogList(output: output) + } + }, onError: { error in + print(#function, error) + }) + .disposed(by: disposeBag) + } +} diff --git a/On_off_iOS/On_off_iOS/Home/TodayResolution/Model/TodayResolution.swift b/On_off_iOS/On_off_iOS/Home/TodayResolution/Model/TodayResolution.swift deleted file mode 100644 index 72ac278..0000000 --- a/On_off_iOS/On_off_iOS/Home/TodayResolution/Model/TodayResolution.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// TodayResolution.swift -// On_off_iOS -// -// Created by 신예진 on 2/2/24. -// - -import Foundation - -struct TodayResolutionInfo { - - let info: String? //이런 형식으로 쓰기 - -} diff --git a/On_off_iOS/On_off_iOS/KeyChain/KeyChainWrapper.swift b/On_off_iOS/On_off_iOS/KeyChain/KeyChainWrapper.swift new file mode 100644 index 0000000..50568ac --- /dev/null +++ b/On_off_iOS/On_off_iOS/KeyChain/KeyChainWrapper.swift @@ -0,0 +1,62 @@ +// +// KeyChainWrapper.swift +// On_off_iOS +// +// Created by 신예진 on 2/13/24. +// + +import Foundation + +/// 키체인 +final class KeychainWrapper { + + /// 문자열 값을 Keychain에 저장하는 함수 + static func saveItem(value: String, forKey key: String) -> Bool { + + /// 문자열을 Data로 변환 + if let data = value.data(using: .utf8) { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, // Keychain 클래스 (일반적으로 비밀번호) + kSecAttrAccount as String: key, // 키 + kSecValueData as String: data, // 데이터 + kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked + ] + + SecItemDelete(query as CFDictionary) // 이미 키에 저장된 값이 있다면 삭제 + let status = SecItemAdd(query as CFDictionary, nil) // Keychain에 새로운 값 저장 + return status == errSecSuccess// 저장 성공 여부를 리턴 + } + return false + } + + /// Keychain에서 문자열 값을 로드하는 함수 + static func loadItem(forKey key: String) -> String? { + guard let kCFBooleanTrue = kCFBooleanTrue else {return nil} + + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecReturnData as String: kCFBooleanTrue, // 데이터 반환 설정 + kSecMatchLimit as String: kSecMatchLimitOne // 로드할 값이 하나라는 것을 설정 + ] + var result: AnyObject? + + // Keychain에서 값 로드 + let status = SecItemCopyMatching(query as CFDictionary, &result) + + // 값이 로드되고 변환이 성공 시 + if status == errSecSuccess, let data = result as? Data, let value = String(data: data, encoding: .utf8) { + return value + } + return nil + } + + static func delete(key: String) -> Bool { + let deleteQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword, + kSecAttrAccount: key] + let status = SecItemDelete(deleteQuery as CFDictionary) + if status == errSecSuccess { return true } + + return false + } +}