diff --git a/DailyQuest/DailyQuest.xcodeproj/project.pbxproj b/DailyQuest/DailyQuest.xcodeproj/project.pbxproj index 11eeac0..0b8a740 100644 --- a/DailyQuest/DailyQuest.xcodeproj/project.pbxproj +++ b/DailyQuest/DailyQuest.xcodeproj/project.pbxproj @@ -46,6 +46,9 @@ 34283103292E2D9B00AE811B /* ToggleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34283102292E2D9B00AE811B /* ToggleCell.swift */; }; 34283105292E2E3F00AE811B /* ToggleField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34283104292E2E3F00AE811B /* ToggleField.swift */; }; 3429084F29383D73001812B1 /* UserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3429084E29383D73001812B1 /* UserRepository.swift */; }; + 34404D92293EF75F0007E661 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34404D91293EF75F0007E661 /* SettingsViewModel.swift */; }; + 34404D94293EF9850007E661 /* SettingsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34404D93293EF9850007E661 /* SettingsUseCase.swift */; }; + 34404D96293EF9E50007E661 /* DefaultSettingsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34404D95293EF9E50007E661 /* DefaultSettingsUseCase.swift */; }; 3449AD5B2922164B00B87619 /* Quest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3449AD5A2922164B00B87619 /* Quest.swift */; }; 3449AD5D2922197000B87619 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3449AD5C2922197000B87619 /* User.swift */; }; 3449AD6029222B3900B87619 /* UserInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3449AD5F29222B3900B87619 /* UserInfoCell.swift */; }; @@ -105,9 +108,9 @@ 34EE0C662935FD7D002BEC23 /* BrowseItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3499552629235D1E007AB99E /* BrowseItemViewModel.swift */; }; 34EE6EB72924C674005AF583 /* QuestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34EE6EB62924C674005AF583 /* QuestView.swift */; }; 34EE6EB92924CAA1005AF583 /* QuestViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34EE6EB82924CAA1005AF583 /* QuestViewModel.swift */; }; + 34FCD366293DE62700E0DC8A /* DefaultEnrollUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34FCD365293DE62700E0DC8A /* DefaultEnrollUseCase.swift */; }; 34FCD369293DEED600E0DC8A /* FriendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34FCD368293DEED600E0DC8A /* FriendViewController.swift */; }; 34FCD36B293DF2F600E0DC8A /* FriendStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34FCD36A293DF2F600E0DC8A /* FriendStatusView.swift */; }; - 34FCD366293DE62700E0DC8A /* DefaultEnrollUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34FCD365293DE62700E0DC8A /* DefaultEnrollUseCase.swift */; }; 34FEFB992935EA6D00954A40 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 34FEFB982935EA6D00954A40 /* Kingfisher */; }; 34FF6C5A292B86F8002AFD4D /* SnapKit-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 34FF6C46292B8014002AFD4D /* SnapKit-Dynamic */; }; 34FF6C5D292B8B27002AFD4D /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 34FF6C5C292B8B27002AFD4D /* RxCocoa */; }; @@ -121,6 +124,10 @@ 9BD8CCFB2937407100E6EA2F /* FollowingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD8CCFA2937407100E6EA2F /* FollowingCell.swift */; }; 9BD8CD00293829DD00E6EA2F /* FollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD8CCFF293829DD00E6EA2F /* FollowingView.swift */; }; 9BD8CD022938418500E6EA2F /* LastFollowingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD8CD012938418500E6EA2F /* LastFollowingCell.swift */; }; + A5003AB4293F5FEC00082A9C /* SignUpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5003AB3293F5FEC00082A9C /* SignUpViewModel.swift */; }; + A5003AB6293F601E00082A9C /* SignUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5003AB5293F601E00082A9C /* SignUpViewController.swift */; }; + A5003AB9293F909300082A9C /* UserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5003AB8293F909300082A9C /* UserUseCase.swift */; }; + A5003ABB293F914500082A9C /* DefaultUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5003ABA293F914500082A9C /* DefaultUserUseCase.swift */; }; A50DE906292B53D900E1FD60 /* DefaultQuestsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50DE905292B53D900E1FD60 /* DefaultQuestsRepository.swift */; }; A50DE90B292B73B900E1FD60 /* FirebaseDatabaseSwift in Frameworks */ = {isa = PBXBuildFile; productRef = A50DE90A292B73B900E1FD60 /* FirebaseDatabaseSwift */; }; A50DE911292B74C500E1FD60 /* DTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50DE910292B74C500E1FD60 /* DTO.swift */; }; @@ -149,7 +156,6 @@ A5AC96E629223F06003B7637 /* QuestsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AC96E529223F06003B7637 /* QuestsStorage.swift */; }; A5AC96E829223F27003B7637 /* RealmQuestsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AC96E729223F27003B7637 /* RealmQuestsStorage.swift */; }; A5D3C813293DB1BE00F43B76 /* DataConfigure.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D3C812293DB1BE00F43B76 /* DataConfigure.swift */; }; - A5D3C820293DD52B00F43B76 /* RepositoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D3C81F293DD52B00F43B76 /* RepositoryManager.swift */; }; A5D3C823293DDDAD00F43B76 /* ProtectedUserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D3C822293DDDAD00F43B76 /* ProtectedUserRepository.swift */; }; A5D3C828293DDEBE00F43B76 /* QuestsRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D3C827293DDEBE00F43B76 /* QuestsRepositoryTests.swift */; }; A5D3C829293DDF8A00F43B76 /* DataConfigure.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D3C812293DB1BE00F43B76 /* DataConfigure.swift */; }; @@ -157,7 +163,6 @@ A5D3C82B293DDF9000F43B76 /* QuestDTO+Mapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1CFB3E292B585700CCE97A /* QuestDTO+Mapping.swift */; }; A5D3C82C293DDF9200F43B76 /* DTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50DE910292B74C500E1FD60 /* DTO.swift */; }; A5D3C82D293DDF9400F43B76 /* BrowseQuestDTO+Mapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD8CCF62935D7BB00E6EA2F /* BrowseQuestDTO+Mapping.swift */; }; - A5D3C82F293DDF9800F43B76 /* RepositoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D3C81F293DD52B00F43B76 /* RepositoryManager.swift */; }; A5D3C830293DDF9B00F43B76 /* DefaultQuestsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50DE905292B53D900E1FD60 /* DefaultQuestsRepository.swift */; }; A5D3C831293DDF9D00F43B76 /* DefaultAuthRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A517A84529349D91005CB1E8 /* DefaultAuthRepository.swift */; }; A5D3C832293DDFA000F43B76 /* DefaultUserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A511228F29384FAF00384B4B /* DefaultUserRepository.swift */; }; @@ -253,6 +258,9 @@ 34283102292E2D9B00AE811B /* ToggleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleCell.swift; sourceTree = ""; }; 34283104292E2E3F00AE811B /* ToggleField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleField.swift; sourceTree = ""; }; 3429084E29383D73001812B1 /* UserRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRepository.swift; sourceTree = ""; }; + 34404D91293EF75F0007E661 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; + 34404D93293EF9850007E661 /* SettingsUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsUseCase.swift; sourceTree = ""; }; + 34404D95293EF9E50007E661 /* DefaultSettingsUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultSettingsUseCase.swift; sourceTree = ""; }; 3449AD5A2922164B00B87619 /* Quest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quest.swift; sourceTree = ""; }; 3449AD5C2922197000B87619 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 3449AD5F29222B3900B87619 /* UserInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoCell.swift; sourceTree = ""; }; @@ -307,9 +315,9 @@ 34EE0C632935FD6B002BEC23 /* BrowseViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowseViewModelTests.swift; sourceTree = ""; }; 34EE6EB62924C674005AF583 /* QuestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestView.swift; sourceTree = ""; }; 34EE6EB82924CAA1005AF583 /* QuestViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestViewModel.swift; sourceTree = ""; }; + 34FCD365293DE62700E0DC8A /* DefaultEnrollUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultEnrollUseCase.swift; sourceTree = ""; }; 34FCD368293DEED600E0DC8A /* FriendViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendViewController.swift; sourceTree = ""; }; 34FCD36A293DF2F600E0DC8A /* FriendStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendStatusView.swift; sourceTree = ""; }; - 34FCD365293DE62700E0DC8A /* DefaultEnrollUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultEnrollUseCase.swift; sourceTree = ""; }; 9B1CFB3E292B585700CCE97A /* QuestDTO+Mapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuestDTO+Mapping.swift"; sourceTree = ""; }; 9BD8CCF22935BC0D00E6EA2F /* DefaultBrowseRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultBrowseRepository.swift; sourceTree = ""; }; 9BD8CCF42935C38300E6EA2F /* UserDTO+Mapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDTO+Mapping.swift"; sourceTree = ""; }; @@ -317,6 +325,10 @@ 9BD8CCFA2937407100E6EA2F /* FollowingCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingCell.swift; sourceTree = ""; }; 9BD8CCFF293829DD00E6EA2F /* FollowingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingView.swift; sourceTree = ""; }; 9BD8CD012938418500E6EA2F /* LastFollowingCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastFollowingCell.swift; sourceTree = ""; }; + A5003AB3293F5FEC00082A9C /* SignUpViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewModel.swift; sourceTree = ""; }; + A5003AB5293F601E00082A9C /* SignUpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewController.swift; sourceTree = ""; }; + A5003AB8293F909300082A9C /* UserUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserUseCase.swift; sourceTree = ""; }; + A5003ABA293F914500082A9C /* DefaultUserUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultUserUseCase.swift; sourceTree = ""; }; A50DE905292B53D900E1FD60 /* DefaultQuestsRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultQuestsRepository.swift; sourceTree = ""; }; A50DE910292B74C500E1FD60 /* DTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DTO.swift; sourceTree = ""; }; A50F9A3329266F45005C00FE /* NetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = ""; }; @@ -343,7 +355,6 @@ A5AC96E529223F06003B7637 /* QuestsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestsStorage.swift; sourceTree = ""; }; A5AC96E729223F27003B7637 /* RealmQuestsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmQuestsStorage.swift; sourceTree = ""; }; A5D3C812293DB1BE00F43B76 /* DataConfigure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataConfigure.swift; sourceTree = ""; }; - A5D3C81F293DD52B00F43B76 /* RepositoryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryManager.swift; sourceTree = ""; }; A5D3C822293DDDAD00F43B76 /* ProtectedUserRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtectedUserRepository.swift; sourceTree = ""; }; A5D3C827293DDEBE00F43B76 /* QuestsRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestsRepositoryTests.swift; sourceTree = ""; }; B50078D529222F3F0070AFC4 /* CircleCheckView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleCheckView.swift; sourceTree = ""; }; @@ -426,6 +437,8 @@ isa = PBXGroup; children = ( 34113BE72934917500AB4919 /* LoginViewModel.swift */, + A5003AB3293F5FEC00082A9C /* SignUpViewModel.swift */, + 34404D91293EF75F0007E661 /* SettingsViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -436,6 +449,7 @@ 3416FC86292B54BF00B504C5 /* Protocols */, 3416FC89292B560800B504C5 /* DefaultQuestUseCase.swift */, 34FCD365293DE62700E0DC8A /* DefaultEnrollUseCase.swift */, + A5003ABA293F914500082A9C /* DefaultUserUseCase.swift */, ); path = Home; sourceTree = ""; @@ -445,6 +459,7 @@ children = ( 3416FC87292B54DB00B504C5 /* QuestUseCase.swift */, 344A4599293DC495007A3D37 /* EnrollUseCase.swift */, + A5003AB8293F909300082A9C /* UserUseCase.swift */, ); path = Protocols; sourceTree = ""; @@ -500,6 +515,7 @@ children = ( 3417B1422935DA2E00900454 /* Protocols */, 340A724A29348C2200B26AA6 /* DefaultAuthUseCase.swift */, + 34404D95293EF9E50007E661 /* DefaultSettingsUseCase.swift */, ); path = Settings; sourceTree = ""; @@ -516,6 +532,7 @@ isa = PBXGroup; children = ( 340A724829348B1B00B26AA6 /* AuthUseCase.swift */, + 34404D93293EF9850007E661 /* SettingsUseCase.swift */, ); path = Protocols; sourceTree = ""; @@ -525,6 +542,7 @@ children = ( 342830A9292E12C700AE811B /* SettingsViewController.swift */, 34113BEA2934A3B200AB4919 /* LoginViewController.swift */, + A5003AB5293F601E00082A9C /* SignUpViewController.swift */, ); path = ViewController; sourceTree = ""; @@ -1013,7 +1031,6 @@ A5AC96D42922356E003B7637 /* Repositories */ = { isa = PBXGroup; children = ( - A5D3C81F293DD52B00F43B76 /* RepositoryManager.swift */, A50DE905292B53D900E1FD60 /* DefaultQuestsRepository.swift */, A517A84529349D91005CB1E8 /* DefaultAuthRepository.swift */, A511228F29384FAF00384B4B /* DefaultUserRepository.swift */, @@ -1231,6 +1248,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 34404D94293EF9850007E661 /* SettingsUseCase.swift in Sources */, A5A8C8362936EC7E00988C88 /* SubQuestEntity.swift in Sources */, 34EC71D1292DF27E004813CB /* Date+.swift in Sources */, 34D8360129359C8A001DE9DF /* BrowseRepository.swift in Sources */, @@ -1251,6 +1269,7 @@ A5A8C8382936ED0F00988C88 /* SubQuestEntity+Mapping.swift in Sources */, 34874AA229250C43000570DF /* UIButton+.swift in Sources */, A50F9A3F292679BC005C00FE /* NetworkConfigure.swift in Sources */, + 34404D92293EF75F0007E661 /* SettingsViewModel.swift in Sources */, 34A529E7292481E1001BAD34 /* BrowseCoordinator.swift in Sources */, B5115453292CD07100FDBD22 /* CalendarViewModel.swift in Sources */, 34A529D329247903001BAD34 /* TabCoordinator.swift in Sources */, @@ -1263,6 +1282,7 @@ 34CAE318292B19A3007653AD /* QuestsRepository.swift in Sources */, A50DE911292B74C500E1FD60 /* DTO.swift in Sources */, 3449AD5D2922197000B87619 /* User.swift in Sources */, + A5003AB6293F601E00082A9C /* SignUpViewController.swift in Sources */, 9BD8CCF52935C38300E6EA2F /* UserDTO+Mapping.swift in Sources */, A51F01CD29233ABB0031ECA2 /* RealmUserInfoStorage.swift in Sources */, B5833F732924C08900503E0D /* CalendarView.swift in Sources */, @@ -1275,11 +1295,11 @@ 342830FD292E2AF200AE811B /* NavigateField.swift in Sources */, 342830F2292E196E00AE811B /* CommonField.swift in Sources */, 342830A8292E0FAC00AE811B /* SettingsCoordinator.swift in Sources */, + A5003AB9293F909300082A9C /* UserUseCase.swift in Sources */, 342830FB292E2A5F00AE811B /* NavigateItemViewModel.swift in Sources */, A51F01C82923392F0031ECA2 /* UserInfoStorage.swift in Sources */, 34A529E429248178001BAD34 /* SettingsSceneDIContainer.swift in Sources */, 340A7246293455CE00B26AA6 /* AuthRepository.swift in Sources */, - A5D3C820293DD52B00F43B76 /* RepositoryManager.swift in Sources */, 345687FA2937815900CA51E3 /* QuantityView.swift in Sources */, 342830AA292E12C700AE811B /* SettingsViewController.swift in Sources */, 34A529E029247F1F001BAD34 /* HomeViewController.swift in Sources */, @@ -1302,6 +1322,7 @@ A50DE906292B53D900E1FD60 /* DefaultQuestsRepository.swift in Sources */, 34ACC32D291DE9C000741371 /* AppDelegate.swift in Sources */, A5AC96D929223648003B7637 /* RealmStorage.swift in Sources */, + 34404D96293EF9E50007E661 /* DefaultSettingsUseCase.swift in Sources */, 3417B1442935DA4A00900454 /* BrowseUseCase.swift in Sources */, A51189C329226E66008A9D33 /* QuestEntity+Mapping.swift in Sources */, A51F01D3292340360031ECA2 /* BrowseQuestsStorage.swift in Sources */, @@ -1313,6 +1334,7 @@ A51F01CA2923397E0031ECA2 /* UserInfoEntity.swift in Sources */, 345687F42937329E00CA51E3 /* EnrollViewController.swift in Sources */, A511229029384FAF00384B4B /* DefaultUserRepository.swift in Sources */, + A5003ABB293F914500082A9C /* DefaultUserUseCase.swift in Sources */, 342830FF292E2B2A00AE811B /* NavigateCell.swift in Sources */, A51F01D8292343A80031ECA2 /* RealmBrowseQuestsStorage.swift in Sources */, A51F01DD2923468F0031ECA2 /* BrowseQuestEntity+Mapping.swift in Sources */, @@ -1328,6 +1350,7 @@ 34283101292E2D7A00AE811B /* ToggleItemViewModel.swift in Sources */, 34FCD366293DE62700E0DC8A /* DefaultEnrollUseCase.swift in Sources */, A5AC96E829223F27003B7637 /* RealmQuestsStorage.swift in Sources */, + A5003AB4293F5FEC00082A9C /* SignUpViewModel.swift in Sources */, 345687F829374D2500CA51E3 /* DayNamePickerView.swift in Sources */, 349955122923220E007AB99E /* SwiftUIPreview.swift in Sources */, 9BD8CD022938418500E6EA2F /* LastFollowingCell.swift in Sources */, @@ -1342,7 +1365,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A5D3C82F293DDF9800F43B76 /* RepositoryManager.swift in Sources */, A5D3C83C293DDFC400F43B76 /* SubQuestEntity+Mapping.swift in Sources */, A5D3C83D293DDFCC00F43B76 /* QuestEntity.swift in Sources */, A5656452292BB89A0033E763 /* QuestsStorage.swift in Sources */, diff --git a/DailyQuest/DailyQuest/Application/DIContainer/SettingsSceneDIContainer.swift b/DailyQuest/DailyQuest/Application/DIContainer/SettingsSceneDIContainer.swift index a61a0e6..1fe966d 100644 --- a/DailyQuest/DailyQuest/Application/DIContainer/SettingsSceneDIContainer.swift +++ b/DailyQuest/DailyQuest/Application/DIContainer/SettingsSceneDIContainer.swift @@ -8,27 +8,54 @@ import UIKit final class SettingsSceneDIContainer { - + + lazy var userInfoStorage: UserInfoStorage = RealmUserInfoStorage() + // MARK: - Repositories func makeAuthRepository() -> AuthRepository { return DefaultAuthRepository() } - + + func makeUserRepository() -> UserRepository { + return DefaultUserRepository(persistentStorage: userInfoStorage) + } + // MARK: - Use Cases func makeAuthUseCase() -> AuthUseCase { return DefaultAuthUseCase(authRepository: makeAuthRepository()) } - + + func makeSettingsUseCase() -> SettingsUseCase { + return DefaultSettingsUseCase(userRepository: makeUserRepository(), + authRepository: makeAuthRepository()) + } + // MARK: - View Models func makeLoginViewModel() -> LoginViewModel { return LoginViewModel(authUseCase: makeAuthUseCase()) } + + func makeSignUpViewModel() -> SignUpViewModel { + return SignUpViewModel(authUseCase: makeAuthUseCase()) + } + func makeSettingsViewModel() -> SettingsViewModel { + return SettingsViewModel(settingsUseCase: makeSettingsUseCase()) + } + // MARK: - View Controller func makeLoginViewController() -> LoginViewController { return LoginViewController.create(with: makeLoginViewModel()) } + + func makeSignUpViewController() -> SignUpViewController { + return SignUpViewController.create(with: makeSignUpViewModel()) + } + func makeSettingsViewController() -> SettingsViewController { + return SettingsViewController.create(with: makeSettingsViewModel()) + } + // MARK: - Flow func makeSettingsCoordinator(navigationController: UINavigationController, settingsSceneDIContainer: SettingsSceneDIContainer) -> SettingsCoordinator { diff --git a/DailyQuest/DailyQuest/Assets.xcassets/AccentColor.colorset/Contents.json b/DailyQuest/DailyQuest/Assets.xcassets/AccentColor.colorset/Contents.json index eb87897..641de21 100644 --- a/DailyQuest/DailyQuest/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/DailyQuest/DailyQuest/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,6 +1,15 @@ { "colors" : [ { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.490", + "green" : "0.871", + "red" : "1.000" + } + }, "idiom" : "universal" } ], diff --git a/DailyQuest/DailyQuest/Data/Network/DataMapping/UserDTO+Mapping.swift b/DailyQuest/DailyQuest/Data/Network/DataMapping/UserDTO+Mapping.swift index db2f680..7adf2f9 100644 --- a/DailyQuest/DailyQuest/Data/Network/DataMapping/UserDTO+Mapping.swift +++ b/DailyQuest/DailyQuest/Data/Network/DataMapping/UserDTO+Mapping.swift @@ -32,6 +32,15 @@ struct UserDTO: DTO { self.description = user.description self.allow = user.allow } + + init(uuid: String, userDto: UserDTO) { + self.uuid = uuid + self.nickName = userDto.nickName + self.profileURL = userDto.profileURL + self.backgroundImageURL = userDto.backgroundImageURL + self.description = userDto.description + self.allow = userDto.allow + } } extension UserDTO { diff --git a/DailyQuest/DailyQuest/Data/PersistentStorages/RealmStorage/RealmStorage.swift b/DailyQuest/DailyQuest/Data/PersistentStorages/RealmStorage/RealmStorage.swift index b80ce8a..9b0d0ad 100644 --- a/DailyQuest/DailyQuest/Data/PersistentStorages/RealmStorage/RealmStorage.swift +++ b/DailyQuest/DailyQuest/Data/PersistentStorages/RealmStorage/RealmStorage.swift @@ -77,6 +77,7 @@ final class RealmStorage { return entity } + @discardableResult func deleteAllEntity(type: O.Type) throws -> [O] { guard let persistentContainer = persistentContainer else { print(#function) diff --git a/DailyQuest/DailyQuest/Data/Repositories/DefaultAuthRepository.swift b/DailyQuest/DailyQuest/Data/Repositories/DefaultAuthRepository.swift index a3cc947..03b2d45 100644 --- a/DailyQuest/DailyQuest/Data/Repositories/DefaultAuthRepository.swift +++ b/DailyQuest/DailyQuest/Data/Repositories/DefaultAuthRepository.swift @@ -25,4 +25,8 @@ extension DefaultAuthRepository: AuthRepository { func signOut() -> Single { return self.networkService.signOut() } + + func signUp(email: String, password: String, user: User) -> Single { + return self.networkService.signUp(email: email, password: password, userDto: user.toDTO()) + } } diff --git a/DailyQuest/DailyQuest/Data/Repositories/DefaultBrowseRepository.swift b/DailyQuest/DailyQuest/Data/Repositories/DefaultBrowseRepository.swift index 9d79af9..7d2568a 100644 --- a/DailyQuest/DailyQuest/Data/Repositories/DefaultBrowseRepository.swift +++ b/DailyQuest/DailyQuest/Data/Repositories/DefaultBrowseRepository.swift @@ -12,15 +12,12 @@ final class DefaultBrowseRepository { private let persistentStorage: BrowseQuestsStorage private let networkService: NetworkService - - private let disposeBag: DisposeBag + private let disposeBag: DisposeBag = DisposeBag() init(persistentStorage: BrowseQuestsStorage, - networkService: NetworkService = FirebaseService.shared, - repositoryManager: RepositoryManager = RepositoryManager.shared) { + networkService: NetworkService = FirebaseService.shared) { self.persistentStorage = persistentStorage self.networkService = networkService - self.disposeBag = repositoryManager.disposeBag } } @@ -32,36 +29,39 @@ extension DefaultBrowseRepository: BrowseRepository { func fetch() -> Observable<[BrowseQuest]> { return self.networkService.getAllowUsers(limit: 10) .map { $0.toDomain() } - .flatMap { user in - self.networkService - .read(type: QuestDTO.self, userCase: .anotherUser(user.uuid), access: .quests, filter: .today(Date())) - .map { $0.toDomain() } - .toArray() - .asObservable() - .map { questList in - return BrowseQuest(user: user, quests: questList) - } - } - + .flatMap(fetchBrowseQuestNetworkService(user:)) .filter { !$0.quests.isEmpty } .toArray() .asObservable() - .do(afterNext: { browseQuests in + .do(afterNext: { [weak self] browseQuests in + guard let self = self else { return } self.persistentStorage.deleteBrowseQuests() .asObservable() .concatMap { _ in Observable.from(browseQuests) - .flatMap { browseQuest in - self.persistentStorage.saveBrowseQuest(browseQuest: browseQuest) - .asObservable() - } + .flatMap (self.saveBrowseQuestPersistentStorage(browseQuest:)) } - .subscribe(onError: { error in - print(error) - }).disposed(by: self.disposeBag) + .subscribe() + .disposed(by: self.disposeBag) }) .catch { error in return self.persistentStorage.fetchBrowseQuests() } } } + +private extension DefaultBrowseRepository { + func fetchBrowseQuestNetworkService(user: User) -> Observable { + networkService + .read(type: QuestDTO.self, userCase: .anotherUser(user.uuid), access: .quests, filter: .today(Date())) + .map { $0.toDomain() } + .toArray() + .asObservable() + .map { return BrowseQuest(user: user, quests: $0) } + } + + func saveBrowseQuestPersistentStorage(browseQuest: BrowseQuest) -> Observable { + persistentStorage.saveBrowseQuest(browseQuest: browseQuest) + .asObservable() + } +} diff --git a/DailyQuest/DailyQuest/Data/Repositories/DefaultQuestsRepository.swift b/DailyQuest/DailyQuest/Data/Repositories/DefaultQuestsRepository.swift index ecfb85b..6b82bf5 100644 --- a/DailyQuest/DailyQuest/Data/Repositories/DefaultQuestsRepository.swift +++ b/DailyQuest/DailyQuest/Data/Repositories/DefaultQuestsRepository.swift @@ -9,10 +9,10 @@ import RxSwift import Foundation final class DefaultQuestsRepository { - + private let persistentStorage: QuestsStorage private let networkService: NetworkService - + init(persistentStorage: QuestsStorage, networkService: NetworkService = FirebaseService.shared) { self.persistentStorage = persistentStorage self.networkService = networkService @@ -22,74 +22,86 @@ final class DefaultQuestsRepository { extension DefaultQuestsRepository: QuestsRepository { func save(with quest: [Quest]) -> Single<[Quest]> { return persistentStorage.saveQuests(with: quest) - .flatMap { quests in - return Observable.from(quests) - .withUnretained(self) - .concatMap { (owner, quest) in - return owner.networkService.create(userCase: .currentUser, - access: .quests, - dto: quest.toDTO()) - .map { $0.toDomain() } - .catchAndReturn(quest) - } - .toArray() + .flatMap (saveNetworkService(quests:)) + .do{ event in + print("save", event) } } - + func fetch(by date: Date) -> Observable<[Quest]> { return persistentStorage.fetchQuests(by: date) - .catch { _ in - self.networkService.read(type: QuestDTO.self, userCase: .currentUser, access: .quests, filter: .today(date)) - .map { $0.toDomain() } - .toArray() - .asObservable() - } + .catch { [weak self] _ in + guard let self = self else { return Observable.just([]) } + return self.fetchNetworkService(date: date) + } + .catchAndReturn([]) } - + func update(with quest: Quest) -> Single { return persistentStorage.updateQuest(with: quest) .flatMap(updateNetworkService(quest:)) } - + func delete(with questId: UUID) -> Single { return persistentStorage.deleteQuest(with: questId) - .flatMap { quest in - self.networkService.delete(userCase: .currentUser, access: .quests, dto: quest.toDTO()) - } - .map { $0.toDomain() } - .asObservable() - .asSingle() + .flatMap(deleteNetworkService(quest:)) } - + func deleteAll(with groupId: UUID) -> Single<[Quest]> { return persistentStorage.deleteQuestGroup(with: groupId) - .flatMap { quests in - return Observable.from(quests) - .concatMap { quest in - self.networkService.delete(userCase: .currentUser, - access: .quests, - dto: quest.toDTO()) - } - .map { $0.toDomain() } - .toArray() - } + .flatMap(deleteAllNetworkService(quests:)) } - - func fetch(by uuid: String, date: Date) -> Observable<[Quest]> { // 받을 날짜까지 받아와야함 - return self.networkService.read(type: QuestDTO.self, - userCase: .anotherUser(uuid), - access: .quests, - filter: .today(date)) - .map { $0.toDomain() } - .toArray() - .asObservable() + + func fetch(by uuid: String, date: Date) -> Observable<[Quest]> { + return networkService.read(type: QuestDTO.self, + userCase: .anotherUser(uuid), + access: .quests, + filter: .today(date)) + .map { $0.toDomain() } + .toArray() + .asObservable() } } private extension DefaultQuestsRepository { + func saveNetworkService(quests: [Quest]) -> Single<[Quest]> { + return Observable.from(quests) + .withUnretained(self) + .concatMap { (owner, quest) in + return owner.networkService.create(userCase: .currentUser, + access: .quests, + dto: quest.toDTO()) + .map { $0.toDomain() } + .catchAndReturn(quest) + } + .toArray() + } + + func fetchNetworkService(date: Date) -> Observable<[Quest]> { + return networkService.read(type: QuestDTO.self, userCase: .currentUser, access: .quests, filter: .today(date)) + .map { $0.toDomain() } + .toArray() + .asObservable() + .catchAndReturn([]) + } + func updateNetworkService(quest: Quest) -> Single { return networkService.update(userCase: .currentUser, access: .quests, dto: quest.toDTO()) .map { $0.toDomain() } .catchAndReturn(quest) } + + func deleteNetworkService(quest: Quest) -> Single { + self.networkService.delete(userCase: .currentUser, access: .quests, dto: quest.toDTO()) + .map { $0.toDomain() } + .catchAndReturn(quest) + } + + func deleteAllNetworkService(quests: [Quest]) -> Single<[Quest]> { + Observable.from(quests) + .concatMap(deleteNetworkService(quest:)) + .toArray() + .catchAndReturn(quests) + } + } diff --git a/DailyQuest/DailyQuest/Data/Repositories/DefaultUserRepository.swift b/DailyQuest/DailyQuest/Data/Repositories/DefaultUserRepository.swift index 71994d1..141a444 100644 --- a/DailyQuest/DailyQuest/Data/Repositories/DefaultUserRepository.swift +++ b/DailyQuest/DailyQuest/Data/Repositories/DefaultUserRepository.swift @@ -13,7 +13,7 @@ final class DefaultUserRepository { private let persistentStorage: UserInfoStorage private let networkService: NetworkService - init(persistentStorage: UserInfoStorage, networkService: NetworkService) { + init(persistentStorage: UserInfoStorage, networkService: NetworkService = FirebaseService.shared) { self.persistentStorage = persistentStorage self.networkService = networkService } @@ -21,44 +21,59 @@ final class DefaultUserRepository { extension DefaultUserRepository: UserRepository { func isLoggedIn() -> BehaviorRelay { - return self.networkService.uid + return networkService.uid } func readUser() -> Observable { return self.persistentStorage.fetchUserInfo() - .catch { _ in - return self.networkService.read(type: UserDTO.self, userCase: .currentUser, access: .userInfo, filter: nil) - .map { $0.toDomain() } + .catch { [weak self] _ in + guard let self = self else { return Observable.just(User()) } + return self.fetchUserNetworkService() } } func updateUser(by user: User) -> Observable { - return self.persistentStorage.updateUserInfo(user: user) + return persistentStorage.updateUserInfo(user: user) .asObservable() - .concatMap { _ in - return self.networkService.update(userCase: .currentUser, access: .userInfo, dto: user.toDTO()) - .map { $0.toDomain() } - .asObservable() - } + .flatMap(updateUserNetworkService(user:)) } func fetchUser(by uuid: String) -> Observable { - return self.networkService.read(type: UserDTO.self, - userCase: .anotherUser(uuid), - access: .userInfo, - filter: nil) + return networkService.read(type: UserDTO.self, + userCase: .anotherUser(uuid), + access: .userInfo, + filter: nil) .map { $0.toDomain() } } } extension DefaultUserRepository: ProtectedUserRepository { func deleteUser() -> Observable { - return self.persistentStorage.deleteUserInfo() + // return self.persistentStorage.deleteUserInfo() + // .map { _ in true } + // .asObservable() + // .concatMap { _ in + // return self.networkService.delete(userCase: .currentUser, access: .userInfo, dto: UserDTO()) + // .map { _ in true } + // } + return networkService.delete(userCase: .currentUser, access: .userInfo, dto: UserDTO()) .map { _ in true } + .catchAndReturn(false) .asObservable() - .concatMap { _ in - return self.networkService.delete(userCase: .currentUser, access: .userInfo, dto: UserDTO()) - .map { _ in true } - } } } + +private extension DefaultUserRepository { + func fetchUserNetworkService() -> Observable { + networkService.read(type: UserDTO.self, userCase: .currentUser, access: .userInfo, filter: nil) + .map { $0.toDomain() } + } + + func updateUserNetworkService(user: User) -> Observable { + networkService.update(userCase: .currentUser, access: .userInfo, dto: user.toDTO()) + .map { $0.toDomain() } + .asObservable() + .catchAndReturn(user) + } +} + diff --git a/DailyQuest/DailyQuest/Domain/Entities/User.swift b/DailyQuest/DailyQuest/Domain/Entities/User.swift index 68763cf..32ee39d 100644 --- a/DailyQuest/DailyQuest/Domain/Entities/User.swift +++ b/DailyQuest/DailyQuest/Domain/Entities/User.swift @@ -14,4 +14,33 @@ struct User { let backgroundImageURL: String let description: String let allow: Bool + + init(){ + self.uuid = "" + self.nickName = "" + self.profileURL = "" + self.backgroundImageURL = "" + self.description = "" + self.allow = false + } + + init(nickName: String){ + self.uuid = "" + self.nickName = nickName + self.profileURL = "" + self.backgroundImageURL = "" + self.description = "" + self.allow = false + } + + init(uuid: String, nickName: String, profileURL: String, backgroundImageURL: String, description: String, allow: Bool) { + self.uuid = uuid + self.nickName = nickName + self.profileURL = profileURL + self.backgroundImageURL = backgroundImageURL + self.description = description + self.allow = allow + } + + } diff --git a/DailyQuest/DailyQuest/Domain/Interfaces/Repositories/AuthRepository.swift b/DailyQuest/DailyQuest/Domain/Interfaces/Repositories/AuthRepository.swift index 75b6b65..71640aa 100644 --- a/DailyQuest/DailyQuest/Domain/Interfaces/Repositories/AuthRepository.swift +++ b/DailyQuest/DailyQuest/Domain/Interfaces/Repositories/AuthRepository.swift @@ -19,4 +19,12 @@ protocol AuthRepository { /// 로그아웃 동작을 수행합니다. /// - Returns: 성공하면 true를, 실패하면 error을 방출하는 Observable을 반환합니다. func signOut() -> Single + + /// 회원가입 동작을 수행합니다. + /// - Parameters: + /// - email: 사용자의 email 입니다. + /// - password: 사용자의 password입니다. + /// - user: 사용자의 정보입니다. + /// - Returns: 성공하면 true를, 실패하면 error를 방출하는 Observable을 반환합니다. + func signUp(email: String, password: String, user: User) -> Single } diff --git a/DailyQuest/DailyQuest/Domain/UseCases/Home/DefaultUserUseCase.swift b/DailyQuest/DailyQuest/Domain/UseCases/Home/DefaultUserUseCase.swift new file mode 100644 index 0000000..5b6d85e --- /dev/null +++ b/DailyQuest/DailyQuest/Domain/UseCases/Home/DefaultUserUseCase.swift @@ -0,0 +1,33 @@ +// +// DefaultUserUseCase.swift +// DailyQuest +// +// Created by 이전희 on 2022/12/07. +// + +import RxSwift + +final class DefaultUserUseCase { + private let userRepository: UserRepository + + init(userRepository: UserRepository) { + self.userRepository = userRepository + } +} + +extension DefaultUserUseCase: UserUseCase { + func fetch() -> Observable { + return userRepository.readUser() + } + + func save(with user: User) -> Observable { + return userRepository.updateUser(by: user) + } + + func delete() -> Observable { + guard let userRepository = userRepository as? ProtectedUserRepository else { return Observable.just(false) } + return userRepository.deleteUser() + .catchAndReturn(false) + } +} + diff --git a/DailyQuest/DailyQuest/Domain/UseCases/Home/Protocols/UserUseCase.swift b/DailyQuest/DailyQuest/Domain/UseCases/Home/Protocols/UserUseCase.swift new file mode 100644 index 0000000..36f91c3 --- /dev/null +++ b/DailyQuest/DailyQuest/Domain/UseCases/Home/Protocols/UserUseCase.swift @@ -0,0 +1,15 @@ +// +// UserUseCase.swift +// DailyQuest +// +// Created by 이전희 on 2022/12/07. +// + +import RxSwift + +protocol UserUseCase { + func fetch() -> Observable + func save(with user: User) -> Observable + + func delete() -> Observable +} diff --git a/DailyQuest/DailyQuest/Domain/UseCases/Settings/DefaultAuthUseCase.swift b/DailyQuest/DailyQuest/Domain/UseCases/Settings/DefaultAuthUseCase.swift index 14abcd7..1ca3c0f 100644 --- a/DailyQuest/DailyQuest/Domain/UseCases/Settings/DefaultAuthUseCase.swift +++ b/DailyQuest/DailyQuest/Domain/UseCases/Settings/DefaultAuthUseCase.swift @@ -11,7 +11,7 @@ import RxSwift final class DefaultAuthUseCase { private let authRepository: AuthRepository - + init(authRepository: AuthRepository) { self.authRepository = authRepository } @@ -22,17 +22,26 @@ extension DefaultAuthUseCase: AuthUseCase { return authRepository .signIn(email: email, password: password) .catch { _ in - return .just(false) - } + return .just(false) + } .asObservable() } - + func signOut() -> Observable { return authRepository .signOut() .catch { _ in - return .just(false) - } + return .just(false) + } + .asObservable() + } + + func signUp(email: String, password: String, user: User) -> Observable { + return authRepository + .signUp(email: email, password: password, user: user) + .catch { _ in + return .just(false) + } .asObservable() } } diff --git a/DailyQuest/DailyQuest/Domain/UseCases/Settings/DefaultSettingsUseCase.swift b/DailyQuest/DailyQuest/Domain/UseCases/Settings/DefaultSettingsUseCase.swift new file mode 100644 index 0000000..89ef700 --- /dev/null +++ b/DailyQuest/DailyQuest/Domain/UseCases/Settings/DefaultSettingsUseCase.swift @@ -0,0 +1,40 @@ +// +// DefaultSettingsUseCase.swift +// DailyQuest +// +// Created by jinwoong Kim on 2022/12/06. +// + +import Foundation + +import RxSwift + +final class DefaultSettingsUseCase { + private let userRepository: UserRepository + private let authRepository: AuthRepository + + init(userRepository: UserRepository, authRepository: AuthRepository) { + self.userRepository = userRepository + self.authRepository = authRepository + } +} + +extension DefaultSettingsUseCase: SettingsUseCase { + func isLoggedIn() -> Observable { + return userRepository + .isLoggedIn() + .map { id in + guard let _ = id else { return false } + return true + } + .asObservable() + } + + func signOut() -> Observable { + return authRepository + .signOut() + .map { _ in true } + .catchAndReturn(false) + .asObservable() + } +} diff --git a/DailyQuest/DailyQuest/Domain/UseCases/Settings/Protocols/AuthUseCase.swift b/DailyQuest/DailyQuest/Domain/UseCases/Settings/Protocols/AuthUseCase.swift index d1859ed..f4c9067 100644 --- a/DailyQuest/DailyQuest/Domain/UseCases/Settings/Protocols/AuthUseCase.swift +++ b/DailyQuest/DailyQuest/Domain/UseCases/Settings/Protocols/AuthUseCase.swift @@ -12,4 +12,5 @@ import RxSwift protocol AuthUseCase { func signIn(email: String, password: String) -> Observable func signOut() -> Observable + func signUp(email: String, password: String, user: User) -> Observable } diff --git a/DailyQuest/DailyQuest/Domain/UseCases/Settings/Protocols/SettingsUseCase.swift b/DailyQuest/DailyQuest/Domain/UseCases/Settings/Protocols/SettingsUseCase.swift new file mode 100644 index 0000000..2f4d42d --- /dev/null +++ b/DailyQuest/DailyQuest/Domain/UseCases/Settings/Protocols/SettingsUseCase.swift @@ -0,0 +1,15 @@ +// +// SettingsUseCase.swift +// DailyQuest +// +// Created by jinwoong Kim on 2022/12/06. +// + +import Foundation + +import RxSwift + +protocol SettingsUseCase { + func isLoggedIn() -> Observable + func signOut() -> Observable +} diff --git a/DailyQuest/DailyQuest/Infrastructure/FirebaseService/FirebaseService.swift b/DailyQuest/DailyQuest/Infrastructure/FirebaseService/FirebaseService.swift index e619f15..c727d0b 100644 --- a/DailyQuest/DailyQuest/Infrastructure/FirebaseService/FirebaseService.swift +++ b/DailyQuest/DailyQuest/Infrastructure/FirebaseService/FirebaseService.swift @@ -29,36 +29,9 @@ final class FirebaseService: NetworkService { uid.accept(auth.currentUser?.uid) } - @discardableResult - private func documentReference(userCase: UserCase) throws -> DocumentReference { - switch userCase { - case .currentUser: - guard let currentUserUid = uid.value else { throw NetworkServiceError.noAuthError } - return db.collection(userCase.path).document(currentUserUid) - case let .anotherUser(uid): - return db.collection(userCase.path).document(uid) - } - } - - private func checkPermission(crud: CRUD, userCase: UserCase, access: Access) throws { - switch userCase { - case .currentUser: - switch access { - case .receiveQuests: - if crud == .create { throw NetworkServiceError.permissionDenied } - default: - break - } - case .anotherUser: - switch access { - case .receiveQuests: - if crud == .delete { throw NetworkServiceError.permissionDenied } - default: - if crud != .read { throw NetworkServiceError.permissionDenied } - } - } - } +} +extension FirebaseService { func signIn(email: String, password: String) -> Single { return Single.create { [weak self] single in do { @@ -93,6 +66,65 @@ final class FirebaseService: NetworkService { } } + func signUp(email: String, password: String, userDto: UserDTO) -> Single { + return Single.create { [weak self] single in + guard let self = self else { + single(.failure(NetworkServiceError.noNetworkService)) + return Disposables.create() + } + + self.auth.createUser(withEmail: email, password: password) { authResult, error in + do { + if let error = error { throw error } + guard let authResult = authResult else { throw NetworkServiceError.noAuthError } + try self.createUser(uuid: authResult.user.uid, userDto: userDto) + single(.success(true)) + } catch let error { + single(.failure(error)) + } + } + return Disposables.create() + } + } + + func createUser(uuid: String, userDto: UserDTO) throws { + let userDto = UserDTO(uuid: uuid, userDto: userDto) + try db.collection(UserCase.currentUser.path).document(uuid) + .setData(from: userDto) + } +} + +extension FirebaseService { + @discardableResult + private func documentReference(userCase: UserCase) throws -> DocumentReference { + switch userCase { + case .currentUser: + guard let currentUserUid = uid.value else { throw NetworkServiceError.noAuthError } + return db.collection(userCase.path).document(currentUserUid) + case let .anotherUser(uid): + return db.collection(userCase.path).document(uid) + } + } + + private func checkPermission(crud: CRUD, userCase: UserCase, access: Access) throws { + switch userCase { + case .currentUser: + switch access { + case .receiveQuests: + if crud == .create { throw NetworkServiceError.permissionDenied } + default: + break + } + case .anotherUser: + switch access { + case .receiveQuests: + if crud == .delete { throw NetworkServiceError.permissionDenied } + default: + if crud != .read { throw NetworkServiceError.permissionDenied } + } + } + } + /// Create /// - Parameters: /// - userCase: current User / another User diff --git a/DailyQuest/DailyQuest/Infrastructure/NetworkService.swift b/DailyQuest/DailyQuest/Infrastructure/NetworkService.swift index d101564..fff6537 100644 --- a/DailyQuest/DailyQuest/Infrastructure/NetworkService.swift +++ b/DailyQuest/DailyQuest/Infrastructure/NetworkService.swift @@ -23,6 +23,7 @@ protocol NetworkService { func signIn(email: String, password: String) -> Single func signOut() -> Single + func signUp(email: String, password: String, userDto: UserDTO) -> Single func create(userCase: UserCase, access: Access, dto: T) -> Single func read(type: T.Type, userCase: UserCase, access: Access, filter: DateFilter?) -> Observable @@ -32,6 +33,6 @@ protocol NetworkService { func uploadDataStorage(data: Data, path: StoragePath) -> Single func downloadDataStorage(fileName: String) -> Single func deleteDataStorage(fileName: String) -> Single - - func getAllowUsers(limit: Int)->Observable + + func getAllowUsers(limit: Int) -> Observable } diff --git a/DailyQuest/DailyQuest/Presentation/Settings/Flow/SettingsCoordinator.swift b/DailyQuest/DailyQuest/Presentation/Settings/Flow/SettingsCoordinator.swift index 6775f3d..40748e3 100644 --- a/DailyQuest/DailyQuest/Presentation/Settings/Flow/SettingsCoordinator.swift +++ b/DailyQuest/DailyQuest/Presentation/Settings/Flow/SettingsCoordinator.swift @@ -15,35 +15,50 @@ protocol SettingsCoordinator: Coordinator { final class DefaultSettingsCoordinator: SettingsCoordinator { private var disposableBag = DisposeBag() - + var finishDelegate: CoordinatorFinishDelegate? var childCoordinators: [Coordinator] = [] var navigationController: UINavigationController let settingsSceneDIContainer: SettingsSceneDIContainer - + init(navigationController: UINavigationController, settingsSceneDIContainer: SettingsSceneDIContainer) { self.navigationController = navigationController self.settingsSceneDIContainer = settingsSceneDIContainer } - + func start() { - let settingsController = SettingsViewController() - navigationController.pushViewController(settingsController, animated: false) + let settingsViewController = settingsSceneDIContainer.makeSettingsViewController() + navigationController.pushViewController(settingsViewController, animated: false) - settingsController + settingsViewController .itemDidClick .bind(onNext: { [weak self] event in - switch event { - case .showLoginFlow: - self?.showLoginFlow() - } - }) + switch event { + case .showLoginFlow: + self?.showLoginFlow() + } + }) .disposed(by: disposableBag) } - + func showLoginFlow() { let loginViewController = settingsSceneDIContainer.makeLoginViewController() + loginViewController + .itemDidClick + .bind(onNext: { [weak self] event in + switch event { + case .showSignUpFlow: + self?.showSignUpFlow() + } + }) + .disposed(by: disposableBag) + navigationController.pushViewController(loginViewController, animated: true) } + + func showSignUpFlow() { + let signUpViewController = settingsSceneDIContainer.makeSignUpViewController() + navigationController.pushViewController(signUpViewController, animated: true) + } } diff --git a/DailyQuest/DailyQuest/Presentation/Settings/View/CommonField.swift b/DailyQuest/DailyQuest/Presentation/Settings/View/CommonField.swift index a8b2125..064a554 100644 --- a/DailyQuest/DailyQuest/Presentation/Settings/View/CommonField.swift +++ b/DailyQuest/DailyQuest/Presentation/Settings/View/CommonField.swift @@ -7,8 +7,9 @@ import UIKit -enum ViewType { - case login +enum ViewType: String { + case login = "로그인" + case logout = "로그아웃" } protocol CommonField { diff --git a/DailyQuest/DailyQuest/Presentation/Settings/View/NavigateField/NavigateCell.swift b/DailyQuest/DailyQuest/Presentation/Settings/View/NavigateField/NavigateCell.swift index edb9c14..320ec1c 100644 --- a/DailyQuest/DailyQuest/Presentation/Settings/View/NavigateField/NavigateCell.swift +++ b/DailyQuest/DailyQuest/Presentation/Settings/View/NavigateField/NavigateCell.swift @@ -54,6 +54,6 @@ final class NavigateCell: UITableViewCell { func setup(with viewModel: NavigateItemViewModel) { icon.image = UIImage(systemName: viewModel.imageName) - title.text = viewModel.title + title.text = viewModel.viewType.rawValue } } diff --git a/DailyQuest/DailyQuest/Presentation/Settings/View/NavigateField/NavigateField.swift b/DailyQuest/DailyQuest/Presentation/Settings/View/NavigateField/NavigateField.swift index 060b4f6..079f7d4 100644 --- a/DailyQuest/DailyQuest/Presentation/Settings/View/NavigateField/NavigateField.swift +++ b/DailyQuest/DailyQuest/Presentation/Settings/View/NavigateField/NavigateField.swift @@ -13,6 +13,14 @@ final class NavigateField { init(viewModel: NavigateItemViewModel) { self.viewModel = viewModel } + + func toggle(with status: Bool) { + if status { + viewModel.viewType = .logout + } else { + viewModel.viewType = .login + } + } } extension NavigateField: CommonField { diff --git a/DailyQuest/DailyQuest/Presentation/Settings/View/NavigateField/NavigateItemViewModel.swift b/DailyQuest/DailyQuest/Presentation/Settings/View/NavigateField/NavigateItemViewModel.swift index 6b0ea71..15ef952 100644 --- a/DailyQuest/DailyQuest/Presentation/Settings/View/NavigateField/NavigateItemViewModel.swift +++ b/DailyQuest/DailyQuest/Presentation/Settings/View/NavigateField/NavigateItemViewModel.swift @@ -10,5 +10,5 @@ import Foundation struct NavigateItemViewModel { let title: String let imageName: String - let viewType: ViewType + var viewType: ViewType } diff --git a/DailyQuest/DailyQuest/Presentation/Settings/ViewController/LoginViewController.swift b/DailyQuest/DailyQuest/Presentation/Settings/ViewController/LoginViewController.swift index 0924658..0e95263 100644 --- a/DailyQuest/DailyQuest/Presentation/Settings/ViewController/LoginViewController.swift +++ b/DailyQuest/DailyQuest/Presentation/Settings/ViewController/LoginViewController.swift @@ -12,70 +12,85 @@ import RxCocoa import SnapKit final class LoginViewController: UIViewController { + enum Event { + case showSignUpFlow + } + private var viewModel: LoginViewModel! private var disposableBag = DisposeBag() - + + var itemDidClick = PublishSubject() + private lazy var container: UIStackView = { let container = UIStackView() container.axis = .vertical container.spacing = 10 - + return container }() - + private lazy var emailField: TextFieldForm = { let emailField = TextFieldForm() emailField.placeholder = "email" - + emailField.autocapitalizationType = .none return emailField }() - + private lazy var passwordField: TextFieldForm = { let passwordField = TextFieldForm() passwordField.placeholder = "password" - + passwordField.isSecureTextEntry = true return passwordField }() - + private lazy var submitButton: UIButton = { var config = UIButton.Configuration.filled() config.baseBackgroundColor = .maxYellow config.title = "로그인" - + emailField.autocapitalizationType = .none + return UIButton(configuration: config) + }() + + private lazy var signUpButton: UIButton = { + var config = UIButton.Configuration.plain() + config.baseForegroundColor = .gray + config.title = "회원가입" + config.buttonSize = .small return UIButton(configuration: config) }() - + // MARK: Life Cycle static func create(with viewModel: LoginViewModel) -> LoginViewController { let vc = LoginViewController() vc.setup(with: viewModel) - + return vc } - + override func viewDidLoad() { super.viewDidLoad() - + configureUI() - + bind() } - + private func configureUI() { view.backgroundColor = .white - + container.addArrangedSubview(emailField) container.addArrangedSubview(passwordField) container.addArrangedSubview(submitButton) - + container.addArrangedSubview(signUpButton) + view.addSubview(container) - + container.snp.makeConstraints { make in make.center.equalToSuperview() make.width.equalToSuperview().multipliedBy(0.8) } } - + private func setup(with authViewModel: LoginViewModel) { viewModel = authViewModel } @@ -83,24 +98,29 @@ final class LoginViewController: UIViewController { extension LoginViewController { private func bind() { + signUpButton.rx.tap.bind(onNext: { [weak self] _ in + guard let self = self else { return } + self.itemDidClick.onNext(.showSignUpFlow) + }).disposed(by: disposableBag) + let input = LoginViewModel.Input( emailFieldDidEditEvent: emailField.rx.text.orEmpty.asObservable(), passwordFieldDidEditEvent: passwordField.rx.text.orEmpty.asObservable(), submitButtonDidTapEvent: submitButton.rx.tap.asObservable() ) - + let output = viewModel.transform(input: input, disposeBag: disposableBag) - + output .buttonEnabled .drive(submitButton.rx.isEnabled) .disposed(by: disposableBag) - + output .loginResult .subscribe(onNext: { result in - print("login result is :::: ", result) - }) + print("login result is :::: ", result) + }) .disposed(by: disposableBag) } } diff --git a/DailyQuest/DailyQuest/Presentation/Settings/ViewController/SettingsViewController.swift b/DailyQuest/DailyQuest/Presentation/Settings/ViewController/SettingsViewController.swift index 1cb18c8..ff11c38 100644 --- a/DailyQuest/DailyQuest/Presentation/Settings/ViewController/SettingsViewController.swift +++ b/DailyQuest/DailyQuest/Presentation/Settings/ViewController/SettingsViewController.swift @@ -11,49 +11,101 @@ import RxSwift import RxCocoa final class SettingsViewController: UITableViewController { - enum Event { - case showLoginFlow - } + private var viewModel: SettingsViewModel! + private var disposableBag = DisposeBag() var itemDidClick = PublishSubject() - private let fields: [CommonField] = [ - ToggleField(viewModel: .init(title: "둘러보기 허용", imageName: "person.crop.circle.badge.checkmark")), - PlainField(viewModel: .init(title: "some", info: "some...?", imageName: "pencil")), - NavigateField(viewModel: .init(title: "로그인", imageName: "person.circle.fill", viewType: .login)), - PlainField(viewModel: .init(title: "앱 버전", info: "0.0.1", imageName: "exclamationmark.transmission")) - ] - // MARK: - Life Cycle + static func create(with viewModel: SettingsViewModel) -> SettingsViewController { + let vc = SettingsViewController() + vc.viewModel = viewModel + + return vc + } + override func viewDidLoad() { super.viewDidLoad() tableView.separatorStyle = .singleLine register() + + bind() } + private func bind() { + let output = viewModel.transform() + + output + .loginStatusDidChange + .subscribe(onNext: { [weak self] in self?.reloadData() }) + .disposed(by: disposableBag) + } + + private func reloadData() { + tableView.reloadData() + } + + private func register() { + viewModel.fields.forEach { field in + field.register(for: self.tableView) + } + } +} + +extension SettingsViewController { + func showAlert(preferredStyle: UIAlertController.Style = .alert, + completion: (() -> Void)? = nil) + { + let title = "로그아웃" + let message = "로그아웃 하시겠습니까?" + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + + let okAction = UIAlertAction(title: "Ok", style: .default) { [weak self] _ in + self?.signOut() + } + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) + + alert.addAction(okAction) + alert.addAction(cancelAction) + + self.present(alert, animated: true, completion: completion) + } + + private func signOut() { + viewModel + .signOut() + .subscribe() + .disposed(by: disposableBag) + } +} + +extension SettingsViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return fields.count + return viewModel.fields.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let field = fields[indexPath.row] + let field = viewModel.fields[indexPath.row] return field.dequeue(for: tableView, at: indexPath) } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let field = fields[indexPath.row] + let field = viewModel.fields[indexPath.row] guard let type = field.didSelect() else { return } switch type { case .login: itemDidClick.onNext(.showLoginFlow) + case .logout: + showAlert() } } - - private func register() { - fields.forEach { field in - field.register(for: self.tableView) - } +} + +extension SettingsViewController { + enum Event { + case showLoginFlow } } diff --git a/DailyQuest/DailyQuest/Presentation/Settings/ViewController/SignUpViewController.swift b/DailyQuest/DailyQuest/Presentation/Settings/ViewController/SignUpViewController.swift new file mode 100644 index 0000000..e445209 --- /dev/null +++ b/DailyQuest/DailyQuest/Presentation/Settings/ViewController/SignUpViewController.swift @@ -0,0 +1,127 @@ +// +// SignUpViewController.swift +// DailyQuest +// +// Created by 이전희 on 2022/12/06. +// + +import UIKit + +import RxSwift +import RxCocoa +import SnapKit + +final class SignUpViewController: UIViewController { + private var viewModel: SignUpViewModel! + private var disposableBag = DisposeBag() + + private lazy var container: UIStackView = { + let container = UIStackView() + container.axis = .vertical + container.spacing = 10 + + return container + }() + + private lazy var emailField: TextFieldForm = { + let emailField = TextFieldForm() + emailField.placeholder = "email" + emailField.autocapitalizationType = .none + + return emailField + }() + + private lazy var passwordField: TextFieldForm = { + let passwordField = TextFieldForm() + passwordField.placeholder = "password (6글자 이상)" + passwordField.isSecureTextEntry = true + + return passwordField + }() + + private lazy var passwordConfirmField: TextFieldForm = { + let passwordConfirmField = TextFieldForm() + passwordConfirmField.placeholder = "password 확인" + passwordConfirmField.isSecureTextEntry = true + return passwordConfirmField + }() + + private lazy var nickNameField: TextFieldForm = { + let nickNameField = TextFieldForm() + nickNameField.placeholder = "닉네임" + emailField.autocapitalizationType = .none + return nickNameField + }() + + private lazy var submitButton: UIButton = { + var config = UIButton.Configuration.filled() + config.baseBackgroundColor = .maxYellow + config.title = "회원가입" + + return UIButton(configuration: config) + }() + + // MARK: Life Cycle + static func create(with viewModel: SignUpViewModel) -> SignUpViewController { + let vc = SignUpViewController() + vc.setup(with: viewModel) + return vc + } + + override func viewDidLoad() { + super.viewDidLoad() + + configureUI() + + bind() + } + + private func configureUI() { + view.backgroundColor = .white + + [emailField, + passwordField, + passwordConfirmField, + nickNameField, + submitButton].forEach { field in + container.addArrangedSubview(field) + } + + view.addSubview(container) + + container.snp.makeConstraints { make in + make.center.equalToSuperview() + make.width.equalToSuperview().multipliedBy(0.8) + } + } + + private func setup(with authViewModel: SignUpViewModel) { + viewModel = authViewModel + } +} + +extension SignUpViewController { + private func bind() { + let input = SignUpViewModel.Input( + emailFieldDidEditEvent: emailField.rx.text.orEmpty.asObservable(), + passwordFieldDidEditEvent: passwordField.rx.text.orEmpty.asObservable(), + passwordConfirmFieldDidEditEvent: passwordConfirmField.rx.text.orEmpty.asObservable(), + nickNameFieldDidEditEvent: nickNameField.rx.text.orEmpty.asObservable(), + submitButtonDidTapEvent: submitButton.rx.tap.asObservable() + ) + + let output = viewModel.transform(input: input, disposeBag: disposableBag) + + output + .buttonEnabled + .drive(submitButton.rx.isEnabled) + .disposed(by: disposableBag) + + output + .signUpResult + .subscribe(onNext: { result in + print("SignUp result is :::: ", result) + }) + .disposed(by: disposableBag) + } +} diff --git a/DailyQuest/DailyQuest/Presentation/Settings/ViewModel/SettingsViewModel.swift b/DailyQuest/DailyQuest/Presentation/Settings/ViewModel/SettingsViewModel.swift new file mode 100644 index 0000000..ee76fb4 --- /dev/null +++ b/DailyQuest/DailyQuest/Presentation/Settings/ViewModel/SettingsViewModel.swift @@ -0,0 +1,53 @@ +// +// SettingsViewModel.swift +// DailyQuest +// +// Created by jinwoong Kim on 2022/12/06. +// + +import Foundation + +import RxSwift + +final class SettingsViewModel { + private(set) var fields: [CommonField] + private var navigateField: NavigateField + + private let settingsUseCase: SettingsUseCase + private var disposableBag = DisposeBag() + + init(settingsUseCase: SettingsUseCase) { + self.settingsUseCase = settingsUseCase + + let toggleField = ToggleField(viewModel: .init(title: "둘러보기 허용", imageName: "person.crop.circle.badge.checkmark")) + let plainField = PlainField(viewModel: .init(title: "앱 버전", info: "0.0.1", imageName: "exclamationmark.transmission")) + self.navigateField = NavigateField(viewModel: .init(title: "로그인", imageName: "person.circle.fill", viewType: .login)) + + self.fields = [ + toggleField, + plainField, + navigateField + ] + + print(self.fields.count) + } + + struct Input {} + + struct Output { + let loginStatusDidChange: Observable + } + + func transform() -> Output { + let loginStatusDidChange = settingsUseCase + .isLoggedIn() + .do(onNext: navigateField.toggle(with:)) + .map { _ in Void() } + + return Output(loginStatusDidChange: loginStatusDidChange) + } + + func signOut() -> Observable { + return settingsUseCase.signOut() + } +} diff --git a/DailyQuest/DailyQuest/Presentation/Settings/ViewModel/SignUpViewModel.swift b/DailyQuest/DailyQuest/Presentation/Settings/ViewModel/SignUpViewModel.swift new file mode 100644 index 0000000..f154ae8 --- /dev/null +++ b/DailyQuest/DailyQuest/Presentation/Settings/ViewModel/SignUpViewModel.swift @@ -0,0 +1,83 @@ +// +// SignUpViewModel.swift +// DailyQuest +// +// Created by 이전희 on 2022/12/06. +// + +import Foundation + +import RxSwift +import RxCocoa + +final class SignUpViewModel { + private let authUseCase: AuthUseCase + + init(authUseCase: AuthUseCase) { + self.authUseCase = authUseCase + } + + struct Input { + let emailFieldDidEditEvent: Observable + let passwordFieldDidEditEvent: Observable + let passwordConfirmFieldDidEditEvent: Observable + let nickNameFieldDidEditEvent: Observable + let submitButtonDidTapEvent: Observable + } + + struct Output { + let buttonEnabled: Driver + let signUpResult: Observable + } + + func transform(input: Input, disposeBag: DisposeBag) -> Output { + let buttonEnabled = Observable + .combineLatest(input.emailFieldDidEditEvent, + input.passwordFieldDidEditEvent, + input.passwordConfirmFieldDidEditEvent, + input.nickNameFieldDidEditEvent, + resultSelector: checkButtonEnble(emailText: passwordText: passwordConfirmText: nickName:)) + .asDriver(onErrorJustReturn: false) + + let signUpResult = input + .submitButtonDidTapEvent + .withLatestFrom(Observable + .combineLatest(input.emailFieldDidEditEvent, + input.passwordFieldDidEditEvent, + input.nickNameFieldDidEditEvent, + resultSelector: { ($0, $1, User(nickName: $2)) })) + .flatMap (authUseCase.signUp(email: password: user:)) + + return Output(buttonEnabled: buttonEnabled, signUpResult: signUpResult) + } + + func checkEmpty(_ strArray: [String]) -> Bool { + return !strArray.reduce(false) { $0 || $1.isEmpty } + } + + func checkSame(str1: String, str2: String) -> Bool { + return str1 == str2 + } + + func checkEmail(str: String) -> Bool { + + guard let index = str.firstIndex(of: "@") else { return false } + let pos = str.distance(from: str.startIndex, to: index) + return 0 < pos && pos < str.count - 1 + } + + func checkPasswordCount(str: String) -> Bool { + return str.count >= 6 + } + + func checkButtonEnble(emailText: String, + passwordText: String, + passwordConfirmText: String, + nickName: String) -> Bool { + return checkEmail(str: emailText) && + checkEmpty([emailText, passwordText, passwordConfirmText, nickName]) && + checkSame(str1: passwordText, str2: passwordConfirmText) && + checkPasswordCount(str: passwordText) + } +} +