diff --git a/File.swift b/File.swift deleted file mode 100644 index a624f34a..00000000 --- a/File.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// File.swift -// KkuMulKum -// -// Created by 이지훈 on 7/9/24. -// - -import Foundation diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index 0591003c..19d358db 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -7,6 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 782B406F2C3DBF93008B0CA7 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782B406E2C3DBF93008B0CA7 /* ProfileViewController.swift */; }; + 782B40722C3DBFA3008B0CA7 /* ProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782B40712C3DBFA3008B0CA7 /* ProfileViewModel.swift */; }; + 782B40752C3DBFBA008B0CA7 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782B40742C3DBFBA008B0CA7 /* ProfileView.swift */; }; + 782B407B2C3E395A008B0CA7 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782B407A2C3E395A008B0CA7 /* WelcomeView.swift */; }; + 782B407D2C3E3984008B0CA7 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782B407C2C3E3984008B0CA7 /* WelcomeViewController.swift */; }; + 782B407F2C3E44B7008B0CA7 /* WelcomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782B407E2C3E44B7008B0CA7 /* WelcomeViewModel.swift */; }; + 782B40822C3E4925008B0CA7 /* NicknameViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782B40812C3E4925008B0CA7 /* NicknameViewModel.swift */; }; 784E4D942C3B1C7F00BC943C /* KakaoSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 784E4D932C3B1C7F00BC943C /* KakaoSDK */; }; 784E4D962C3B1C7F00BC943C /* KakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 784E4D952C3B1C7F00BC943C /* KakaoSDKAuth */; }; 784E4D992C3B95A900BC943C /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 784E4D982C3B95A900BC943C /* KeychainAccess */; }; @@ -48,6 +55,8 @@ 789873342C3D1A7B00435E96 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789873312C3D1A7B00435E96 /* LoginView.swift */; }; 789AD4B32C3C0093002E2688 /* SocialLoginResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789AD4B22C3C0093002E2688 /* SocialLoginResponseModel.swift */; }; 789AD4B52C3C0147002E2688 /* ResissueResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */; }; + 78AED1342C3D951F000AD80A /* NicknameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1332C3D951F000AD80A /* NicknameViewController.swift */; }; + 78AED1372C3D98D1000AD80A /* NicknameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1362C3D98D1000AD80A /* NicknameView.swift */; }; 78B9286C2C29402C006D9942 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B9286B2C29402C006D9942 /* AppDelegate.swift */; }; 78B9286E2C29402C006D9942 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B9286D2C29402C006D9942 /* SceneDelegate.swift */; }; 78B928752C29402E006D9942 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 78B928742C29402E006D9942 /* Assets.xcassets */; }; @@ -75,7 +84,6 @@ DDA2EE732C385EB9007C6059 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA2EE722C385EB9007C6059 /* MainTabBarController.swift */; }; DDA2EE752C385FB1007C6059 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA2EE742C385FB1007C6059 /* HomeViewController.swift */; }; DDA2EE772C385FC3007C6059 /* GroupListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA2EE762C385FC3007C6059 /* GroupListViewController.swift */; }; - DDA2EE792C385FCF007C6059 /* MyPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA2EE782C385FCF007C6059 /* MyPageViewController.swift */; }; DDAF1C7C2C3D5B86008A37D3 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = DDAF1C7B2C3D5B86008A37D3 /* RxCocoa */; }; DDAF1C7E2C3D5B86008A37D3 /* RxRelay in Frameworks */ = {isa = PBXBuildFile; productRef = DDAF1C7D2C3D5B86008A37D3 /* RxRelay */; }; DDAF1C812C3D5BD5008A37D3 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DDAF1C802C3D5BD5008A37D3 /* Kingfisher */; }; @@ -86,6 +94,11 @@ DDAF1C912C3D6E3D008A37D3 /* TardyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF1C8B2C3D6E3D008A37D3 /* TardyViewController.swift */; }; DDAF1C922C3D6E3D008A37D3 /* PromiseInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF1C8C2C3D6E3D008A37D3 /* PromiseInfoViewController.swift */; }; DDAF1C932C3D6E3D008A37D3 /* PromiseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF1C8D2C3D6E3D008A37D3 /* PromiseViewController.swift */; }; + DE159D322C406E1600425101 /* MyPageAlarmSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE159D2A2C406E1600425101 /* MyPageAlarmSettingView.swift */; }; + DE159D332C406E1600425101 /* MyPageContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE159D2B2C406E1600425101 /* MyPageContentView.swift */; }; + DE159D342C406E1600425101 /* MyPageEtcSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE159D2C2C406E1600425101 /* MyPageEtcSettingView.swift */; }; + DE159D352C406E1600425101 /* MyPageNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE159D2D2C406E1600425101 /* MyPageNavigationView.swift */; }; + DE159D362C406E1600425101 /* MyPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE159D2F2C406E1600425101 /* MyPageViewController.swift */; }; DE254AA52C31131600A4015E /* Color.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DE254AA42C31131600A4015E /* Color.xcassets */; }; DE254AA82C3118EA00A4015E /* UIView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE254AA72C3118EA00A4015E /* UIView+.swift */; }; DE254AAA2C31190E00A4015E /* UIStackView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE254AA92C31190E00A4015E /* UIStackView+.swift */; }; @@ -136,12 +149,22 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 782B406E2C3DBF93008B0CA7 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; + 782B40712C3DBFA3008B0CA7 /* ProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModel.swift; sourceTree = ""; }; + 782B40742C3DBFBA008B0CA7 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; + 782B407A2C3E395A008B0CA7 /* WelcomeView.swift */ = {isa = PBXFileReference; indentWidth = 5; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; + 782B407C2C3E3984008B0CA7 /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; + 782B407E2C3E44B7008B0CA7 /* WelcomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewModel.swift; sourceTree = ""; }; + 782B40812C3E4925008B0CA7 /* NicknameViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameViewModel.swift; sourceTree = ""; }; 785AE1D02C3B07A600677CA0 /* PrivacyInfo.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = PrivacyInfo.plist; sourceTree = ""; }; 7898732F2C3D1A7B00435E96 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 789873302C3D1A7B00435E96 /* LoginViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; 789873312C3D1A7B00435E96 /* LoginView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; 789AD4B22C3C0093002E2688 /* SocialLoginResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginResponseModel.swift; sourceTree = ""; }; 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResissueResponseModel.swift; sourceTree = ""; }; + 78AED1312C3D94F2000AD80A /* KkuMulKum.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KkuMulKum.entitlements; sourceTree = ""; }; + 78AED1332C3D951F000AD80A /* NicknameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameViewController.swift; sourceTree = ""; }; + 78AED1362C3D98D1000AD80A /* NicknameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameView.swift; sourceTree = ""; }; 78B928682C29402C006D9942 /* KkuMulKum.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KkuMulKum.app; sourceTree = BUILT_PRODUCTS_DIR; }; 78B9286B2C29402C006D9942 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 78B9286D2C29402C006D9942 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -171,7 +194,6 @@ DDA2EE722C385EB9007C6059 /* MainTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = ""; }; DDA2EE742C385FB1007C6059 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; DDA2EE762C385FC3007C6059 /* GroupListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupListViewController.swift; sourceTree = ""; }; - DDA2EE782C385FCF007C6059 /* MyPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageViewController.swift; sourceTree = ""; }; DDAF1C832C3D5D19008A37D3 /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; }; DDAF1C882C3D6E3D008A37D3 /* ReadyStatusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadyStatusViewController.swift; sourceTree = ""; }; DDAF1C892C3D6E3D008A37D3 /* PromiseSegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromiseSegmentedControl.swift; sourceTree = ""; }; @@ -179,6 +201,11 @@ DDAF1C8B2C3D6E3D008A37D3 /* TardyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TardyViewController.swift; sourceTree = ""; }; DDAF1C8C2C3D6E3D008A37D3 /* PromiseInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromiseInfoViewController.swift; sourceTree = ""; }; DDAF1C8D2C3D6E3D008A37D3 /* PromiseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromiseViewController.swift; sourceTree = ""; }; + DE159D2A2C406E1600425101 /* MyPageAlarmSettingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyPageAlarmSettingView.swift; sourceTree = ""; }; + DE159D2B2C406E1600425101 /* MyPageContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyPageContentView.swift; sourceTree = ""; }; + DE159D2C2C406E1600425101 /* MyPageEtcSettingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyPageEtcSettingView.swift; sourceTree = ""; }; + DE159D2D2C406E1600425101 /* MyPageNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyPageNavigationView.swift; sourceTree = ""; }; + DE159D2F2C406E1600425101 /* MyPageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyPageViewController.swift; sourceTree = ""; }; DE254AA42C31131600A4015E /* Color.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Color.xcassets; sourceTree = ""; }; DE254AA72C3118EA00A4015E /* UIView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+.swift"; sourceTree = ""; }; DE254AA92C31190E00A4015E /* UIStackView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackView+.swift"; sourceTree = ""; }; @@ -277,6 +304,82 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 782B406C2C3DBF72008B0CA7 /* Profile */ = { + isa = PBXGroup; + children = ( + 782B40732C3DBFA8008B0CA7 /* View */, + 782B40702C3DBF97008B0CA7 /* ViewModel */, + 782B406D2C3DBF7C008B0CA7 /* VIewController */, + ); + path = Profile; + sourceTree = ""; + }; + 782B406D2C3DBF7C008B0CA7 /* VIewController */ = { + isa = PBXGroup; + children = ( + 782B406E2C3DBF93008B0CA7 /* ProfileViewController.swift */, + ); + path = VIewController; + sourceTree = ""; + }; + 782B40702C3DBF97008B0CA7 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 782B40712C3DBFA3008B0CA7 /* ProfileViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 782B40732C3DBFA8008B0CA7 /* View */ = { + isa = PBXGroup; + children = ( + 782B40742C3DBFBA008B0CA7 /* ProfileView.swift */, + ); + path = View; + sourceTree = ""; + }; + 782B40762C3E389F008B0CA7 /* Welcome */ = { + isa = PBXGroup; + children = ( + 782B40792C3E38C3008B0CA7 /* ViewController */, + 782B40782C3E38BC008B0CA7 /* ViewModel */, + 782B40772C3E38B5008B0CA7 /* View */, + ); + path = Welcome; + sourceTree = ""; + }; + 782B40772C3E38B5008B0CA7 /* View */ = { + isa = PBXGroup; + children = ( + 782B407A2C3E395A008B0CA7 /* WelcomeView.swift */, + ); + path = View; + sourceTree = ""; + }; + 782B40782C3E38BC008B0CA7 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 782B407E2C3E44B7008B0CA7 /* WelcomeViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 782B40792C3E38C3008B0CA7 /* ViewController */ = { + isa = PBXGroup; + children = ( + 782B407C2C3E3984008B0CA7 /* WelcomeViewController.swift */, + ); + path = ViewController; + sourceTree = ""; + }; + 782B40802C3E48BD008B0CA7 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 782B40812C3E4925008B0CA7 /* NicknameViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; 789873352C3D1B3000435E96 /* View */ = { isa = PBXGroup; children = ( @@ -301,6 +404,32 @@ path = ViewController; sourceTree = ""; }; + 78AED1322C3D9514000AD80A /* Nickname */ = { + isa = PBXGroup; + children = ( + 782B40802C3E48BD008B0CA7 /* ViewModel */, + 78AED1382C3D98DB000AD80A /* ViewController */, + 78AED1352C3D98C1000AD80A /* NicknameView */, + ); + path = Nickname; + sourceTree = ""; + }; + 78AED1352C3D98C1000AD80A /* NicknameView */ = { + isa = PBXGroup; + children = ( + 78AED1362C3D98D1000AD80A /* NicknameView.swift */, + ); + path = NicknameView; + sourceTree = ""; + }; + 78AED1382C3D98DB000AD80A /* ViewController */ = { + isa = PBXGroup; + children = ( + 78AED1332C3D951F000AD80A /* NicknameViewController.swift */, + ); + path = ViewController; + sourceTree = ""; + }; 78B9285F2C29402C006D9942 = { isa = PBXGroup; children = ( @@ -320,6 +449,7 @@ 78B9286A2C29402C006D9942 /* KkuMulKum */ = { isa = PBXGroup; children = ( + 78AED1312C3D94F2000AD80A /* KkuMulKum.entitlements */, DE254AA12C31106700A4015E /* Application */, DE254AA22C31107700A4015E /* Source */, DE254AA32C31107C00A4015E /* Resource */, @@ -331,6 +461,9 @@ DD865B652C3920F600C351A2 /* Onboarding */ = { isa = PBXGroup; children = ( + 782B40762C3E389F008B0CA7 /* Welcome */, + 782B406C2C3DBF72008B0CA7 /* Profile */, + 78AED1322C3D9514000AD80A /* Nickname */, DD865B662C39210E00C351A2 /* Login */, ); path = Onboarding; @@ -401,14 +534,6 @@ path = GroupList; sourceTree = ""; }; - DDA2EE7D2C386087007C6059 /* MyPage */ = { - isa = PBXGroup; - children = ( - DDA2EE782C385FCF007C6059 /* MyPageViewController.swift */, - ); - path = MyPage; - sourceTree = ""; - }; DDA2EE7E2C3860B2007C6059 /* Core */ = { isa = PBXGroup; children = ( @@ -436,6 +561,34 @@ path = Promise; sourceTree = ""; }; + DE159D2E2C406E1600425101 /* View */ = { + isa = PBXGroup; + children = ( + DE159D2A2C406E1600425101 /* MyPageAlarmSettingView.swift */, + DE159D2B2C406E1600425101 /* MyPageContentView.swift */, + DE159D2C2C406E1600425101 /* MyPageEtcSettingView.swift */, + DE159D2D2C406E1600425101 /* MyPageNavigationView.swift */, + ); + path = View; + sourceTree = ""; + }; + DE159D302C406E1600425101 /* ViewController */ = { + isa = PBXGroup; + children = ( + DE159D2F2C406E1600425101 /* MyPageViewController.swift */, + ); + path = ViewController; + sourceTree = ""; + }; + DE159D312C406E1600425101 /* MyPage */ = { + isa = PBXGroup; + children = ( + DE159D2E2C406E1600425101 /* View */, + DE159D302C406E1600425101 /* ViewController */, + ); + path = MyPage; + sourceTree = ""; + }; DE254AA12C31106700A4015E /* Application */ = { isa = PBXGroup; children = ( @@ -451,8 +604,8 @@ DDA2EE7E2C3860B2007C6059 /* Core */, DDA2EE7B2C386078007C6059 /* Home */, DDA2EE7C2C38607F007C6059 /* GroupList */, + DE159D312C406E1600425101 /* MyPage */, DE6D4D0E2C3F14D80005584B /* MeetingInfo */, - DDA2EE7D2C386087007C6059 /* MyPage */, DD865B652C3920F600C351A2 /* Onboarding */, DDAF1C872C3D6E3D008A37D3 /* Promise */, ); @@ -847,20 +1000,25 @@ DE6D4D142C3F14D80005584B /* MeetingPromiseCell.swift in Sources */, DE6D4D102C3F14D80005584B /* InvitationCodePopUpView.swift in Sources */, DD30721A2C3C011600416D9F /* AddPromiseRequestModel.swift in Sources */, + DE159D332C406E1600425101 /* MyPageContentView.swift in Sources */, DE6D4D112C3F14D80005584B /* MeetingInfoBannerView.swift in Sources */, DE6D4D122C3F14D80005584B /* MeetingInfoView.swift in Sources */, DD30721E2C3C0CC800416D9F /* PromiseInfoResponseModel.swift in Sources */, DD931B722C3DA92700526452 /* EnterReadyInfoButtonView.swift in Sources */, A3FB18512C3BF531001483E5 /* RegisterMeetingsResponseModel.swift in Sources */, 789AD4B32C3C0093002E2688 /* SocialLoginResponseModel.swift in Sources */, + DE159D352C406E1600425101 /* MyPageNavigationView.swift in Sources */, DE9E188B2C3BC92500DB76B4 /* EmptyModel.swift in Sources */, DDA2EE732C385EB9007C6059 /* MainTabBarController.swift in Sources */, A3FB184D2C3BF45F001483E5 /* MakeMeetingsRequestModel.swift in Sources */, DEBA03312C3C2972002ED8F2 /* ViewController.swift in Sources */, DD931B742C3DAB9A00526452 /* ReadyPlanInfoView.swift in Sources */, 789873342C3D1A7B00435E96 /* LoginView.swift in Sources */, + 782B40822C3E4925008B0CA7 /* NicknameViewModel.swift in Sources */, + 782B406F2C3DBF93008B0CA7 /* ProfileViewController.swift in Sources */, A3FB18592C3BF77D001483E5 /* MeetingInfoResponseModel.swift in Sources */, DEA932182C3F180800FDF637 /* MeetingPromisesModel.swift in Sources */, + DE159D342C406E1600425101 /* MyPageEtcSettingView.swift in Sources */, DE9E18842C3BA84500DB76B4 /* CustomTextField.swift in Sources */, A3FB184F2C3BF4BC001483E5 /* MakeMeetingsResponseModel.swift in Sources */, DEF725DB2C3F3BBF008C87C7 /* Toast.swift in Sources */, @@ -868,11 +1026,14 @@ DE254AB72C3119D000A4015E /* ReuseIdentifiable.swift in Sources */, DDA2EE752C385FB1007C6059 /* HomeViewController.swift in Sources */, DDAF1C922C3D6E3D008A37D3 /* PromiseInfoViewController.swift in Sources */, + 78AED1342C3D951F000AD80A /* NicknameViewController.swift in Sources */, DE254AB42C31199B00A4015E /* UITextField+.swift in Sources */, DDAF1C912C3D6E3D008A37D3 /* TardyViewController.swift in Sources */, + 782B407D2C3E3984008B0CA7 /* WelcomeViewController.swift in Sources */, DE8248002C36E857000601BC /* ObservablePattern.swift in Sources */, DDAF1C902C3D6E3D008A37D3 /* PromiseViewModel.swift in Sources */, DE254AAA2C31190E00A4015E /* UIStackView+.swift in Sources */, + DE159D362C406E1600425101 /* MyPageViewController.swift in Sources */, DED5DBF02C345317006ECE7E /* BaseCollectionViewCell.swift in Sources */, DE32D1D42C3BFB56006848DF /* UpdateProfileNameModel.swift in Sources */, DE6D4D132C3F14D80005584B /* MeetingMemberCell.swift in Sources */, @@ -882,11 +1043,14 @@ DE254AAE2C31193600A4015E /* UIFont+.swift in Sources */, DE6D4D152C3F14D80005584B /* InvitationCodePopUpViewController.swift in Sources */, DE9E189A2C3BCCBE00DB76B4 /* UtilsTemp.swift in Sources */, + 782B407B2C3E395A008B0CA7 /* WelcomeView.swift in Sources */, DD3072142C3BF87A00416D9F /* NearestPromiseResponseModel.swift in Sources */, + 782B407F2C3E44B7008B0CA7 /* WelcomeViewModel.swift in Sources */, DD3072162C3BFE4E00416D9F /* UpcomingPromiseListResponseModel.swift in Sources */, A3FB18572C3BF704001483E5 /* MeetingListResponseModel.swift in Sources */, DE254AB22C31197B00A4015E /* UIButton+.swift in Sources */, DE6D4D162C3F14D80005584B /* MeetingInfoViewController.swift in Sources */, + DE159D322C406E1600425101 /* MyPageAlarmSettingView.swift in Sources */, DDA2EE772C385FC3007C6059 /* GroupListViewController.swift in Sources */, 78B9286C2C29402C006D9942 /* AppDelegate.swift in Sources */, DEBA032F2C3C24F2002ED8F2 /* ModelType.swift in Sources */, @@ -895,10 +1059,11 @@ DED5DBF42C34539A006ECE7E /* BaseTableViewCell.swift in Sources */, 78B9286E2C29402C006D9942 /* SceneDelegate.swift in Sources */, DE6D4D172C3F14D80005584B /* MeetingInfoViewModel.swift in Sources */, - DDA2EE792C385FCF007C6059 /* MyPageViewController.swift in Sources */, + 78AED1372C3D98D1000AD80A /* NicknameView.swift in Sources */, A3FB185B2C3BF7DF001483E5 /* MeetingMembersResponseModel.swift in Sources */, DD3072222C3C0DA300416D9F /* PromiseParticipantListResponseModel.swift in Sources */, 789873322C3D1A7B00435E96 /* LoginViewController.swift in Sources */, + 782B40722C3DBFA3008B0CA7 /* ProfileViewModel.swift in Sources */, DDAF1C8F2C3D6E3D008A37D3 /* PromiseSegmentedControl.swift in Sources */, DE32D1D22C3BF703006848DF /* LoginUserResponseModel.swift in Sources */, DE9E18892C3BC91000DB76B4 /* ResponseBodyDTO.swift in Sources */, @@ -909,6 +1074,7 @@ DD931B762C3DC16100526452 /* PromiseInfoView.swift in Sources */, DD3072242C3C0EB200416D9F /* MyPromiseReadyInfoRequestModel.swift in Sources */, 789873332C3D1A7B00435E96 /* LoginViewModel.swift in Sources */, + 782B40752C3DBFBA008B0CA7 /* ProfileView.swift in Sources */, DED5DBEE2C34529A006ECE7E /* BaseView.swift in Sources */, DE9E18802C3BA4AA00DB76B4 /* CustomButton.swift in Sources */, DE254AB02C31195B00A4015E /* NSAttributedString+.swift in Sources */, @@ -1057,6 +1223,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = KkuMulKum/KkuMulKum.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; @@ -1090,6 +1257,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = KkuMulKum/KkuMulKum.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; diff --git a/KkuMulKum/Application/AppDelegate.swift b/KkuMulKum/Application/AppDelegate.swift index 00e72fe0..e2d166b5 100644 --- a/KkuMulKum/Application/AppDelegate.swift +++ b/KkuMulKum/Application/AppDelegate.swift @@ -16,6 +16,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + + // sleep(1) // KakaoSDK 초기화 과정에서 앱 키를 동적으로 불러오기 if let kakaoAppKey = fetchKakaoAppKeyFromPrivacyInfo() { KakaoSDK.initSDK(appKey: kakaoAppKey) diff --git a/KkuMulKum/Application/SceneDelegate.swift b/KkuMulKum/Application/SceneDelegate.swift index 11d514e9..e75269f7 100644 --- a/KkuMulKum/Application/SceneDelegate.swift +++ b/KkuMulKum/Application/SceneDelegate.swift @@ -18,7 +18,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { ) { guard let windowScene = (scene as? UIWindowScene) else { return } self.window = UIWindow(windowScene: windowScene) - self.window?.rootViewController = MainTabBarController() + self.window?.rootViewController = NicknameViewController() self.window?.makeKeyAndVisible() } diff --git a/KkuMulKum/Resource/Assets.xcassets/Image/img_edit.imageset/Contents.json b/KkuMulKum/Resource/Assets.xcassets/Image/img_edit.imageset/Contents.json new file mode 100644 index 00000000..c9c7d7d5 --- /dev/null +++ b/KkuMulKum/Resource/Assets.xcassets/Image/img_edit.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "edit.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/KkuMulKum/Resource/Assets.xcassets/Image/img_edit.imageset/edit.png b/KkuMulKum/Resource/Assets.xcassets/Image/img_edit.imageset/edit.png new file mode 100644 index 00000000..e9fcd177 Binary files /dev/null and b/KkuMulKum/Resource/Assets.xcassets/Image/img_edit.imageset/edit.png differ diff --git a/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/Contents.json b/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/Contents.json index 165fd2cc..a00d154d 100644 --- a/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/Contents.json +++ b/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/Contents.json @@ -1,19 +1,8 @@ { "images" : [ { - "filename" : "ic_profilebasic.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "ic_profilebasic@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "ic_profilebasic@3x.png", - "idiom" : "universal", - "scale" : "3x" + "filename" : "Mask group.svg", + "idiom" : "universal" } ], "info" : { diff --git a/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/Mask group.svg b/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/Mask group.svg new file mode 100644 index 00000000..fceab80b --- /dev/null +++ b/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/Mask group.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/ic_profilebasic.png b/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/ic_profilebasic.png deleted file mode 100644 index 085ccad5..00000000 Binary files a/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/ic_profilebasic.png and /dev/null differ diff --git a/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/ic_profilebasic@2x.png b/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/ic_profilebasic@2x.png deleted file mode 100644 index 906c465e..00000000 Binary files a/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/ic_profilebasic@2x.png and /dev/null differ diff --git a/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/ic_profilebasic@3x.png b/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/ic_profilebasic@3x.png deleted file mode 100644 index b3221088..00000000 Binary files a/KkuMulKum/Resource/Assets.xcassets/Image/img_profile.imageset/ic_profilebasic@3x.png and /dev/null differ diff --git a/KkuMulKum/Resource/Assets.xcassets/Splash.imageset/Contents.json b/KkuMulKum/Resource/Assets.xcassets/Splash.imageset/Contents.json new file mode 100644 index 00000000..af845b10 --- /dev/null +++ b/KkuMulKum/Resource/Assets.xcassets/Splash.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Splash.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Splash 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Splash 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/KkuMulKum/Resource/Assets.xcassets/Splash.imageset/Splash 1.png b/KkuMulKum/Resource/Assets.xcassets/Splash.imageset/Splash 1.png new file mode 100644 index 00000000..6d90a30c Binary files /dev/null and b/KkuMulKum/Resource/Assets.xcassets/Splash.imageset/Splash 1.png differ diff --git a/KkuMulKum/Resource/Assets.xcassets/Splash.imageset/Splash 2.png b/KkuMulKum/Resource/Assets.xcassets/Splash.imageset/Splash 2.png new file mode 100644 index 00000000..6d90a30c Binary files /dev/null and b/KkuMulKum/Resource/Assets.xcassets/Splash.imageset/Splash 2.png differ diff --git a/KkuMulKum/Resource/Assets.xcassets/Splash.imageset/Splash.png b/KkuMulKum/Resource/Assets.xcassets/Splash.imageset/Splash.png new file mode 100644 index 00000000..6d90a30c Binary files /dev/null and b/KkuMulKum/Resource/Assets.xcassets/Splash.imageset/Splash.png differ diff --git a/KkuMulKum/Resource/Base.lproj/LaunchScreen.storyboard b/KkuMulKum/Resource/Base.lproj/LaunchScreen.storyboard index 865e9329..03ad235a 100644 --- a/KkuMulKum/Resource/Base.lproj/LaunchScreen.storyboard +++ b/KkuMulKum/Resource/Base.lproj/LaunchScreen.storyboard @@ -1,8 +1,11 @@ - - + + + - + + + @@ -11,15 +14,32 @@ - + - + + + + + + + + + + + + - + + + + + + + diff --git a/KkuMulKum/Resource/Info.plist b/KkuMulKum/Resource/Info.plist index baa28139..b4103525 100644 --- a/KkuMulKum/Resource/Info.plist +++ b/KkuMulKum/Resource/Info.plist @@ -2,6 +2,10 @@ + NSPhotoLibraryAddUsageDescription + 정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진! + NSCameraUsageDescription + 정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진! UIUserInterfaceStyle Light CFBundleURLTypes diff --git a/KkuMulKum/Source/MyPage/MyPageViewController.swift b/KkuMulKum/Source/MyPage/MyPageViewController.swift deleted file mode 100644 index 73f1ea54..00000000 --- a/KkuMulKum/Source/MyPage/MyPageViewController.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// MyPageViewController.swift -// KkuMulKum -// -// Created by YOUJIM on 7/6/24. -// - -import UIKit - -class MyPageViewController: BaseViewController { - - override func viewDidLoad() { - super.viewDidLoad() - - view.backgroundColor = .white - } - - - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. - } - */ - -} diff --git a/KkuMulKum/Source/MyPage/View/MyPageAlarmSettingView.swift b/KkuMulKum/Source/MyPage/View/MyPageAlarmSettingView.swift new file mode 100644 index 00000000..a44447f8 --- /dev/null +++ b/KkuMulKum/Source/MyPage/View/MyPageAlarmSettingView.swift @@ -0,0 +1,80 @@ +// +// MyPageAlarmSettingView.swift +// KkuMulKum +// +// Created by 이지훈 on 7/9/24. +// + +import UIKit + +import SnapKit +import Then + +class AlarmSettingView: BaseView { + private let containerView = UIView().then { + $0.backgroundColor = .white + $0.layer.borderWidth = 1 + $0.layer.borderColor = UIColor.gray2.cgColor + $0.layer.cornerRadius = 8 + } + + private let stackView = UIStackView(axis: .vertical).then { + $0.spacing = 8 + $0.alignment = .fill + $0.distribution = .fill + } + + private let titleStackView = UIStackView(axis: .horizontal).then { + $0.alignment = .center + $0.distribution = .equalSpacing + } + + private let titleLabel = UILabel().then { + $0.setText("알림 설정", style: .body03, color: .black) + + } + + private let toggleSwitch = UISwitch().then { + $0.onTintColor = .green + } + + private let subtitleLabel = UILabel().then { + $0.setText("준비, 이동을 시작해야할 시간에\n푸시 알림을 받을 수 있습니다.", style: .caption02, color: .gray) + } + + override func setupView() { + super.setupView() + backgroundColor = .systemMint.withAlphaComponent(0.1) + + addSubview(containerView) + containerView.addSubview(stackView) + + titleStackView.addArrangedSubviews(titleLabel, toggleSwitch) + stackView.addArrangedSubviews(titleStackView, subtitleLabel) + } + + override func setupAutoLayout() { + super.setupAutoLayout() + + containerView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + stackView.snp.makeConstraints { + $0.edges.equalToSuperview().inset(15) + } + + titleStackView.snp.makeConstraints { + $0.height.greaterThanOrEqualTo(44) + } + + toggleSwitch.snp.makeConstraints { + $0.width.equalTo(51) + $0.height.equalTo(31) + } + + subtitleLabel.snp.makeConstraints { + $0.height.greaterThanOrEqualTo(40) // 최소 높이 설정 + } + } +} diff --git a/KkuMulKum/Source/MyPage/View/MyPageContentView.swift b/KkuMulKum/Source/MyPage/View/MyPageContentView.swift new file mode 100644 index 00000000..51d0cdca --- /dev/null +++ b/KkuMulKum/Source/MyPage/View/MyPageContentView.swift @@ -0,0 +1,84 @@ +// +// MyPageContentView.swift +// KkuMulKum +// +// Created by 이지훈 on 7/9/24. +// + +import UIKit + +import SnapKit +import Then + +class MyPageContentView: BaseView { + let profileStackView = UIStackView(axis: .vertical).then { + $0.spacing = 12 + $0.alignment = .center + } + + let profileImageView = UIImageView().then { + $0.image = UIImage(named: "img_profile") + $0.contentMode = .scaleAspectFill + $0.clipsToBounds = true + } + + let nameLabel = UILabel().then { + $0.font = UIFont.pretendard(.body01) + $0.textColor = .gray8 + $0.text = "꾸물리안 님" + } + + let levelView = UIView().then { + $0.backgroundColor = .maincolor + $0.layer.cornerRadius = 20 + } + + let levelLabel = UILabel().then { + $0.textColor = .white + $0.font = UIFont.pretendard(.body02) + $0.text = "Lv. 1 지각대장 꾸물이" + } + + let separatorView = UIView().then { + $0.backgroundColor = .green2 + } + + override func setupView() { + backgroundColor = .clear + addSubviews(profileStackView,levelView,separatorView) + profileStackView.addArrangedSubviews(profileImageView, nameLabel) + levelView.addSubview(levelLabel) + } + + override func setupAutoLayout() { + profileStackView.snp.makeConstraints { + $0.top.equalToSuperview().offset(20) + $0.centerX.equalToSuperview() + } + + profileImageView.snp.makeConstraints { + $0.size.equalTo(100) + } + + nameLabel.snp.makeConstraints { + $0.top.equalTo(profileImageView.snp.bottom).offset(12) + } + + levelView.snp.makeConstraints { + $0.top.equalTo(nameLabel.snp.bottom).offset(12) + $0.height.equalTo(36) + $0.leading.trailing.equalToSuperview().inset(80) + } + + levelLabel.snp.makeConstraints { + $0.center.equalToSuperview() + } + + separatorView.snp.makeConstraints { + $0.height.equalTo(6) + $0.leading.trailing.equalToSuperview() + $0.top.equalTo(levelView.snp.bottom).offset(35) + $0.bottom.equalToSuperview().offset(-20) + } + } +} diff --git a/KkuMulKum/Source/MyPage/View/MyPageEtcSettingView.swift b/KkuMulKum/Source/MyPage/View/MyPageEtcSettingView.swift new file mode 100644 index 00000000..b444cfbb --- /dev/null +++ b/KkuMulKum/Source/MyPage/View/MyPageEtcSettingView.swift @@ -0,0 +1,106 @@ +// +// MyPageEtcSettingView.swift +// KkuMulKum +// +// Created by 이지훈 on 7/9/24. +// + +import UIKit + +import SnapKit +import Then + +class EtcSettingView: BaseView { + let containerView = UIView().then { + $0.backgroundColor = .white + $0.layer.borderWidth = 1 + $0.layer.borderColor = UIColor.gray2.cgColor + $0.layer.cornerRadius = 12 + } + + let stackView = UIStackView().then { + $0.axis = .vertical + $0.spacing = 0 + $0.distribution = .fillEqually + } + + override func setupView() { + addSubview(containerView) + containerView.addSubview(stackView) + + let rows = [ + createRow(title: "버전정보", subtitle: "0.1.0"), + createRow(title: "이용약관"), + createRow(title: "로그아웃"), + createRow(title: "탈퇴하기") + ] + + rows.forEach { + stackView.addArrangedSubview($0) + $0.heightAnchor.constraint(equalToConstant: 50).isActive = true + } + } + + override func setupAutoLayout() { + containerView.snp.makeConstraints { + $0.edges.equalToSuperview().inset(16) + } + + stackView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } + + private func createRow(title: String, subtitle: String? = nil) -> UIView { + let rowView = UIView() + let titleLabel = UILabel().then { + $0.text = title + $0.font = UIFont.pretendard(.body01) + $0.textColor = .gray7 + } + rowView.addSubview(titleLabel) + + titleLabel.snp.makeConstraints { + $0.left.equalToSuperview().offset(16) + $0.centerY.equalToSuperview() + } + + if let subtitle = subtitle { + let subtitleLabel = UILabel().then { + $0.text = subtitle + $0.font = UIFont.pretendard(.body01) + $0.textColor = .gray8 + } + rowView.addSubview(subtitleLabel) + + subtitleLabel.snp.makeConstraints { + $0.right.equalToSuperview().offset(-16) + $0.centerY.equalToSuperview() + } + } + + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(rowTapped(_:))) + rowView.addGestureRecognizer(tapGesture) + rowView.isUserInteractionEnabled = true + + return rowView + } + + @objc private func rowTapped(_ gesture: UITapGestureRecognizer) { + guard let tappedView = gesture.view else { return } + let index = stackView.arrangedSubviews.firstIndex(of: tappedView) + + switch index { + case 0: + print("버전정보 탭됨") + case 1: + print("이용약관 탭됨") + case 2: + print("로그아웃 탭됨") + case 3: + print("탈퇴하기 탭됨") + default: + break + } + } +} diff --git a/KkuMulKum/Source/MyPage/View/MyPageNavigationView.swift b/KkuMulKum/Source/MyPage/View/MyPageNavigationView.swift new file mode 100644 index 00000000..a929501d --- /dev/null +++ b/KkuMulKum/Source/MyPage/View/MyPageNavigationView.swift @@ -0,0 +1,43 @@ +// +// MypageView.swift +// KkuMulKum +// +// Created by 이지훈 on 7/9/24. +// + +import UIKit + +import SnapKit +import Then + +class MyPageNavigationView: BaseView { + + let titleLabel = UILabel().then { + $0.text = "마이페이지" + $0.textAlignment = .center + $0.font = UIFont.pretendard(.body03) + $0.textColor = .black + } + + let separatorView = UIView().then { + $0.backgroundColor = .gray1 + } + + override func setupView() { + backgroundColor = .white + addSubview(titleLabel) + addSubview(separatorView) + } + + override func setupAutoLayout() { + titleLabel.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.bottom.equalToSuperview().offset(-16) + } + + separatorView.snp.makeConstraints { + $0.height.equalTo(1) + $0.leading.trailing.bottom.equalToSuperview() + } + } +} diff --git a/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift b/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift new file mode 100644 index 00000000..db6ad7d4 --- /dev/null +++ b/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift @@ -0,0 +1,56 @@ +// +// MyPageViewController.swift +// KkuMulKum +// +// Created by YOUJIM on 7/6/24. +// + +import UIKit + +class MyPageViewController: BaseViewController { + + private let navigationView = MyPageNavigationView() + private let myPageContentView = MyPageContentView() + private let alarmSettingView = AlarmSettingView() + private let etcSettingView = EtcSettingView() + + override func viewDidLoad() { + super.viewDidLoad() + setupView() + setupAutoLayout() + } + + override func setupView() { + super.setupView() + view.backgroundColor = .green1 + view.addSubview(navigationView) + view.addSubview(myPageContentView) + view.addSubview(alarmSettingView) + view.addSubview(etcSettingView) + } + + private func setupAutoLayout() { + navigationView.snp.makeConstraints { + $0.top.equalTo(view) + $0.leading.trailing.equalTo(view.safeAreaLayoutGuide) + $0.height.equalTo(96) + } + + myPageContentView.snp.makeConstraints { + $0.top.equalTo(navigationView.snp.bottom) + $0.leading.trailing.equalToSuperview() + } + + alarmSettingView.snp.makeConstraints { + $0.top.equalTo(myPageContentView.snp.bottom).offset(20) + $0.leading.trailing.equalToSuperview().inset(20) + $0.height.greaterThanOrEqualTo(120) + } + + etcSettingView.snp.makeConstraints { + $0.top.equalTo(alarmSettingView.snp.bottom).offset(20) + $0.leading.trailing.equalToSuperview().inset(4) + $0.bottom.lessThanOrEqualTo(view.safeAreaLayoutGuide.snp.bottom).offset(-20) + } + } +} diff --git a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift index 53127bf6..d7868a29 100644 --- a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift @@ -70,12 +70,19 @@ class LoginViewModel: NSObject { extension LoginViewModel: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { - guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else { + guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential else { print("Authorization failed: Credential is not of type ASAuthorizationAppleIDCredential") return } - let userName = credential.fullName?.givenName ?? "Apple user" + + let userName = appleIDCredential.fullName?.givenName ?? "Apple user" loginState.value = .loggedIn(userInfo: "Apple user: \(userName)") + + // 액세스 토큰 출력 + if let identityToken = appleIDCredential.identityToken, + let tokenString = String(data: identityToken, encoding: .utf8) { + print("Apple Login Access Token: \(tokenString)") + } } func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { diff --git a/KkuMulKum/Source/Onboarding/Nickname/NicknameView/NicknameView.swift b/KkuMulKum/Source/Onboarding/Nickname/NicknameView/NicknameView.swift new file mode 100644 index 00000000..a56ba06f --- /dev/null +++ b/KkuMulKum/Source/Onboarding/Nickname/NicknameView/NicknameView.swift @@ -0,0 +1,107 @@ +// +// NicknameView.swift +// KkuMulKum +// +// Created by 이지훈 on 7/10/24. +// + +import UIKit + +import SnapKit +import Then + +class NicknameView: BaseView { + + let navigationBar = UIView().then { + $0.backgroundColor = .white + } + + let titleLabel = UILabel().then { + $0.setText("프로필 설정", style: .body03, color: .black) + } + + let separatorLine = UIView().then { + $0.backgroundColor = .gray2 + } + + let subtitleLabel = UILabel().then { + $0.setText("이름을 설정해 주세요", style: .head01, color: .gray8) + } + + let nicknameTextField = CustomTextField(placeHolder: "이름을 입력해 주세요").then { + $0.layer.cornerRadius = 8 + $0.layer.borderWidth = 1 + $0.layer.borderColor = UIColor.gray3.cgColor + } + + let characterCountLabel = UILabel().then { + $0.setText("0/5", style: .body06, color: .gray) + $0.textAlignment = .right + } + + let errorLabel = UILabel().then { + $0.setText("한글, 영문, 숫자만을 사용해 총 5자 이내로 입력해주세요.", style: .caption02, color: .red) + $0.isHidden = true + } + + let nextButton = UIButton().then { + $0.setTitle("다음", style: .body01, color: .white) + $0.backgroundColor = .gray2 + $0.setLayer(borderWidth: 0, borderColor: .clear, cornerRadius: 8) + $0.isEnabled = false + } + + override func setupView() { + backgroundColor = .white + + [navigationBar, separatorLine, subtitleLabel, nicknameTextField, errorLabel, nextButton].forEach { addSubview($0) } + navigationBar.addSubview(titleLabel) + nicknameTextField.addSubview(characterCountLabel) + } + + override func setupAutoLayout() { + navigationBar.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(92) + } + + titleLabel.snp.makeConstraints { + $0.centerX.equalTo(navigationBar.snp.centerX) + $0.bottom.equalTo(navigationBar.snp.bottom).offset(-12) + } + + separatorLine.snp.makeConstraints { + $0.top.equalTo(navigationBar.snp.bottom) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(1) + } + + subtitleLabel.snp.makeConstraints { + $0.top.equalTo(separatorLine.snp.bottom).offset(24) + $0.leading.trailing.equalToSuperview().inset(20) + } + + nicknameTextField.snp.makeConstraints { + $0.top.equalTo(subtitleLabel.snp.bottom).offset(22) + $0.leading.trailing.equalToSuperview().inset(20) + $0.height.equalTo(CustomTextField.defaultHeight) + } + + characterCountLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.trailing.equalToSuperview().inset(12) + $0.width.equalTo(30) + } + + errorLabel.snp.makeConstraints { + $0.top.equalTo(nicknameTextField.snp.bottom).offset(8) + $0.leading.trailing.equalToSuperview().inset(20) + } + + nextButton.snp.makeConstraints { + $0.leading.trailing.equalToSuperview().inset(20) + $0.bottom.equalTo(safeAreaLayoutGuide).offset(-20) + $0.height.equalTo(52) + } + } +} diff --git a/KkuMulKum/Source/Onboarding/Nickname/ViewController/NicknameViewController.swift b/KkuMulKum/Source/Onboarding/Nickname/ViewController/NicknameViewController.swift new file mode 100644 index 00000000..5c15c511 --- /dev/null +++ b/KkuMulKum/Source/Onboarding/Nickname/ViewController/NicknameViewController.swift @@ -0,0 +1,93 @@ +// +// NicknameViewController.swift +// KkuMulKum +// +// Created by 이지훈 on 7/10/24. +// + +import UIKit + +class NicknameViewController: BaseViewController { + + private let nicknameView = NicknameView() + private let viewModel = NicknameViewModel() + + override func loadView() { + view = nicknameView + } + + override func viewDidLoad() { + super.viewDidLoad() + setupBindings() + setupActions() + setupTextField() + setupTapGesture() + } + + private func setupBindings() { + viewModel.nicknameState.bind { [weak self] state in + switch state { + case .empty: + self?.nicknameView.nicknameTextField.layer.borderColor = UIColor.gray3.cgColor + self?.nicknameView.errorLabel.isHidden = true + case .valid: + self?.nicknameView.nicknameTextField.layer.borderColor = UIColor.maincolor.cgColor + self?.nicknameView.errorLabel.isHidden = true + case .invalid: + self?.nicknameView.nicknameTextField.layer.borderColor = UIColor.red.cgColor + self?.nicknameView.errorLabel.isHidden = false + } + } + + viewModel.errorMessage.bind { [weak self] errorMessage in + self?.nicknameView.errorLabel.text = errorMessage + } + + viewModel.isNextButtonEnabled.bind { [weak self] isEnabled in + self?.nicknameView.nextButton.isEnabled = isEnabled + self?.nicknameView.nextButton.backgroundColor = isEnabled ? .maincolor : .gray2 + } + + viewModel.characterCount.bind { [weak self] count in + self?.nicknameView.characterCountLabel.text = count + } + } + + private func setupActions() { + nicknameView.nicknameTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) + nicknameView.nextButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside) + } + + private func setupTextField() { + nicknameView.nicknameTextField.delegate = self + nicknameView.nicknameTextField.returnKeyType = .done + } + + private func setupTapGesture() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) + view.addGestureRecognizer(tapGesture) + } + + @objc private func textFieldDidChange(_ textField: UITextField) { + viewModel.validateNickname(textField.text ?? "") + } + + @objc private func nextButtonTapped() { + let profileSetupVC = ProfileSetupViewController(viewModel: ProfileSetupViewModel(nickname: viewModel.nickname.value)) + profileSetupVC.modalPresentationStyle = .fullScreen + present(profileSetupVC, animated: true, completion: nil) + } + + @objc private func dismissKeyboard() { + view.endEditing(true) + nicknameView.nicknameTextField.layer.borderColor = UIColor.gray3.cgColor + } +} + +extension NicknameViewController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + nicknameView.nicknameTextField.layer.borderColor = UIColor.gray3.cgColor + return true + } +} diff --git a/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift b/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift new file mode 100644 index 00000000..ef6d3b2d --- /dev/null +++ b/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift @@ -0,0 +1,43 @@ +// +// NicknameViewModel.swift +// KkuMulKum +// +// Created by 이지훈 on 7/10/24. +// + +import Foundation + +enum NicknameState { + case empty + case valid + case invalid +} + +class NicknameViewModel { + let nickname = ObservablePattern("") + let nicknameState = ObservablePattern(.empty) + let errorMessage = ObservablePattern(nil) + let isNextButtonEnabled = ObservablePattern(false) + let characterCount = ObservablePattern("0/5") + + private let nicknameRegex = "^[가-힣a-zA-Z0-9]{1,5}$" + + func validateNickname(_ name: String) { + nickname.value = name + characterCount.value = "\(name.count)/5" + + if name.isEmpty { + nicknameState.value = .empty + errorMessage.value = nil + isNextButtonEnabled.value = false + } else if name.range(of: nicknameRegex, options: .regularExpression) != nil { + nicknameState.value = .valid + errorMessage.value = nil + isNextButtonEnabled.value = true + } else { + nicknameState.value = .invalid + errorMessage.value = "한글, 영문, 숫자만을 사용해 총 5자 이내로 입력해주세요." + isNextButtonEnabled.value = false + } + } +} diff --git a/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileViewController.swift b/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileViewController.swift new file mode 100644 index 00000000..ad6bafb7 --- /dev/null +++ b/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileViewController.swift @@ -0,0 +1,96 @@ +// +// ProfileViewController.swift +// KkuMulKum +// +// Created by 이지훈 on 7/10/24. +// + +import UIKit + +class ProfileSetupViewController: BaseViewController { + private let rootView = ProfileSetupView() + private let viewModel: ProfileSetupViewModel + + init(viewModel: ProfileSetupViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = rootView + } + + override func viewDidLoad() { + super.viewDidLoad() + setupBindings() + setupAction() + } + + private func setupBindings() { + viewModel.profileImage.bind { [weak self] image in + self?.rootView.profileImageView.image = image + } + + viewModel.isConfirmButtonEnabled.bind { [weak self] isEnabled in + self?.rootView.confirmButton.isEnabled = isEnabled + self?.rootView.confirmButton.alpha = isEnabled ? 1.0 : 0.5 + } + } + + override func setupAction() { + rootView.confirmButton.addTarget(self, action: #selector(confirmButtonTapped), for: .touchUpInside) + rootView.skipButton.addTarget(self, action: #selector(skipButtonTapped), for: .touchUpInside) + rootView.cameraButton.addTarget(self, action: #selector(cameraButtonTapped), for: .touchUpInside) + } + + @objc private func confirmButtonTapped() { + let welcomeVC = WelcomeViewController(viewModel: WelcomeViewModel(nickname: viewModel.nickname)) + welcomeVC.modalPresentationStyle = .fullScreen + present(welcomeVC, animated: true, completion: nil) + } + + @objc private func skipButtonTapped() { + let welcomeVC = WelcomeViewController(viewModel: WelcomeViewModel(nickname: viewModel.nickname)) + welcomeVC.modalPresentationStyle = .fullScreen + present(welcomeVC, animated: true, completion: nil) + } + + @objc private func cameraButtonTapped() { + let imagePicker = UIImagePickerController() + imagePicker.delegate = self + imagePicker.sourceType = .photoLibrary + imagePicker.allowsEditing = true + present(imagePicker, animated: true) + } + + private func cropToCircle(image: UIImage) -> UIImage { + let shorterSide = min(image.size.width, image.size.height) + let imageBounds = CGRect(x: 0, y: 0, width: shorterSide, height: shorterSide) + UIGraphicsBeginImageContextWithOptions(imageBounds.size, false, UIScreen.main.scale) + let context = UIGraphicsGetCurrentContext()! + context.addEllipse(in: imageBounds) + context.clip() + image.draw(in: imageBounds) + let circleImage = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + return circleImage + } +} + +extension ProfileSetupViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + if let editedImage = info[.editedImage] as? UIImage ?? info[.originalImage] as? UIImage { + let croppedImage = cropToCircle(image: editedImage) + viewModel.updateProfileImage(croppedImage) + } + dismiss(animated: true) + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + dismiss(animated: true) + } +} diff --git a/KkuMulKum/Source/Onboarding/Profile/View/ProfileView.swift b/KkuMulKum/Source/Onboarding/Profile/View/ProfileView.swift new file mode 100644 index 00000000..84acd483 --- /dev/null +++ b/KkuMulKum/Source/Onboarding/Profile/View/ProfileView.swift @@ -0,0 +1,114 @@ +// +// ProfileVIew.swift +// KkuMulKum +// +// Created by 이지훈 on 7/10/24. +// + +import UIKit + +import SnapKit +import Then + +class ProfileSetupView: BaseView { + + let navigationBar = UIView().then { + $0.backgroundColor = .white + } + + let titleLabel = UILabel().then { + $0.text = "프로필 설정" + $0.font = UIFont.pretendard(.body03) + $0.textAlignment = .center + } + + let separatorLine = UIView().then { + $0.backgroundColor = .gray2 + } + + let subtitleLabel = UILabel().then { + $0.text = "프로필을 설정해 주세요" + $0.font = UIFont.pretendard(.head01) + $0.textColor = .gray8 + $0.textAlignment = .center + } + + let profileImageView = UIImageView().then { + $0.image = UIImage.imgProfile + $0.contentMode = .scaleAspectFill + $0.clipsToBounds = true + $0.layer.cornerRadius = 75 + $0.layer.borderWidth = 1 + $0.layer.borderColor = UIColor.gray3.cgColor + } + + let cameraButton = UIButton().then { + $0.setImage(UIImage.iconCamera, for: .normal) + $0.tintColor = .white + $0.layer.cornerRadius = 15 + } + + let skipButton = UIButton().then { + $0.setTitle("지금은 넘어가기", for: .normal) + $0.setTitleColor(.gray5, for: .normal) + $0.titleLabel?.font = UIFont.pretendard(.body05) + } + + let confirmButton = UIButton().then { + $0.setTitle("확인", for: .normal) + $0.backgroundColor = .maincolor + $0.layer.cornerRadius = 8 + $0.titleLabel?.font = UIFont.pretendard(.body03) + } + + override func setupView() { + backgroundColor = .white + [navigationBar, separatorLine, subtitleLabel, profileImageView, cameraButton, skipButton, confirmButton].forEach { addSubview($0) } + navigationBar.addSubview(titleLabel) + } + + override func setupAutoLayout() { + navigationBar.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(93) + } + + titleLabel.snp.makeConstraints { + $0.centerX.equalTo(navigationBar.snp.centerX) + $0.bottom.equalTo(navigationBar.snp.bottom).offset(-12) + } + + separatorLine.snp.makeConstraints { + $0.top.equalTo(navigationBar.snp.bottom) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(1) + } + + subtitleLabel.snp.makeConstraints { + $0.top.equalTo(separatorLine.snp.bottom).offset(24) + $0.leading.trailing.equalToSuperview().inset(20) + } + + profileImageView.snp.makeConstraints { + $0.top.equalTo(subtitleLabel.snp.bottom).offset(120) + $0.centerX.equalToSuperview() + $0.size.equalTo(150) + } + + cameraButton.snp.makeConstraints { + $0.bottom.trailing.equalTo(profileImageView) + $0.size.equalTo(42) + } + + skipButton.snp.makeConstraints { + $0.bottom.equalTo(confirmButton.snp.top).offset(-20) + $0.centerX.equalToSuperview() + } + + confirmButton.snp.makeConstraints { + $0.bottom.equalTo(safeAreaLayoutGuide).offset(-20) + $0.leading.trailing.equalToSuperview().inset(20) + $0.height.equalTo(50) + } + } +} diff --git a/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileViewModel.swift b/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileViewModel.swift new file mode 100644 index 00000000..4a2f9cc2 --- /dev/null +++ b/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileViewModel.swift @@ -0,0 +1,23 @@ +// +// ProfileViewModel.swift +// KkuMulKum +// +// Created by 이지훈 on 7/10/24. +// + +import UIKit + +class ProfileSetupViewModel { + let profileImage = ObservablePattern(UIImage.imgProfile) + let isConfirmButtonEnabled = ObservablePattern(false) + let nickname: String + + init(nickname: String) { + self.nickname = nickname + } + + func updateProfileImage(_ image: UIImage?) { + profileImage.value = image + isConfirmButtonEnabled.value = image != nil + } +} diff --git a/KkuMulKum/Source/Onboarding/Welcome/View/WelcomeView.swift b/KkuMulKum/Source/Onboarding/Welcome/View/WelcomeView.swift new file mode 100644 index 00000000..d065898d --- /dev/null +++ b/KkuMulKum/Source/Onboarding/Welcome/View/WelcomeView.swift @@ -0,0 +1,58 @@ +// +// WelcomeVIew.swift +// KkuMulKum +// +// Created by 이지훈 on 7/10/24. +// + +import UIKit + +import SnapKit +import Then + +class WelcomeView: BaseView { + let characterImageView = UIImageView().then { + $0.image = UIImage(named: "img_logo") + $0.contentMode = .scaleAspectFit + } + + let welcomeLabel = UILabel().then { + $0.textAlignment = .center + $0.font = .pretendard(.body01) + } + + let confirmButton = UIButton().then { + $0.setTitle("확인", for: .normal) + $0.setTitleColor(.white, for: .normal) + $0.backgroundColor = .maincolor + $0.layer.cornerRadius = 8 + } + + override func setupView() { + addSubviews(characterImageView, welcomeLabel,confirmButton) + + } + + override func setupAutoLayout() { + characterImageView.snp.makeConstraints { + $0.top.equalTo(safeAreaLayoutGuide).offset(200) + $0.centerX.equalToSuperview() + $0.height.width.equalTo(150) + } + + welcomeLabel.snp.makeConstraints { + $0.top.equalTo(characterImageView.snp.bottom).offset(20) + $0.centerX.equalToSuperview() + } + + confirmButton.snp.makeConstraints { + $0.bottom.equalTo(safeAreaLayoutGuide).offset(-20) + $0.leading.trailing.equalToSuperview().inset(20) + $0.height.equalTo(50) + } + } + + func configure(with nickname: String) { + welcomeLabel.text = "\(nickname)님 반가워요!" + } +} diff --git a/KkuMulKum/Source/Onboarding/Welcome/ViewController/WelcomeViewController.swift b/KkuMulKum/Source/Onboarding/Welcome/ViewController/WelcomeViewController.swift new file mode 100644 index 00000000..7a7cdcf6 --- /dev/null +++ b/KkuMulKum/Source/Onboarding/Welcome/ViewController/WelcomeViewController.swift @@ -0,0 +1,53 @@ +// +// WelcomeViewController.swift +// KkuMulKum +// +// Created by 이지훈 on 7/10/24. +// + +import UIKit + +class WelcomeViewController: BaseViewController { + private let welcomeView = WelcomeView() + private let viewModel: WelcomeViewModel + + init(viewModel: WelcomeViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + modalPresentationStyle = .overFullScreen + modalTransitionStyle = .crossDissolve + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = welcomeView + } + + override func viewDidLoad() { + super.viewDidLoad() + setupView() + setupActions() + updateWelcomeLabel() + } + + override func setupView() { + view.backgroundColor = .green2 + welcomeView.backgroundColor = .green1 + } + + private func setupActions() { + welcomeView.confirmButton.addTarget(self, action: #selector(confirmButtonTapped), for: .touchUpInside) + } + + private func updateWelcomeLabel() { + welcomeView.welcomeLabel.text = "\(viewModel.nickname.value)님 반가워요!" + } + + @objc private func confirmButtonTapped() { + // TODO: main화면으로 넘기기 + print("Confirm button tapped") + } +} diff --git a/KkuMulKum/Source/Onboarding/Welcome/ViewModel/WelcomeViewModel.swift b/KkuMulKum/Source/Onboarding/Welcome/ViewModel/WelcomeViewModel.swift new file mode 100644 index 00000000..25e13b59 --- /dev/null +++ b/KkuMulKum/Source/Onboarding/Welcome/ViewModel/WelcomeViewModel.swift @@ -0,0 +1,16 @@ +// +// WelcomeVIewModel.swift +// KkuMulKum +// +// Created by 이지훈 on 7/10/24. +// + +import Foundation + +class WelcomeViewModel { + let nickname: ObservablePattern + + init(nickname: String) { + self.nickname = ObservablePattern(nickname) + } +}