diff --git a/NearTalk/NearTalk.xcodeproj/project.pbxproj b/NearTalk/NearTalk.xcodeproj/project.pbxproj index c7b32c5d..f95ebefd 100644 --- a/NearTalk/NearTalk.xcodeproj/project.pbxproj +++ b/NearTalk/NearTalk.xcodeproj/project.pbxproj @@ -49,6 +49,12 @@ 555A6B432924B9E4001336ED /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 555A6B422924B9E4001336ED /* GoogleService-Info.plist */; }; 555A6B482924BE65001336ED /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 555A6B472924BE65001336ED /* UserProfile.swift */; }; 556EE2512926402000EEA145 /* ChatRoomListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 556EE2502926402000EEA145 /* ChatRoomListCell.swift */; }; + 6001DA88292D47320062EAD0 /* UserChatRoomTicket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6001DA87292D47320062EAD0 /* UserChatRoomTicket.swift */; }; + 6001DA8A292D5F270062EAD0 /* ChatMessageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6001DA89292D5F270062EAD0 /* ChatMessageRepository.swift */; }; + 6001DA8C292DBE020062EAD0 /* UploadChatRoomInfoUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6001DA8B292DBE020062EAD0 /* UploadChatRoomInfoUseCase.swift */; }; + 6001DA8E292DBF400062EAD0 /* MediaRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6001DA8D292DBF400062EAD0 /* MediaRepository.swift */; }; + 6001DA90292DBF770062EAD0 /* DefaultMediaRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6001DA8F292DBF770062EAD0 /* DefaultMediaRepository.swift */; }; + 6001DA92292DC86A0062EAD0 /* DefaultChatMessageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6001DA91292DC86A0062EAD0 /* DefaultChatMessageRepository.swift */; }; 6008FC9D2925DBCF0044F017 /* NCLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6008FC9C2925DBCF0044F017 /* NCLocation.swift */; }; 601273402921D34000E1456B /* LaunchScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6012733F2921D34000E1456B /* LaunchScreenViewModel.swift */; }; 601273422921D34E00E1456B /* LaunchScreenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 601273412921D34E00E1456B /* LaunchScreenViewController.swift */; }; @@ -111,6 +117,7 @@ 6073A3AE2928AE9800D51CFD /* Codable+Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6073A3AD2928AE9800D51CFD /* Codable+Dictionary.swift */; }; 60C2A789291F3FE000F7D284 /* LaunchScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C2A788291F3FE000F7D284 /* LaunchScreenCoordinator.swift */; }; 60DCAAF42928B9610087A63D /* Cartfile in Resources */ = {isa = PBXBuildFile; fileRef = 60DCAAF32928B9610087A63D /* Cartfile */; }; + 60E749DC292C88F40048F877 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E749DB292C88F40048F877 /* AuthService.swift */; }; 60FD705C29238CBB00A4C29A /* StorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60FD705929238CBB00A4C29A /* StorageService.swift */; }; 60FD705D29238CBB00A4C29A /* RealTimeDatabaseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60FD705A29238CBB00A4C29A /* RealTimeDatabaseService.swift */; }; 60FD706029238DD400A4C29A /* FirebaseAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60FD705F29238DD400A4C29A /* FirebaseAuthService.swift */; }; @@ -240,6 +247,13 @@ 555A6B422924B9E4001336ED /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 555A6B472924BE65001336ED /* UserProfile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = ""; }; 556EE2502926402000EEA145 /* ChatRoomListCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatRoomListCell.swift; sourceTree = ""; }; + 6001DA86292CB70B0062EAD0 /* NearTalk.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NearTalk.entitlements; sourceTree = ""; }; + 6001DA87292D47320062EAD0 /* UserChatRoomTicket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserChatRoomTicket.swift; sourceTree = ""; }; + 6001DA89292D5F270062EAD0 /* ChatMessageRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageRepository.swift; sourceTree = ""; }; + 6001DA8B292DBE020062EAD0 /* UploadChatRoomInfoUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadChatRoomInfoUseCase.swift; sourceTree = ""; }; + 6001DA8D292DBF400062EAD0 /* MediaRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaRepository.swift; sourceTree = ""; }; + 6001DA8F292DBF770062EAD0 /* DefaultMediaRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultMediaRepository.swift; sourceTree = ""; }; + 6001DA91292DC86A0062EAD0 /* DefaultChatMessageRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultChatMessageRepository.swift; sourceTree = ""; }; 6008FC9C2925DBCF0044F017 /* NCLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCLocation.swift; sourceTree = ""; }; 6012733F2921D34000E1456B /* LaunchScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchScreenViewModel.swift; sourceTree = ""; }; 601273412921D34E00E1456B /* LaunchScreenViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchScreenViewController.swift; sourceTree = ""; }; @@ -303,6 +317,7 @@ 6073A3AD2928AE9800D51CFD /* Codable+Dictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Codable+Dictionary.swift"; sourceTree = ""; }; 60C2A788291F3FE000F7D284 /* LaunchScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchScreenCoordinator.swift; sourceTree = ""; }; 60DCAAF32928B9610087A63D /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile; sourceTree = ""; }; + 60E749DB292C88F40048F877 /* AuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; 60FB1DA9A810C61099D92942 /* Pods-NearTalk.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NearTalk.debug.xcconfig"; path = "Target Support Files/Pods-NearTalk/Pods-NearTalk.debug.xcconfig"; sourceTree = ""; }; 60FD705929238CBB00A4C29A /* StorageService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageService.swift; sourceTree = ""; }; 60FD705A29238CBB00A4C29A /* RealTimeDatabaseService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealTimeDatabaseService.swift; sourceTree = ""; }; @@ -551,11 +566,12 @@ 1660D226291DDB37005F7B24 /* NearTalk */ = { isa = PBXGroup; children = ( + 6001DA86292CB70B0062EAD0 /* NearTalk.entitlements */, 1660D25A291DDD73005F7B24 /* Application */, 1660D25F291DDE16005F7B24 /* Resource */, - 1660D25D291DDD9C005F7B24 /* Data */, - 1660D25B291DDD7D005F7B24 /* Domain */, 1660D25C291DDD88005F7B24 /* Presentation */, + 1660D25B291DDD7D005F7B24 /* Domain */, + 1660D25D291DDD9C005F7B24 /* Data */, 1660D25E291DDDAA005F7B24 /* Infrastructure */, ); path = NearTalk; @@ -698,6 +714,7 @@ 603EDDA22926C01D00BEE65C /* UserChatModel.swift */, 5506CDDE292B50D10002F328 /* Friend.swift */, 603FD38D2929301200687ED4 /* BaseEntity.swift */, + 6001DA87292D47320062EAD0 /* UserChatRoomTicket.swift */, ); path = Entities; sourceTree = ""; @@ -716,6 +733,7 @@ 603FD399292B4F0F00687ED4 /* VerifyUserUseCase.swift */, 603FD39B292B4F4A00687ED4 /* LogoutUseCase.swift */, 83D5ECC6292B74FA00AD1781 /* DropOutUseCase.swift */, + 6001DA8B292DBE020062EAD0 /* UploadChatRoomInfoUseCase.swift */, ); path = UseCases; sourceTree = ""; @@ -738,6 +756,8 @@ 603EDDAA2926C52200BEE65C /* DefaultTabBarRepository.swift */, 603FD39229296B8500687ED4 /* DefaultProfileRepository.swift */, 83D5ECC4292B736500AD1781 /* DefaultAuthRepository.swift */, + 6001DA8F292DBF770062EAD0 /* DefaultMediaRepository.swift */, + 6001DA91292DC86A0062EAD0 /* DefaultChatMessageRepository.swift */, ); path = Repositories; sourceTree = ""; @@ -815,12 +835,14 @@ isa = PBXGroup; children = ( 555A6B3D29238D65001336ED /* ChatRoomListRepository.swift */, + 6001DA89292D5F270062EAD0 /* ChatMessageRepository.swift */, 83D354402925412400B9236A /* UserProfileRepository.swift */, 83D354472925438C00B9236A /* UserUUIDRepository.swift */, 8349529D2925FEAE004C5B3B /* ImageRepository.swift */, 603EDDA82926C51100BEE65C /* TabBarRepository.swift */, 603FD38F29296AA300687ED4 /* ProfileRepository.swift */, 83D5ECC2292B714500AD1781 /* AuthRepository.swift */, + 6001DA8D292DBF400062EAD0 /* MediaRepository.swift */, ); path = Repositories; sourceTree = ""; @@ -942,6 +964,7 @@ 60FD705F29238DD400A4C29A /* FirebaseAuthService.swift */, 603EDD6229261A2000BEE65C /* FirebaseKey.swift */, 603FD389292928D000687ED4 /* FirebaseQueryKey.swift */, + 60E749DB292C88F40048F877 /* AuthService.swift */, ); path = Network; sourceTree = ""; @@ -1433,6 +1456,7 @@ 603EDDA92926C51100BEE65C /* TabBarRepository.swift in Sources */, 555A6B3529238C13001336ED /* ChatRoomListDIContainer.swift in Sources */, 555A6B3A29238C9B001336ED /* FetchChatRoomUseCase.swift in Sources */, + 6001DA8E292DBF400062EAD0 /* MediaRepository.swift in Sources */, 1648BAB1292387100076AC35 /* DmChatRoomAnnotationView.swift in Sources */, 1660D22C291DDB38005F7B24 /* ViewController.swift in Sources */, 555A6B482924BE65001336ED /* UserProfile.swift in Sources */, @@ -1456,6 +1480,7 @@ 603EDDB12926C87700BEE65C /* RootTabBarController.swift in Sources */, 603EDDA32926C01D00BEE65C /* UserChatModel.swift in Sources */, 5506CDDF292B50D10002F328 /* Friend.swift in Sources */, + 6001DA90292DBF770062EAD0 /* DefaultMediaRepository.swift in Sources */, 60FD706A2924080A00A4C29A /* ChatMessage.swift in Sources */, 555A6B2F29236D73001336ED /* FriendListViewModel.swift in Sources */, 16530578291E18560026B815 /* UIViewPreview.swift in Sources */, @@ -1478,12 +1503,15 @@ 16530576291E17F70026B815 /* UIViewController+Preview.swift in Sources */, 83D354412925412400B9236A /* UserProfileRepository.swift in Sources */, 7CFC7D6C2923710200A249E7 /* RangeZoneView.swift in Sources */, + 6001DA92292DC86A0062EAD0 /* DefaultChatMessageRepository.swift in Sources */, 8367AB642922AF6300678669 /* ProfileSettingViewController.swift in Sources */, 1660D228291DDB37005F7B24 /* AppDelegate.swift in Sources */, 1660D266291DE092005F7B24 /* AppCoordinator.swift in Sources */, 7CE806412925528F00D828FF /* CreateGroupChatUseCase.swift in Sources */, 1648BAC02925F48F0076AC35 /* BottomSheetTableViewCell.swift in Sources */, 8330622F2924A402000E786E /* String+Regex.swift in Sources */, + 6001DA8C292DBE020062EAD0 /* UploadChatRoomInfoUseCase.swift in Sources */, + 6001DA8A292D5F270062EAD0 /* ChatMessageRepository.swift in Sources */, 603FD38A292928D000687ED4 /* FirebaseQueryKey.swift in Sources */, 1648BA992921D7480076AC35 /* MainMapCoordinator.swift in Sources */, 555A6B3E29238D65001336ED /* ChatRoomListRepository.swift in Sources */, @@ -1508,7 +1536,7 @@ 83D5ECC5292B736500AD1781 /* DefaultAuthRepository.swift in Sources */, 60FD705C29238CBB00A4C29A /* StorageService.swift in Sources */, 83D5ECC3292B714500AD1781 /* AuthRepository.swift in Sources */, - 555A6B2D29236D61001336ED /* FriendsViewController.swift in Sources */, + 555A6B2D29236D61001336ED /* FriendViewController.swift in Sources */, 1660D22A291DDB38005F7B24 /* SceneDelegate.swift in Sources */, 603EDDB62926C89C00BEE65C /* RootTabBarCoordinator.swift in Sources */, 7CFC7D70292374BD00A249E7 /* ProfileDetailViewController.swift in Sources */, @@ -1517,6 +1545,7 @@ 839D5BDD2922AD1000857FDD /* LoginViewController.swift in Sources */, 603EDDB32926C89000BEE65C /* RootTabBarViewModel.swift in Sources */, 603EDDAB2926C52200BEE65C /* DefaultTabBarRepository.swift in Sources */, + 6001DA88292D47320062EAD0 /* UserChatRoomTicket.swift in Sources */, 83D3543D29252F5B00B9236A /* MyProfileCoordinator.swift in Sources */, 83D354482925438C00B9236A /* UserUUIDRepository.swift in Sources */, 601273422921D34E00E1456B /* LaunchScreenViewController.swift in Sources */, @@ -1524,6 +1553,7 @@ 83D3543F29253A5C00B9236A /* MyProfileLoadUseCase.swift in Sources */, 1660D232291DDB38005F7B24 /* NearTalk.xcdatamodeld in Sources */, 7CFC7D6A292370DB00A249E7 /* GroupChatCreationViewController.swift in Sources */, + 60E749DC292C88F40048F877 /* AuthService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1691,6 +1721,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = NearTalk/NearTalk.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 92NGUT4BRA; @@ -1702,6 +1734,9 @@ ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = NearTalk/Resource/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NearTalk; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Location GPS 권한 요청입니다."; + INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "사진 권한 요청입니다"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; @@ -1716,8 +1751,9 @@ "$(OTHER_LDFLAGS)", "-ObjC", ); - PRODUCT_BUNDLE_IDENTIFIER = Boostcamp.iOS.i5S.NearTalk; + PRODUCT_BUNDLE_IDENTIFIER = com.Boostcamp.iOS.i5S.NearTalk; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -1734,6 +1770,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = NearTalk/NearTalk.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 92NGUT4BRA; @@ -1745,6 +1783,9 @@ ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = NearTalk/Resource/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NearTalk; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Location GPS 권한 요청입니다."; + INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "사진 권한 요청입니다"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; @@ -1759,8 +1800,9 @@ "$(OTHER_LDFLAGS)", "-ObjC", ); - PRODUCT_BUNDLE_IDENTIFIER = Boostcamp.iOS.i5S.NearTalk; + PRODUCT_BUNDLE_IDENTIFIER = com.Boostcamp.iOS.i5S.NearTalk; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; diff --git a/NearTalk/NearTalk/Application/Coordinator/AppCoordinator.swift b/NearTalk/NearTalk/Application/Coordinator/AppCoordinator.swift index 784f39a8..ce10a79e 100644 --- a/NearTalk/NearTalk/Application/Coordinator/AppCoordinator.swift +++ b/NearTalk/NearTalk/Application/Coordinator/AppCoordinator.swift @@ -38,12 +38,15 @@ final class AppCoordinator: Coordinator { extension AppCoordinator: LaunchScreenCoordinatorDependency { func showMainViewController() { self.navigationController?.popViewController(animated: false) - let rootTabBarCoordinator: RootTabBarCoordinator = RootTabBarDIContainer() - .makeTabBarCoordinator(navigationController: self.navigationController) + guard let rootTabBarCoordinator: RootTabBarCoordinator = RootTabBarDIContainer() + .makeTabBarCoordinator(navigationController: self.navigationController) else { + return + } rootTabBarCoordinator.start() } func showLoginViewController() { - print(Self.self, #function) + self.navigationController?.viewControllers.insert(LoginViewController(), at: 0) + self.navigationController?.popViewController(animated: false) } } diff --git a/NearTalk/NearTalk/Application/DIContainer/ChatRoomListDIContainer.swift b/NearTalk/NearTalk/Application/DIContainer/ChatRoomListDIContainer.swift index fb98389a..e48f4832 100644 --- a/NearTalk/NearTalk/Application/DIContainer/ChatRoomListDIContainer.swift +++ b/NearTalk/NearTalk/Application/DIContainer/ChatRoomListDIContainer.swift @@ -30,8 +30,8 @@ final class ChatRoomListDIContainer { // MARK: - Dependencies struct Dependencies { - let apiDataTransferService: DefaultStorageService - let imageDataTransferService: DefaultStorageService + let apiDataTransferService: StorageService + let imageDataTransferService: StorageService } private let dependencies: Dependencies @@ -42,6 +42,25 @@ final class ChatRoomListDIContainer { } // MARK: - Services + func makeFirestoreService() -> FirestoreService { + return DefaultFirestoreService() + } + + func makeDatabaseService() -> RealTimeDatabaseService { + return DefaultRealTimeDatabaseService() + } + + func makeAuthService() -> AuthService { + return DefaultFirebaseAuthService() + } + + // MARK: - Repository + func makeProfileRepository() -> ProfileRepository { + return DefaultProfileRepository( + firestoreService: makeFirestoreService(), + firebaseAuthService: makeAuthService() + ) + } // MARK: - UseCases func makeChatRoomListUseCase() -> FetchChatRoomUseCase { @@ -50,7 +69,12 @@ final class ChatRoomListDIContainer { // MARK: - Repositories func makeRepository() -> ChatRoomListRepository { - return DefaultChatRoomListRepository(dataTransferService: dependencies.apiDataTransferService) + return DefaultChatRoomListRepository( + dataTransferService: dependencies.apiDataTransferService, + profileRepository: makeProfileRepository(), + databaseService: makeDatabaseService(), + firestoreService: makeFirestoreService() + ) } // ExampleMVVM에서는 보여줄수 있는 Scene의 뷰컨트롤러와 뷰모델이 존재 diff --git a/NearTalk/NearTalk/Application/DIContainer/FriendListDIContainer.swift b/NearTalk/NearTalk/Application/DIContainer/FriendListDIContainer.swift index 58a61840..cf5f1903 100644 --- a/NearTalk/NearTalk/Application/DIContainer/FriendListDIContainer.swift +++ b/NearTalk/NearTalk/Application/DIContainer/FriendListDIContainer.swift @@ -12,7 +12,7 @@ final class FriendListDIContainer { // MARK: - Dependencies struct Dependencies { let firestoreService: FirestoreService - let firebaseAuthService: FirebaseAuthService + let firebaseAuthService: AuthService } private let dependencies: Dependencies diff --git a/NearTalk/NearTalk/Application/DIContainer/LaunchScreenDIContainer.swift b/NearTalk/NearTalk/Application/DIContainer/LaunchScreenDIContainer.swift index ce3cf15b..f4af4eb1 100644 --- a/NearTalk/NearTalk/Application/DIContainer/LaunchScreenDIContainer.swift +++ b/NearTalk/NearTalk/Application/DIContainer/LaunchScreenDIContainer.swift @@ -12,7 +12,7 @@ final class LaunchScreenDIContainer { // MARK: - Dependencies // MARK: - Services - func makeAuthService() -> FirebaseAuthService { + func makeAuthService() -> AuthService { return DefaultFirebaseAuthService() } diff --git a/NearTalk/NearTalk/Application/DIContainer/OnboardingDIContainer.swift b/NearTalk/NearTalk/Application/DIContainer/OnboardingDIContainer.swift index b66ab244..738281b3 100644 --- a/NearTalk/NearTalk/Application/DIContainer/OnboardingDIContainer.swift +++ b/NearTalk/NearTalk/Application/DIContainer/OnboardingDIContainer.swift @@ -5,7 +5,6 @@ // Created by Preston Kim on 2022/11/15. // -import Foundation import UIKit final class DefaultOnboardingDIContainer { diff --git a/NearTalk/NearTalk/Application/DIContainer/RootTabBarDIContainer.swift b/NearTalk/NearTalk/Application/DIContainer/RootTabBarDIContainer.swift index 07aba4c3..0103f64d 100644 --- a/NearTalk/NearTalk/Application/DIContainer/RootTabBarDIContainer.swift +++ b/NearTalk/NearTalk/Application/DIContainer/RootTabBarDIContainer.swift @@ -5,13 +5,23 @@ // Created by 고병학 on 2022/11/18. // -import Foundation import UIKit final class RootTabBarDIContainer { // MARK: - Dependencies // MARK: - Services + func makeStorageService() -> StorageService { + return DefaultStorageService() + } + + func makeFirebaseAuthService() -> AuthService { + return DefaultFirebaseAuthService() + } + + func makeFirestoreService() -> FirestoreService { + return DefaultFirestoreService() + } // MARK: - UseCases func makeTabBarUseCase() -> TabBarUseCase { @@ -27,30 +37,34 @@ final class RootTabBarDIContainer { func makeViewModel() -> RootTabBarViewModel { return DefaultRootTabBarViewModel() } - -#warning("mapViewController DI Container 필요") -#warning("chatRoomListViewController DI Container 필요") -#warning("friendListViewController DI Container 필요") -#warning("myProfileViewController DI Container 필요") + // MARK: - Create viewController - func createTabBarController() -> RootTabBarController { - let chatRoomListRepository = DefaultChatRoomListRepository(dataTransferService: DefaultStorageService()) - let chatRoomListUseCase: FetchChatRoomUseCase = DefaultFetchChatRoomUseCase(chatRoomListRepository: chatRoomListRepository) - - let myProfileDIContainer: MyProfileDIContainer = .init() - let myProfileVC: MyProfileViewController = .init(coordinator: myProfileDIContainer.makeMyProfileCoordinator(), viewModel: myProfileDIContainer.makeViewModel()) - - let dependency: RootTabBarControllerDependency = .init( - mapViewController: MainMapViewController(), - chatRoomListViewController: ChatRoomListViewController.create(with: DefaultChatRoomListViewModel(useCase: chatRoomListUseCase)), - friendListViewController: FriendListViewController(), - myProfileViewController: myProfileVC - ) + func createTabBarController(dependency: RootTabBarControllerDependency) -> RootTabBarController { return RootTabBarController(viewModel: makeViewModel(), dependency: dependency) } // MARK: - Coordinator - func makeTabBarCoordinator(navigationController: UINavigationController?) -> RootTabBarCoordinator { - return RootTabBarCoordinator(navigationController: navigationController) + func makeTabBarCoordinator(navigationController: UINavigationController?) -> RootTabBarCoordinator? { + let chatRoomListDIContainerDependency: ChatRoomListDIContainer.Dependencies = .init( + apiDataTransferService: self.makeStorageService(), + imageDataTransferService: self.makeStorageService() + ) + let chatRoomListDIContainer: ChatRoomListDIContainer = .init(dependencies: chatRoomListDIContainerDependency) + let chatRoomListCoordinator: ChatRoomListCoordinator = chatRoomListDIContainer.makeChatRoomListCoordinator(navigationController: .init()) + + let friendListDIContainerDependencies: FriendListDIContainer.Dependencies = .init( + firestoreService: self.makeFirestoreService(), + firebaseAuthService: self.makeFirebaseAuthService() + ) + let friendListDIContainer: FriendListDIContainer = .init(dependencies: friendListDIContainerDependencies) + let friendListCoordinator: FriendListCoordinator = friendListDIContainer.makeFriendListCoordinator(navigationController: .init()) + + let dependency: RootTabBarCoordinatorDependency = .init( + mainMapCoordinator: MainMapCoordinator(), + chatRoomListCoordinator: chatRoomListCoordinator, + friendListCoordinator: friendListCoordinator, + myProfileCoordinator: MyProfileCoordinator(navigationController: .init()) + ) + return RootTabBarCoordinator(navigationController: navigationController, dependency: dependency) } } diff --git a/NearTalk/NearTalk/Data/Repositories/ChatRoomListRepository.swift b/NearTalk/NearTalk/Data/Repositories/ChatRoomListRepository.swift index c1d7336e..a1d0b01a 100644 --- a/NearTalk/NearTalk/Data/Repositories/ChatRoomListRepository.swift +++ b/NearTalk/NearTalk/Data/Repositories/ChatRoomListRepository.swift @@ -13,8 +13,12 @@ protocol ChatRoomListRepository { // fireBase 채팅방 목록 가져오기 func fetchChatRoomList() -> Observable<[ChatRoom]> func fetchUserChatRoomModel() -> Observable<[UserChatRoomModel]> - - // coreData 채팅방 목록 가져오기 -// func fetchCoreDataChatRoomList() -> Observable<[ChatRoom]> - + + func createChatRoom(_ chatRoom: ChatRoom) -> Completable + func fetchChatRoomListWithCoordinates(southWest: NCLocation, northEast: NCLocation) -> Single<[ChatRoom]> + func fetchUserChatRoomUUIDList() -> Single<[String]> + func fetchChatRoomInfo(_ chatRoomID: String) -> Single + func observeChatRoomInfo(_ chatRoomID: String) -> Observable + func fetchUserChatRoomTickets() -> Single<[UserChatRoomTicket]> + func updateUserChatRoomTicket(_ ticket: UserChatRoomTicket) -> Completable } diff --git a/NearTalk/NearTalk/Data/Repositories/DefaultAuthRepository.swift b/NearTalk/NearTalk/Data/Repositories/DefaultAuthRepository.swift index 33edf518..07d2156e 100644 --- a/NearTalk/NearTalk/Data/Repositories/DefaultAuthRepository.swift +++ b/NearTalk/NearTalk/Data/Repositories/DefaultAuthRepository.swift @@ -8,9 +8,9 @@ import RxSwift final class DefaultAuthRepository: AuthRepository { - private let authService: any FirebaseAuthService + private let authService: any AuthService - init(authService: any FirebaseAuthService) { + init(authService: any AuthService) { self.authService = authService } diff --git a/NearTalk/NearTalk/Data/Repositories/DefaultChatMessageRepository.swift b/NearTalk/NearTalk/Data/Repositories/DefaultChatMessageRepository.swift new file mode 100644 index 00000000..f7deb485 --- /dev/null +++ b/NearTalk/NearTalk/Data/Repositories/DefaultChatMessageRepository.swift @@ -0,0 +1,29 @@ +// +// DefaultChatMessageRepository.swift +// NearTalk +// +// Created by 고병학 on 2022/11/23. +// + +import Foundation +import RxSwift + +final class DefaultChatMessageRepository: ChatMessageRepository { + private let databaseService: RealTimeDatabaseService + + init(databaseService: RealTimeDatabaseService) { + self.databaseService = databaseService + } + + func sendMessage(_ message: ChatMessage) -> Completable { + self.databaseService.sendMessage(message) + } + + func fetchMessage(page: Int, skip: Int, count: Int, roomID: String) -> Single<[ChatMessage]> { + self.databaseService.fetchMessages(page: page, skip: skip, pageCount: count, roomID: roomID) + } + + func observeChatRoomMessages(roomID: String) -> Observable { + self.databaseService.observeNewMessage(roomID) + } +} diff --git a/NearTalk/NearTalk/Data/Repositories/DefaultChatRoomListRepository.swift b/NearTalk/NearTalk/Data/Repositories/DefaultChatRoomListRepository.swift index d45549ee..63085b4b 100644 --- a/NearTalk/NearTalk/Data/Repositories/DefaultChatRoomListRepository.swift +++ b/NearTalk/NearTalk/Data/Repositories/DefaultChatRoomListRepository.swift @@ -10,15 +10,81 @@ import RxSwift final class DefaultChatRoomListRepository { - private let dataTransferService: StorageService + private let dataTransferService: any StorageService + private let databaseService: any RealTimeDatabaseService + private let firestoreService: any FirestoreService + private let profileRepository: any ProfileRepository private(set) var dummyData: ChatRoomDummyData = ChatRoomDummyData() - init(dataTransferService: StorageService) { + init( + dataTransferService: any StorageService, + profileRepository: any ProfileRepository, + databaseService: any RealTimeDatabaseService, + firestoreService: any FirestoreService + ) { self.dataTransferService = dataTransferService + self.profileRepository = profileRepository + self.databaseService = databaseService + self.firestoreService = firestoreService } } extension DefaultChatRoomListRepository: ChatRoomListRepository { + func createChatRoom(_ chatRoom: ChatRoom) -> Completable { + Single.zip( + self.firestoreService.create(data: chatRoom, dataKey: .chatRoom), + self.databaseService.createChatRoom(chatRoom) + ).asCompletable() + } + + func fetchChatRoomListWithCoordinates(southWest: NCLocation, northEast: NCLocation) -> Single<[ChatRoom]> { + let queryList: [FirebaseQueryDTO] = [ + .init(key: "latitude", value: southWest.latitude, queryKey: .isGreaterThan), + .init(key: "latitude", value: northEast.latitude, queryKey: .isLessThan), + .init(key: "longitude", value: southWest.latitude, queryKey: .isGreaterThan), + .init(key: "longitude", value: northEast.latitude, queryKey: .isLessThan) + ] + return self.firestoreService.fetchList(dataKey: .chatRoom, queryList: queryList) + } + + func fetchUserChatRoomUUIDList() -> Single<[String]> { + self.profileRepository + .fetchMyProfile() + .flatMap { [weak self] (profile: UserProfile) in + guard let self, + let uuid: String = profile.uuid else { + throw ChatRoomListRepositoryError.failedToFetch + } + return self.databaseService.fetchUserChatRoomTicketList(uuid) + } + .asObservable() + .map { (tickets: [UserChatRoomTicket]) in + return tickets.compactMap({ $0.roomID }) + }.asSingle() + } + + func fetchChatRoomInfo(_ chatRoomID: String) -> Single { + self.databaseService.fetchChatRoomInfo(chatRoomID) + } + + func observeChatRoomInfo(_ chatRoomID: String) -> Observable { + self.databaseService.observeChatRoomInfo(chatRoomID) + } + + func fetchUserChatRoomTickets() -> Single<[UserChatRoomTicket]> { + self.profileRepository.fetchMyProfile() + .flatMap { [weak self] (profile: UserProfile) in + guard let self, + let uuid: String = profile.uuid else { + throw ChatRoomListRepositoryError.failedToFetch + } + return self.databaseService.fetchUserChatRoomTicketList(uuid) + } + } + + func updateUserChatRoomTicket(_ ticket: UserChatRoomTicket) -> Completable { + self.databaseService.updateUserChatRoomTicket(ticket) + } func fetchChatRoomList() -> Observable<[ChatRoom]> { return Observable<[ChatRoom]>.create { observer in @@ -60,3 +126,8 @@ struct ChatRoomDummyData { userChatRoomData.append(UserChatRoomModel(userID: "s001", chatRoomID: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"])) } } + +enum ChatRoomListRepositoryError: Error { + case failedToFetch + case failedToCrate +} diff --git a/NearTalk/NearTalk/Data/Repositories/DefaultMediaRepository.swift b/NearTalk/NearTalk/Data/Repositories/DefaultMediaRepository.swift new file mode 100644 index 00000000..c930459a --- /dev/null +++ b/NearTalk/NearTalk/Data/Repositories/DefaultMediaRepository.swift @@ -0,0 +1,25 @@ +// +// DefaultMediaRepository.swift +// NearTalk +// +// Created by 고병학 on 2022/11/23. +// + +import Foundation +import RxSwift + +final class DefaultMediaRepository: MediaRepository { + private let storageService: StorageService + + init(storageService: StorageService) { + self.storageService = storageService + } + + func uploadImage(_ imageData: Data) -> RxSwift.Single { + self.storageService.uploadData(data: imageData, fileName: "\(UUID().uuidString).jpg", dataType: .images) + } + + func uploadVideo(_ videoData: Data) -> RxSwift.Single { + self.storageService.uploadData(data: videoData, fileName: "\(UUID().uuidString).mov", dataType: .videos) + } +} diff --git a/NearTalk/NearTalk/Data/Repositories/DefaultProfileRepository.swift b/NearTalk/NearTalk/Data/Repositories/DefaultProfileRepository.swift index 219af689..6e7709ad 100644 --- a/NearTalk/NearTalk/Data/Repositories/DefaultProfileRepository.swift +++ b/NearTalk/NearTalk/Data/Repositories/DefaultProfileRepository.swift @@ -10,9 +10,9 @@ import RxSwift final class DefaultProfileRepository: ProfileRepository { private let firestoreService: FirestoreService - private let firebaseAuthService: FirebaseAuthService + private let firebaseAuthService: AuthService - init(firestoreService: FirestoreService, firebaseAuthService: FirebaseAuthService) { + init(firestoreService: FirestoreService, firebaseAuthService: AuthService) { self.firestoreService = firestoreService self.firebaseAuthService = firebaseAuthService } diff --git a/NearTalk/NearTalk/Domain/Entities/ChatRoom.swift b/NearTalk/NearTalk/Domain/Entities/ChatRoom.swift index d11e2b4c..40af0bd2 100644 --- a/NearTalk/NearTalk/Domain/Entities/ChatRoom.swift +++ b/NearTalk/NearTalk/Domain/Entities/ChatRoom.swift @@ -18,4 +18,5 @@ struct ChatRoom: BaseEntity { var accessibleRadius: Double? var recentMessageID: String? var maxNumberOfParticipants: Int? + var messageCount: Int? } diff --git a/NearTalk/NearTalk/Domain/Entities/UserChatRoomTicket.swift b/NearTalk/NearTalk/Domain/Entities/UserChatRoomTicket.swift new file mode 100644 index 00000000..2dce498c --- /dev/null +++ b/NearTalk/NearTalk/Domain/Entities/UserChatRoomTicket.swift @@ -0,0 +1,16 @@ +// +// UserChatRoomTicket.swift +// NearTalk +// +// Created by 고병학 on 2022/11/23. +// + +import Foundation + +struct UserChatRoomTicket: BaseEntity { + var uuid: String? + var userID: String? + var roomID: String? + var lastReadMessageID: String? + var lastRoomMessageCount: Int? +} diff --git a/NearTalk/NearTalk/Domain/Interfaces/Repositories/ChatMessageRepository.swift b/NearTalk/NearTalk/Domain/Interfaces/Repositories/ChatMessageRepository.swift new file mode 100644 index 00000000..be046d00 --- /dev/null +++ b/NearTalk/NearTalk/Domain/Interfaces/Repositories/ChatMessageRepository.swift @@ -0,0 +1,15 @@ +// +// ChatMessageRepository.swift +// NearTalk +// +// Created by 고병학 on 2022/11/23. +// + +import Foundation +import RxSwift + +protocol ChatMessageRepository { + func sendMessage(_ message: ChatMessage) -> Completable + func fetchMessage(page: Int, skip: Int, count: Int, roomID: String) -> Single<[ChatMessage]> + func observeChatRoomMessages(roomID: String) -> Observable +} diff --git a/NearTalk/NearTalk/Domain/Interfaces/Repositories/MediaRepository.swift b/NearTalk/NearTalk/Domain/Interfaces/Repositories/MediaRepository.swift new file mode 100644 index 00000000..14d8907d --- /dev/null +++ b/NearTalk/NearTalk/Domain/Interfaces/Repositories/MediaRepository.swift @@ -0,0 +1,14 @@ +// +// MediaRepository.swift +// NearTalk +// +// Created by 고병학 on 2022/11/23. +// + +import Foundation +import RxSwift + +protocol MediaRepository { + func uploadImage(_ imageData: Data) -> Single + func uploadVideo(_ videoData: Data) -> Single +} diff --git a/NearTalk/NearTalk/Domain/UseCases/FetchChatRoomUseCase.swift b/NearTalk/NearTalk/Domain/UseCases/FetchChatRoomUseCase.swift index 6f81d86e..aec9aabf 100644 --- a/NearTalk/NearTalk/Domain/UseCases/FetchChatRoomUseCase.swift +++ b/NearTalk/NearTalk/Domain/UseCases/FetchChatRoomUseCase.swift @@ -6,28 +6,39 @@ // import Foundation +import RxCocoa import RxSwift protocol FetchChatRoomUseCase { // func getGroupChatListCoreData() -> Observable<[GroupChatRoomListData]> // func getDataOfDMChatCoreData() -> Observable<[DMChatRoomListData]> - func getGroupChatList() -> Observable<[GroupChatRoomListData]> - func getDMChatList() -> Observable<[DMChatRoomListData]> + func getGroupChatList() -> Observable<[GroupChatRoomListData]> + func getDMChatList() -> Observable<[DMChatRoomListData]> + + func newObserveGroupChatList() -> Observable<[GroupChatRoomListData]> + func newObserveDMChatList() -> Observable<[DMChatRoomListData]> + func getGroupChatListWithCoordinates(southWest: NCLocation, northEast: NCLocation) -> Single<[ChatRoom]> + func getUserChatRoomTickets() -> Single<[UserChatRoomTicket]> } final class DefaultFetchChatRoomUseCase: FetchChatRoomUseCase { - private let disposeBag = DisposeBag() - private let chatRoomListRepository: ChatRoomListRepository! + private let disposeBag: DisposeBag = .init() + private let chatRoomListRepository: ChatRoomListRepository private let chatRoom: Observable<[ChatRoom]> private let userChatRoomModel: Observable<[UserChatRoomModel]> + private var newChatRoomUUIDList: PublishRelay<[String]> = .init() + private lazy var newChatRoom: Observable<[ChatRoom]> = self.newGetChatRoomList() + init(chatRoomListRepository: ChatRoomListRepository) { self.chatRoomListRepository = chatRoomListRepository self.chatRoom = self.chatRoomListRepository.fetchChatRoomList() self.userChatRoomModel = self.chatRoomListRepository.fetchUserChatRoomModel() + + self.newGetChatRoomUUIDList() } func getGroupChatList() -> Observable<[GroupChatRoomListData]> { @@ -44,4 +55,52 @@ final class DefaultFetchChatRoomUseCase: FetchChatRoomUseCase { .map { $0.map { DMChatRoomListData(data: $0) } } } + func newObserveGroupChatList() -> Observable<[GroupChatRoomListData]> { + self.newChatRoom + .map { $0.filter { $0.roomType == "group" } } + .map { $0.map { GroupChatRoomListData(data: $0) } } + } + + func newObserveDMChatList() -> Observable<[DMChatRoomListData]> { + self.newChatRoom + .map { $0.filter { $0.roomType == "dm" } } + .map { $0.map { DMChatRoomListData(data: $0) } } + } + + func getGroupChatListWithCoordinates(southWest: NCLocation, northEast: NCLocation) -> Single<[ChatRoom]> { + self.chatRoomListRepository.fetchChatRoomListWithCoordinates(southWest: southWest, northEast: northEast) + } + + func getUserChatRoomTickets() -> Single<[UserChatRoomTicket]> { + self.chatRoomListRepository.fetchUserChatRoomTickets() + } + + // MARK: - Private + private func newGetChatRoomList() -> Observable<[ChatRoom]> { + self.newChatRoomUUIDList + .flatMap { [weak self] (uuidList: [String]) in + guard let self else { + throw FetchChatRoomUseCaseError.failedToFetchRoom + } + let fetchChatRoomList: [Observable] = uuidList.map { + self.chatRoomListRepository.observeChatRoomInfo($0) + } + return Observable.combineLatest(fetchChatRoomList) + } + } + + private func newGetChatRoomUUIDList() { + self.chatRoomListRepository.fetchUserChatRoomUUIDList() + .subscribe { [weak self] (uuidList: [String]) in + guard let self else { + return + } + self.newChatRoomUUIDList.accept(uuidList) + }.disposed(by: disposeBag) + } +} + +// MARK: - FetchChatRoomUseCaseError +enum FetchChatRoomUseCaseError: Error { + case failedToFetchRoom } diff --git a/NearTalk/NearTalk/Domain/UseCases/LoginUseCase.swift b/NearTalk/NearTalk/Domain/UseCases/LoginUseCase.swift index e394698f..6a08bada 100644 --- a/NearTalk/NearTalk/Domain/UseCases/LoginUseCase.swift +++ b/NearTalk/NearTalk/Domain/UseCases/LoginUseCase.swift @@ -13,9 +13,9 @@ protocol LoginUseCase { } final class DefaultLoginUseCase: LoginUseCase { - private let authService: FirebaseAuthService + private let authService: AuthService - init(authService: FirebaseAuthService) { + init(authService: AuthService) { self.authService = authService } diff --git a/NearTalk/NearTalk/Domain/UseCases/UploadChatRoomInfoUseCase.swift b/NearTalk/NearTalk/Domain/UseCases/UploadChatRoomInfoUseCase.swift new file mode 100644 index 00000000..75680496 --- /dev/null +++ b/NearTalk/NearTalk/Domain/UseCases/UploadChatRoomInfoUseCase.swift @@ -0,0 +1,33 @@ +// +// UploadChatRoomInfoUseCase.swift +// NearTalk +// +// Created by 고병학 on 2022/11/23. +// + +import Foundation +import RxSwift + +protocol UploadChatRoomInfoUseCase { + /// 이미지 업로드 후 파이어베이스 스토리지 경로를 반환한다. + func uploadImage(_ imageData: Data) -> Single + func createChatRoom(_ chatRoom: ChatRoom) -> Completable +} + +final class DefaultUploadChatRoomInfoUseCase: UploadChatRoomInfoUseCase { + private let mediaRepository: MediaRepository + private let chatRoomRepository: ChatRoomListRepository + + init(mediaRepository: MediaRepository, chatRoomRepository: ChatRoomListRepository) { + self.mediaRepository = mediaRepository + self.chatRoomRepository = chatRoomRepository + } + + func uploadImage(_ imageData: Data) -> Single { + self.mediaRepository.uploadImage(imageData) + } + + func createChatRoom(_ chatRoom: ChatRoom) -> Completable { + self.chatRoomRepository.createChatRoom(chatRoom) + } +} diff --git a/NearTalk/NearTalk/Domain/UseCases/VerifyUserUseCase.swift b/NearTalk/NearTalk/Domain/UseCases/VerifyUserUseCase.swift index 7815afb5..a6a09c56 100644 --- a/NearTalk/NearTalk/Domain/UseCases/VerifyUserUseCase.swift +++ b/NearTalk/NearTalk/Domain/UseCases/VerifyUserUseCase.swift @@ -13,9 +13,9 @@ protocol VerifyUserUseCase { } final class DefaultVerifyUserUseCase: VerifyUserUseCase { - private let authService: FirebaseAuthService + private let authService: AuthService - init(authService: FirebaseAuthService) { + init(authService: AuthService) { self.authService = authService } diff --git a/NearTalk/NearTalk/Infrastructure/Network/AuthService.swift b/NearTalk/NearTalk/Infrastructure/Network/AuthService.swift new file mode 100644 index 00000000..f984a3e8 --- /dev/null +++ b/NearTalk/NearTalk/Infrastructure/Network/AuthService.swift @@ -0,0 +1,17 @@ +// +// AuthService.swift +// NearTalk +// +// Created by 고병학 on 2022/11/22. +// + +import Foundation +import RxSwift + +protocol AuthService { + func verifyUser() -> Completable + func fetchCurrentUserEmail() -> Single + func loginWithApple(token idTokenString: String, nonce: String) -> Completable + func logout() -> Completable + func deleteCurrentUser() -> Completable +} diff --git a/NearTalk/NearTalk/Infrastructure/Network/FirebaseAuthService.swift b/NearTalk/NearTalk/Infrastructure/Network/FirebaseAuthService.swift index 70e30e73..7c7ba92c 100644 --- a/NearTalk/NearTalk/Infrastructure/Network/FirebaseAuthService.swift +++ b/NearTalk/NearTalk/Infrastructure/Network/FirebaseAuthService.swift @@ -9,15 +9,7 @@ import FirebaseAuth import Foundation import RxSwift -protocol FirebaseAuthService { - func verifyUser() -> Completable - func fetchCurrentUserEmail() -> Single - func loginWithApple(token idTokenString: String, nonce: String) -> Completable - func logout() -> Completable - func deleteCurrentUser() -> Completable -} - -final class DefaultFirebaseAuthService: FirebaseAuthService { +final class DefaultFirebaseAuthService: AuthService { /// 유저 로그인 확인 func verifyUser() -> Completable { diff --git a/NearTalk/NearTalk/Infrastructure/Network/FirebaseKey.swift b/NearTalk/NearTalk/Infrastructure/Network/FirebaseKey.swift index ed135f5d..80d9b9a5 100644 --- a/NearTalk/NearTalk/Infrastructure/Network/FirebaseKey.swift +++ b/NearTalk/NearTalk/Infrastructure/Network/FirebaseKey.swift @@ -5,7 +5,6 @@ // Created by 고병학 on 2022/11/17. // -import FirebaseFirestore import Foundation enum FirebaseKey { @@ -16,8 +15,10 @@ enum FirebaseKey { } enum RealtimeDB: String { + case users case chatRooms case chatMessages + case userChatRoomTickets } enum Storage: String { diff --git a/NearTalk/NearTalk/Infrastructure/Network/FirestoreService.swift b/NearTalk/NearTalk/Infrastructure/Network/FirestoreService.swift index d47d2166..340fbf3a 100644 --- a/NearTalk/NearTalk/Infrastructure/Network/FirestoreService.swift +++ b/NearTalk/NearTalk/Infrastructure/Network/FirestoreService.swift @@ -18,7 +18,7 @@ protocol FirestoreService { /// 객체 수정 func update(updatedData: T, dataKey: FirebaseKey.FireStore) -> Single /// 객체 삭제 - func delete(data: T, dataKey: FirebaseKey.FireStore ) -> Completable + func delete(data: T, dataKey: FirebaseKey.FireStore) -> Completable /// 객체 리스트 불러오기 func fetchList(dataKey: FirebaseKey.FireStore, queryList: [FirebaseQueryDTO]) -> Single<[T]> diff --git a/NearTalk/NearTalk/Infrastructure/Network/RealTimeDatabaseService.swift b/NearTalk/NearTalk/Infrastructure/Network/RealTimeDatabaseService.swift index 2b9843f8..f36d3722 100644 --- a/NearTalk/NearTalk/Infrastructure/Network/RealTimeDatabaseService.swift +++ b/NearTalk/NearTalk/Infrastructure/Network/RealTimeDatabaseService.swift @@ -10,84 +10,228 @@ import Foundation import RxSwift protocol RealTimeDatabaseService { - func sendMessage(_ message: ChatMessage, mediaData: Data?) -> Single - func observeChatRoom(_ chatRoomID: String) -> Observable<[ChatMessage]> + // MARK: 채팅 메시지 + func sendMessage(_ message: ChatMessage) -> Completable + func fetchMessages(page: Int, skip: Int, pageCount: Int, roomID: String) -> Single<[ChatMessage]> + func observeNewMessage(_ chatRoomID: String) -> Observable + + // MARK: 채팅방 정보 + func createChatRoom(_ chatRoom: ChatRoom) -> Single + func fetchChatRoomInfo(_ chatRoomID: String) -> Single + func observeChatRoomInfo(_ chatRoomID: String) -> Observable + + // MARK: 유저-채팅방 티켓 정보 + func createUserChatRoomTicket(_ ticket: UserChatRoomTicket) -> Completable + func updateUserChatRoomTicket(_ ticket: UserChatRoomTicket) -> Completable + func fetchUserChatRoomTicketList(_ userID: String) -> Single<[UserChatRoomTicket]> } /// Firestore RealTimeDatabase 저장소를 관리하는 서비스 final class DefaultRealTimeDatabaseService: RealTimeDatabaseService { let ref: DatabaseReference - var chatRoomHandler: DatabaseHandle? - + var newMessageHandler: DatabaseHandle? + init() { self.ref = Database.database().reference() } -} - -// MARK: - 채팅 송수신 -extension DefaultRealTimeDatabaseService { - func sendMessage(_ message: ChatMessage, mediaData: Data? = nil) -> Single { - Single.create { [weak self] single in + + // MARK: - 채팅 메시지 + func sendMessage(_ message: ChatMessage) -> Completable { + Completable.create { [weak self] completable in guard let self, let roomID: String = message.chatRoomID, - let messageID: String = message.uuid - else { return Disposables.create() } + let messageID: String = message.uuid, + let messageData: [String: Any] = try? message.encode() else { + completable(.error(DatabaseError.failedToSend)) + return Disposables.create() + } self.ref .child(FirebaseKey.RealtimeDB.chatRooms.rawValue) .child(roomID) .child(FirebaseKey.RealtimeDB.chatMessages.rawValue) .child(messageID) - .setValue(message) + .setValue(messageData) + completable(.completed) + return Disposables.create() + } + } + + func fetchMessages(page: Int, skip: Int, pageCount: Int, roomID: String) -> Single<[ChatMessage]> { + Single<[ChatMessage]>.create { [weak self] single in + guard let self else { + single(.failure(DatabaseError.failedToFetch)) + return Disposables.create() + } - single(.success(true)) + self.ref + .child(FirebaseKey.RealtimeDB.chatRooms.rawValue) + .child(roomID) + .child(FirebaseKey.RealtimeDB.chatMessages.rawValue) + .queryStarting(atValue: skip) + .queryLimited(toFirst: UInt(pageCount)) + .observeSingleEvent(of: .value) { snapshot in + if let value: [[String: Any]] = snapshot.value as? [[String: Any]] { + let chatMessages: [ChatMessage] = value.compactMap({ try? ChatMessage.decode(dictionary: $0) }) + single(.success(chatMessages)) + } + } return Disposables.create() } } - func observeChatRoom(_ chatRoomID: String) -> Observable<[ChatMessage]> { - Observable<[ChatMessage]>.create { [weak self] observer in + func observeNewMessage(_ chatRoomID: String) -> Observable { + Observable.create { [weak self] observer in guard let self else { + observer.onError(DatabaseError.failedToFetch) return Disposables.create() } - self.chatRoomHandler = self.ref + self.newMessageHandler = self.ref .child(FirebaseKey.RealtimeDB.chatRooms.rawValue) .child(chatRoomID) - .observe(.value) { snapshot in - guard let value = snapshot.value as? [[String: Any]] else { - observer.onError(DatabaseError.failedToFetch) - return + .child(FirebaseKey.RealtimeDB.chatMessages.rawValue) + .observe(.childAdded) { (snapshot) -> Void in + if let value: [String: Any] = snapshot.value as? [String: Any], + let chatMessage: ChatMessage = try? ChatMessage.decode(dictionary: value) { + observer.onNext(chatMessage) } - - #warning("pagination") - - let conversations: [ChatMessage] = value.compactMap({ dictionary in - guard let conversationId = dictionary["id"] as? String, - let name = dictionary["name"] as? String, - let otherUserEmail = dictionary["other_user_email"] as? String, - let latestMessage = dictionary["latest_message"] as? [String: Any], - let sent = latestMessage["date"] as? String, - let message = latestMessage["message"] as? String, - let isRead = latestMessage["is_read"] as? Bool else { - return nil - } - return ChatMessage() - }) + } + return Disposables.create() + } + } + + // MARK: - 채팅방 정보 + func createChatRoom(_ chatRoom: ChatRoom) -> Single { + Single.create { [weak self] single in + guard let self, + let uuid: String = chatRoom.uuid, + let chatRoomData: [String: Any] = try? chatRoom.encode() else { + single(.failure(DatabaseError.failedToFetch)) + return Disposables.create() + } + + self.ref + .child(FirebaseKey.RealtimeDB.chatRooms.rawValue) + .child(uuid) + .setValue(chatRoomData) - observer.onNext(conversations) + single(.success(chatRoom)) + return Disposables.create() + } + } + + func fetchChatRoomInfo(_ chatRoomID: String) -> Single { + Single.create { [weak self] single in + guard let self else { + single(.failure(DatabaseError.failedToFetch)) + return Disposables.create() + } + + self.ref + .child(FirebaseKey.RealtimeDB.chatRooms.rawValue) + .child(chatRoomID) + .observeSingleEvent(of: .value) { snapshot in + if let value: [String: Any] = snapshot.value as? [String: Any], + let chatRoom: ChatRoom = try? ChatRoom.decode(dictionary: value) { + single(.success(chatRoom)) + } } return Disposables.create() } } - func observeChatRoomList() { - + func observeChatRoomInfo(_ chatRoomID: String) -> Observable { + Observable.create { [weak self] observable in + guard let self else { + observable.onError(DatabaseError.failedToFetch) + return Disposables.create() + } + + self.ref + .child(FirebaseKey.RealtimeDB.chatRooms.rawValue) + .child(chatRoomID) + .observe(.value) { snapshot in + if let value: [String: Any] = snapshot.value as? [String: Any], + let chatRoom: ChatRoom = try? ChatRoom.decode(dictionary: value) { + observable.onNext(chatRoom) + } + } + + return Disposables.create() + } + } + + // MARK: 유저-채팅방 티켓 정보 + func createUserChatRoomTicket(_ ticket: UserChatRoomTicket) -> Completable { + Completable.create { [weak self] completable in + guard let self, + let uuid: String = ticket.uuid, + let userID: String = ticket.userID, + let ticketData: [String: Any] = try? ticket.encode() else { + completable(.error(DatabaseError.failedToCreate)) + return Disposables.create() + } + + self.ref + .child(FirebaseKey.RealtimeDB.users.rawValue) + .child(userID) + .child(FirebaseKey.RealtimeDB.userChatRoomTickets.rawValue) + .child(uuid) + .setValue(ticketData) + completable(.completed) + return Disposables.create() + } + } + + func updateUserChatRoomTicket(_ ticket: UserChatRoomTicket) -> Completable { + Completable.create { [weak self] completable in + guard let self, + let uuid: String = ticket.uuid, + let userID: String = ticket.userID, + let ticketData: [String: Any] = try? ticket.encode() else { + completable(.error(DatabaseError.failedToCreate)) + return Disposables.create() + } + + self.ref + .child(FirebaseKey.RealtimeDB.users.rawValue) + .child(userID) + .child(FirebaseKey.RealtimeDB.userChatRoomTickets.rawValue) + .child(uuid) + .updateChildValues(ticketData) + completable(.completed) + return Disposables.create() + } + } + + func fetchUserChatRoomTicketList(_ userID: String) -> Single<[UserChatRoomTicket]> { + Single<[UserChatRoomTicket]>.create { [weak self] single in + guard let self else { + single(.failure(DatabaseError.failedToFetch)) + return Disposables.create() + } + + self.ref + .child(FirebaseKey.RealtimeDB.users.rawValue) + .child(userID) + .child(FirebaseKey.RealtimeDB.userChatRoomTickets.rawValue) + .observeSingleEvent(of: .value) { snapshot in + if let value: [[String: Any]] = snapshot.value as? [[String: Any]] { + let userChatRoomTicket: [UserChatRoomTicket] = value.compactMap({ try? UserChatRoomTicket.decode(dictionary: $0) }) + single(.success(userChatRoomTicket)) + } + } + + return Disposables.create() + } } } enum DatabaseError: Error { + case failedToSend case failedToFetch + case failedToCreate } diff --git a/NearTalk/NearTalk/Infrastructure/Network/StorageService.swift b/NearTalk/NearTalk/Infrastructure/Network/StorageService.swift index 4fb2e81f..99259591 100644 --- a/NearTalk/NearTalk/Infrastructure/Network/StorageService.swift +++ b/NearTalk/NearTalk/Infrastructure/Network/StorageService.swift @@ -10,6 +10,7 @@ import Foundation import RxSwift protocol StorageService { + /// Firebase storage에 저장하고 저장된 path(URL x)를 반환한다. func uploadData(data: Data, fileName: String, dataType: FirebaseKey.Storage) -> Single func downloadURL(for path: String) -> Single } diff --git a/NearTalk/NearTalk/NearTalk.entitlements b/NearTalk/NearTalk/NearTalk.entitlements new file mode 100644 index 00000000..a812db50 --- /dev/null +++ b/NearTalk/NearTalk/NearTalk.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.applesignin + + Default + + + diff --git a/NearTalk/NearTalk/Presentation/ChatRoomList/Coordinator/ChatRoomListCoordinator.swift b/NearTalk/NearTalk/Presentation/ChatRoomList/Coordinator/ChatRoomListCoordinator.swift index 40280837..4bfa3ef4 100644 --- a/NearTalk/NearTalk/Presentation/ChatRoomList/Coordinator/ChatRoomListCoordinator.swift +++ b/NearTalk/NearTalk/Presentation/ChatRoomList/Coordinator/ChatRoomListCoordinator.swift @@ -13,8 +13,9 @@ protocol ChatRoomListCoordinatorDependencies { func makeCreateChatRoomViewController() } -final class ChatRoomListCoordinator { - private weak var navigationController: UINavigationController? +final class ChatRoomListCoordinator: Coordinator { + weak var navigationController: UINavigationController? + var parentCoordinator: Coordinator? private let dependencies: ChatRoomListCoordinatorDependencies private weak var chatRoomListViewController: ChatRoomListViewController? diff --git a/NearTalk/NearTalk/Presentation/FriendList/Coordinator/FriendListCoordinator.swift b/NearTalk/NearTalk/Presentation/FriendList/Coordinator/FriendListCoordinator.swift index a8315e10..c4eaa200 100644 --- a/NearTalk/NearTalk/Presentation/FriendList/Coordinator/FriendListCoordinator.swift +++ b/NearTalk/NearTalk/Presentation/FriendList/Coordinator/FriendListCoordinator.swift @@ -14,8 +14,9 @@ protocol FriendListCoordinatorDependencies { // func makeProfileDetailViewController() } -final class FriendListCoordinator { - private weak var navigationController: UINavigationController? +final class FriendListCoordinator: Coordinator { + weak var parentCoordinator: Coordinator? + weak var navigationController: UINavigationController? private let dependencies: FriendListCoordinatorDependencies private weak var friendListViewController: FriendListViewController? diff --git a/NearTalk/NearTalk/Presentation/LaucnScreen/ViewModel/LaunchScreenViewModel.swift b/NearTalk/NearTalk/Presentation/LaucnScreen/ViewModel/LaunchScreenViewModel.swift index a7c6d337..9aa9ca6b 100644 --- a/NearTalk/NearTalk/Presentation/LaucnScreen/ViewModel/LaunchScreenViewModel.swift +++ b/NearTalk/NearTalk/Presentation/LaucnScreen/ViewModel/LaunchScreenViewModel.swift @@ -6,7 +6,6 @@ // import Foundation -import RxCocoa import RxSwift struct LaunchScreenViewModelActions { diff --git a/NearTalk/NearTalk/Presentation/Login/View/LoginViewController.swift b/NearTalk/NearTalk/Presentation/Login/View/LoginViewController.swift index 9f5092ef..929469f2 100644 --- a/NearTalk/NearTalk/Presentation/Login/View/LoginViewController.swift +++ b/NearTalk/NearTalk/Presentation/Login/View/LoginViewController.swift @@ -7,54 +7,100 @@ import AuthenticationServices import RxCocoa +import RxGesture +import RxSwift import SnapKit import Then import UIKit - -final class LoginViewController: UIViewController { +final class LoginViewController: UIViewController, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { + func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + return self.view.window! + } private let logoView = UIImageView(image: UIImage(systemName: "map.circle.fill")) - private let loginButton = ASAuthorizationAppleIDButton(type: .default, style: .black).then { $0.cornerRadius = 5 } - + private let firebaseAuthService: DefaultFirebaseAuthService = DefaultFirebaseAuthService() + private let disposeBag: DisposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() configureUI() configureConstraint() + view.backgroundColor = .white + loginButton.rx.tapGesture() + .bind { + if $0.state == .ended { + self.bindToLoginButton() + } + } + .disposed(by: disposeBag) + } + // init(coordinator: LoginCoordinato) { + // self.coordinator = coordinator + // super.init(nibName: nil, bundle: nil) + // } + // + // required init?(coder: NSCoder) { + // fatalError(“init(coder:) has not been implemented”) + // } + func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + switch authorization.credential { + case let appleIDCredential as ASAuthorizationAppleIDCredential: + guard let userIdentifier = appleIDCredential.identityToken, let idTokenString = String(data: userIdentifier, encoding: .utf8) else { +#if DEBUG + print("Faile to fetch id token") +#endif + return + } + print(appleIDCredential.email) + print(appleIDCredential.identityToken) + print(idTokenString) + firebaseAuthService.loginWithApple(token: idTokenString, nonce: NonceGenerator.randomNonceString()) + .subscribe(onCompleted: { + print("success") + }, onError: { + print("failed: \($0)") + }) + .disposed(by: disposeBag) + default: + break + } + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { +#if DEBUG + print("apple authorization error: \(error)") +#endif } } - private extension LoginViewController { func configureUI() { view.addSubview(logoView) view.addSubview(loginButton) } - func configureConstraint() { self.logoView.snp.makeConstraints { (make) in make.top.equalToSuperview().offset(30) - make.bottom.equalTo(loginButton.snp.top).offset(-30) - make.height.equalTo(logoView.snp.width) + make.height.width.equalTo(60) make.centerX.equalToSuperview() } self.loginButton.snp.makeConstraints { (make) in make.horizontalEdges.equalToSuperview().inset(30) - make.centerY.equalToSuperview() + make.bottom.equalTo(view.safeAreaLayoutGuide).offset(10) make.height.equalTo(loginButton.snp.width).multipliedBy(0.1) } } -} - -#if canImport(SwiftUI) && DEBUG -import SwiftUI - -// swiftlint:disable: type_name -struct LogInViewController_Preview: PreviewProvider { - static var previews: some View { - LoginViewController().showPreview(.iPhone14Pro) - LoginViewController().showPreview(.iPhoneSE3) + + func bindToLoginButton() { + let appleIDProvider: ASAuthorizationAppleIDProvider = ASAuthorizationAppleIDProvider() + let request: ASAuthorizationAppleIDRequest = appleIDProvider.createRequest() + request.requestedScopes = [.email, .fullName] +// request.nonce = NonceGenerator.randomNonceString() + + let authorizationController: ASAuthorizationController = ASAuthorizationController(authorizationRequests: [request]) + authorizationController.delegate = self + authorizationController.presentationContextProvider = self + authorizationController.performRequests() } } -#endif diff --git a/NearTalk/NearTalk/Presentation/MainMap/Coordinator/MainMapCoordinator.swift b/NearTalk/NearTalk/Presentation/MainMap/Coordinator/MainMapCoordinator.swift index 191e420a..d4f2cf37 100644 --- a/NearTalk/NearTalk/Presentation/MainMap/Coordinator/MainMapCoordinator.swift +++ b/NearTalk/NearTalk/Presentation/MainMap/Coordinator/MainMapCoordinator.swift @@ -5,8 +5,14 @@ // Created by lymchgmk on 2022/11/14. // -import Foundation +import UIKit -final class MainMapCoordinator { +final class MainMapCoordinator: Coordinator { + var navigationController: UINavigationController? + var parentCoordinator: Coordinator? + + func start() { + + } } diff --git a/NearTalk/NearTalk/Presentation/TabBar/Coordinator/RootTabBarCoordinator.swift b/NearTalk/NearTalk/Presentation/TabBar/Coordinator/RootTabBarCoordinator.swift index 119ccf12..cf99c71e 100644 --- a/NearTalk/NearTalk/Presentation/TabBar/Coordinator/RootTabBarCoordinator.swift +++ b/NearTalk/NearTalk/Presentation/TabBar/Coordinator/RootTabBarCoordinator.swift @@ -7,17 +7,58 @@ import UIKit +struct RootTabBarCoordinatorDependency { + let mainMapCoordinator: MainMapCoordinator + let chatRoomListCoordinator: ChatRoomListCoordinator + let friendListCoordinator: FriendListCoordinator + let myProfileCoordinator: MyProfileCoordinator +} + final class RootTabBarCoordinator: Coordinator { var navigationController: UINavigationController? var parentCoordinator: Coordinator? - init(navigationController: UINavigationController?) { + private var mainMapCoordinator: MainMapCoordinator? + private var chatRoomListCoordinator: ChatRoomListCoordinator? + private var friendListCoordinator: FriendListCoordinator? + private var myProfileCoordinator: MyProfileCoordinator? + + init(navigationController: UINavigationController?, dependency: RootTabBarCoordinatorDependency) { self.navigationController = navigationController + self.mainMapCoordinator = dependency.mainMapCoordinator + self.chatRoomListCoordinator = dependency.chatRoomListCoordinator + self.friendListCoordinator = dependency.friendListCoordinator + self.myProfileCoordinator = dependency.myProfileCoordinator + + self.assignParentCoordinator() } func start() { - let viewcontroller: RootTabBarController = RootTabBarDIContainer().createTabBarController() + let viewcontroller: RootTabBarController = RootTabBarDIContainer().createTabBarController(dependency: makeDependency()) self.navigationController?.viewControllers.insert(viewcontroller, at: 0) self.navigationController?.popToRootViewController(animated: false) } + +#warning("myProfileViewController DI Container 필요") + private func makeDependency() -> RootTabBarControllerDependency { + + let myProfileDIContainer: MyProfileDIContainer = .init() + let myProfileCoordinator: MyProfileCoordinator = myProfileDIContainer.makeMyProfileCoordinator() + self.myProfileCoordinator = myProfileCoordinator + let myProfileVC: MyProfileViewController = .init(coordinator: myProfileCoordinator, viewModel: myProfileDIContainer.makeViewModel()) + + return .init( + mainMapNavigationController: .init(), + chatRoomListNavigationController: .init(), + friendListNavigationController: .init(), + myProfileNavigationController: .init() + ) + } + + private func assignParentCoordinator() { + self.mainMapCoordinator?.parentCoordinator = self + self.chatRoomListCoordinator?.parentCoordinator = self + self.friendListCoordinator?.parentCoordinator = self + self.myProfileCoordinator?.parentCoordinator = self + } } diff --git a/NearTalk/NearTalk/Presentation/TabBar/View/RootTabBarController.swift b/NearTalk/NearTalk/Presentation/TabBar/View/RootTabBarController.swift index 997657e6..6dd511d0 100644 --- a/NearTalk/NearTalk/Presentation/TabBar/View/RootTabBarController.swift +++ b/NearTalk/NearTalk/Presentation/TabBar/View/RootTabBarController.swift @@ -8,10 +8,10 @@ import UIKit struct RootTabBarControllerDependency { - let mapViewController: MainMapViewController - let chatRoomListViewController: ChatRoomListViewController - let friendListViewController: FriendListViewController - let myProfileViewController: MyProfileViewController + let mainMapNavigationController: UINavigationController + let chatRoomListNavigationController: UINavigationController + let friendListNavigationController: UINavigationController + let myProfileNavigationController: UINavigationController } final class RootTabBarController: UITabBarController { @@ -48,25 +48,25 @@ final class RootTabBarController: UITabBarController { private func configureViewControllers() { self.viewControllers = [ self.embed( - rootVC: self.dependency.mapViewController, + rootNav: self.dependency.mainMapNavigationController, title: "홈", inactivatedImage: UIImage(systemName: "house")?.withTintColor(.darkGray), activatedImage: UIImage(systemName: "house.fill")?.withTintColor(.blue) ), self.embed( - rootVC: self.dependency.chatRoomListViewController, + rootNav: self.dependency.chatRoomListNavigationController, title: "채팅", inactivatedImage: UIImage(systemName: "message")?.withTintColor(.darkGray), activatedImage: UIImage(systemName: "message.fill")?.withTintColor(.blue) ), self.embed( - rootVC: self.dependency.friendListViewController, + rootNav: self.dependency.friendListNavigationController, title: "친구", inactivatedImage: UIImage(systemName: "figure.2.arms.open")?.withTintColor(.darkGray), activatedImage: UIImage(systemName: "figure.2.arms.open")?.withTintColor(.blue) ), self.embed( - rootVC: self.dependency.myProfileViewController, + rootNav: self.dependency.myProfileNavigationController, title: "마이페이지", inactivatedImage: UIImage(systemName: "figure.wave")?.withTintColor(.darkGray), activatedImage: UIImage(systemName: "figure.wave")?.withTintColor(.blue) @@ -75,18 +75,17 @@ final class RootTabBarController: UITabBarController { } private func embed( - rootVC: UIViewController, + rootNav: UINavigationController, title: String?, inactivatedImage: UIImage?, activatedImage: UIImage? ) -> UIViewController { - let navC = UINavigationController(rootViewController: rootVC) let tabBarItem = UITabBarItem( title: title, image: inactivatedImage?.withRenderingMode(.alwaysOriginal), selectedImage: activatedImage?.withRenderingMode(.alwaysOriginal) ) - navC.tabBarItem = tabBarItem - return navC + rootNav.tabBarItem = tabBarItem + return rootNav } }