diff --git a/Mogakco/Sources/App/DIContainer.swift b/Mogakco/Sources/App/DIContainer.swift index d4602921..04eb8020 100644 --- a/Mogakco/Sources/App/DIContainer.swift +++ b/Mogakco/Sources/App/DIContainer.swift @@ -80,6 +80,7 @@ final class DIContainer { repository.chatRoomDataSource = resolver.resolve(ChatRoomDataSourceProtocol.self) repository.localUserDataSource = resolver.resolve(LocalUserDataSourceProtocol.self) repository.reportDataSource = resolver.resolve(ReportDataSourceProtocol.self) + repository.pushNotificationService = resolver.resolve(PushNotificationServiceProtocol.self) return repository } container.register(UserRepositoryProtocol.self) { resolver in @@ -197,6 +198,16 @@ final class DIContainer { useCase.tokenRepository = resolver.resolve(TokenRepositoryProtocol.self) return useCase } + container.register(SubscribePushNotificationUseCaseProtocol.self) { resolver in + var useCase = SubscribePushNotificationUseCase() + useCase.pushNotificationService = resolver.resolve(PushNotificationServiceProtocol.self) + return useCase + } + container.register(UnsubscribePushNotificationUseCaseProtocol.self) { resolver in + var useCase = UnsubscribePushNotificationUseCase() + useCase.pushNotificationService = resolver.resolve(PushNotificationServiceProtocol.self) + return useCase + } } private func registerViewModels() { @@ -204,6 +215,8 @@ final class DIContainer { let viewModel = ChatViewModel() viewModel.chatUseCase = resolver.resolve(ChatUseCaseProtocol.self) viewModel.leaveStudyUseCase = resolver.resolve(LeaveStudyUseCaseProtocol.self) + viewModel.subscribePushNotificationUseCase = resolver.resolve(SubscribePushNotificationUseCaseProtocol.self) + viewModel.unsubscribePushNotificationUseCase = resolver.resolve(UnsubscribePushNotificationUseCaseProtocol.self) return viewModel } container.register(ChatListViewModel.self) { resolver in diff --git a/Mogakco/Sources/Data/DataSources/Protocol/PushNotificationServiceProtocol.swift b/Mogakco/Sources/Data/DataSources/Protocol/PushNotificationServiceProtocol.swift index 728b1197..d5661617 100644 --- a/Mogakco/Sources/Data/DataSources/Protocol/PushNotificationServiceProtocol.swift +++ b/Mogakco/Sources/Data/DataSources/Protocol/PushNotificationServiceProtocol.swift @@ -9,10 +9,12 @@ import RxSwift protocol PushNotificationServiceProtocol { - // FCM Token을 이용하여 특정 유저에게 푸쉬 알림을 보내는 API + // FCM Token을 이용하여 특정 유저에게 푸쉬 알림 전송 func send(request: PushNotificationRequestDTO) -> Observable - // 특정 Topic을 구독하고 있는 유저들에게 푸쉬 알림을 보내는 API + // 특정 Topic을 구독하고 있는 유저들에게 푸쉬 알림 전송 func sendTopic(request: PushNotificationRequestDTO) -> Observable - // Topic을 구독하는 API - func subscribeTopic(topic: String) + // Topic 구독 + func subscribeTopic(topic: String) -> Observable + // Topic 구독 해제 + func unsubscribeTopic(topic: String) -> Observable } diff --git a/Mogakco/Sources/Data/DataSources/Remote/PushNotificationService.swift b/Mogakco/Sources/Data/DataSources/Remote/PushNotificationService.swift index 77f052da..78b41996 100644 --- a/Mogakco/Sources/Data/DataSources/Remote/PushNotificationService.swift +++ b/Mogakco/Sources/Data/DataSources/Remote/PushNotificationService.swift @@ -25,13 +25,27 @@ struct PushNotificationService: PushNotificationServiceProtocol { return provider.request(PushNotificationTarget.send(request)) } - func subscribeTopic(topic: String) { - Messaging.messaging().subscribe(toTopic: topic) { error in - if let error = error { - print("Message Subscribe Error \(error)") - } else { - print("Message Subscribe succeeded") + func subscribeTopic(topic: String) -> Observable { + return Observable.create { emitter in + Messaging.messaging().subscribe(toTopic: topic) { error in + if let error = error { + emitter.onError(error) + } + emitter.onNext(()) } + return Disposables.create() + } + } + + func unsubscribeTopic(topic: String) -> Observable { + return Observable.create { emitter in + Messaging.messaging().unsubscribe(fromTopic: topic) { error in + if let error = error { + emitter.onError(error) + } + emitter.onNext(()) + } + return Disposables.create() } } } diff --git a/Mogakco/Sources/Data/Repositories/ChatRoomRepository.swift b/Mogakco/Sources/Data/Repositories/ChatRoomRepository.swift index b12e60a5..fa1a9982 100644 --- a/Mogakco/Sources/Data/Repositories/ChatRoomRepository.swift +++ b/Mogakco/Sources/Data/Repositories/ChatRoomRepository.swift @@ -37,15 +37,6 @@ struct ChatRoomRepository: ChatRoomRepositoryProtocol { .map { $0.documents.map { $0.toDomain() } } .map { $0.filter { ids.contains($0.id) } } ?? .empty() - // 채팅방 푸쉬 알림 구독 - chatRoomsSb - .subscribe(onNext: { - $0.forEach { - self.pushNotificationService?.subscribeTopic(topic: $0.id) - } - }) - .disposed(by: disposeBag) - // 최근 메세지 호출 let latestChatChatRoomsSb = BehaviorSubject<[ChatRoom]>(value: []) chatRoomsSb @@ -113,6 +104,7 @@ struct ChatRoomRepository: ChatRoomRepositoryProtocol { request: .init(userIDs: $0.userIDs.filter { $0 != user.id }) ) ?? .empty() } + .flatMap { _ in pushNotificationService?.unsubscribeTopic(topic: chatRoom.id) ?? .empty() } .map { _ in () } ?? .empty() let userUpdated = Observable.zip(studyUpdated, chatRoomUpdated) diff --git a/Mogakco/Sources/Data/Repositories/StudyRepository.swift b/Mogakco/Sources/Data/Repositories/StudyRepository.swift index d1671aef..0802fe90 100644 --- a/Mogakco/Sources/Data/Repositories/StudyRepository.swift +++ b/Mogakco/Sources/Data/Repositories/StudyRepository.swift @@ -19,6 +19,7 @@ struct StudyRepository: StudyRepositoryProtocol { var remoteUserDataSource: RemoteUserDataSourceProtocol? var chatRoomDataSource: ChatRoomDataSourceProtocol? var reportDataSource: ReportDataSourceProtocol? + var pushNotificationService: PushNotificationServiceProtocol? private let disposeBag = DisposeBag() func list(sort: StudySort, filters: [StudyFilter]) -> Observable<[Study]> { @@ -89,6 +90,9 @@ struct StudyRepository: StudyRepositoryProtocol { .flatMap { chatRoomDataSource?.create(request: $0) ?? .empty() } + .flatMap { + pushNotificationService?.subscribeTopic(topic: $0.toDomain().id) ?? .empty() + } let updateUser = user .flatMap { user in @@ -254,6 +258,7 @@ struct StudyRepository: StudyRepositoryProtocol { ) ) ?? .empty() } + .flatMap { _ in pushNotificationService?.unsubscribeTopic(topic: id) ?? .empty() } Observable.combineLatest( updateUser, diff --git a/Mogakco/Sources/Domain/UseCases/Protocol/SubscribePushNotificationUseCaseProtocol.swift b/Mogakco/Sources/Domain/UseCases/Protocol/SubscribePushNotificationUseCaseProtocol.swift new file mode 100644 index 00000000..baf79c96 --- /dev/null +++ b/Mogakco/Sources/Domain/UseCases/Protocol/SubscribePushNotificationUseCaseProtocol.swift @@ -0,0 +1,13 @@ +// +// SubscribePushNotificationUseCaseProtocol.swift +// Mogakco +// +// Created by 김범수 on 2022/12/07. +// Copyright © 2022 Mogakco. All rights reserved. +// + +import RxSwift + +protocol SubscribePushNotificationUseCaseProtocol { + func excute(topic: String) -> Observable +} diff --git a/Mogakco/Sources/Domain/UseCases/Protocol/UnsubscribePushNotificationUseCaseProtocol.swift b/Mogakco/Sources/Domain/UseCases/Protocol/UnsubscribePushNotificationUseCaseProtocol.swift new file mode 100644 index 00000000..1f11c107 --- /dev/null +++ b/Mogakco/Sources/Domain/UseCases/Protocol/UnsubscribePushNotificationUseCaseProtocol.swift @@ -0,0 +1,13 @@ +// +// UnsubscribePushNotificationUseCaseProtocol.swift +// Mogakco +// +// Created by 김범수 on 2022/12/07. +// Copyright © 2022 Mogakco. All rights reserved. +// + +import RxSwift + +protocol UnsubscribePushNotificationUseCaseProtocol { + func excute(topic: String) -> Observable +} diff --git a/Mogakco/Sources/Domain/UseCases/SubscribePushNotificationUseCase.swift b/Mogakco/Sources/Domain/UseCases/SubscribePushNotificationUseCase.swift new file mode 100644 index 00000000..bc1f5988 --- /dev/null +++ b/Mogakco/Sources/Domain/UseCases/SubscribePushNotificationUseCase.swift @@ -0,0 +1,18 @@ +// +// SubscribePushNotificationUseCase.swift +// Mogakco +// +// Created by 김범수 on 2022/12/07. +// Copyright © 2022 Mogakco. All rights reserved. +// + +import RxSwift + +struct SubscribePushNotificationUseCase: SubscribePushNotificationUseCaseProtocol { + + var pushNotificationService: PushNotificationServiceProtocol? + + func excute(topic: String) -> Observable { + return pushNotificationService?.subscribeTopic(topic: topic) ?? .empty() + } +} diff --git a/Mogakco/Sources/Domain/UseCases/UnsubscribePushNotificationUseCase.swift b/Mogakco/Sources/Domain/UseCases/UnsubscribePushNotificationUseCase.swift new file mode 100644 index 00000000..4f6c1b8b --- /dev/null +++ b/Mogakco/Sources/Domain/UseCases/UnsubscribePushNotificationUseCase.swift @@ -0,0 +1,18 @@ +// +// UnsubscribePushNotificationUseCase.swift +// Mogakco +// +// Created by 김범수 on 2022/12/07. +// Copyright © 2022 Mogakco. All rights reserved. +// + +import RxSwift + +struct UnsubscribePushNotificationUseCase: UnsubscribePushNotificationUseCaseProtocol { + + var pushNotificationService: PushNotificationServiceProtocol? + + func excute(topic: String) -> Observable { + return pushNotificationService?.unsubscribeTopic(topic: topic) ?? .empty() + } +} diff --git a/Mogakco/Sources/Presentation/Chat/ViewController/ChatViewController.swift b/Mogakco/Sources/Presentation/Chat/ViewController/ChatViewController.swift index df04caea..41e35497 100644 --- a/Mogakco/Sources/Presentation/Chat/ViewController/ChatViewController.swift +++ b/Mogakco/Sources/Presentation/Chat/ViewController/ChatViewController.swift @@ -88,11 +88,6 @@ final class ChatViewController: ViewController { navigationController?.isNavigationBarHidden = false } - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - navigationController?.isNavigationBarHidden = true - } - override var canBecomeFirstResponder: Bool { return true } @@ -116,6 +111,12 @@ final class ChatViewController: ViewController { override func bind() { let input = ChatViewModel.Input( + viewWillAppear: rx.viewWillAppear.map { _ in () }.asObservable(), + viewWillDisappear: rx.viewWillDisappear.map { _ in () }.asObservable(), + willEnterForeground: NotificationCenter.default + .rx.notification(UIApplication.willEnterForegroundNotification).map { _ in () }, + didEnterBackground: NotificationCenter.default + .rx.notification(UIApplication.didEnterBackgroundNotification).map { _ in () }, backButtonDidTap: backButton.rx.tap.asObservable(), studyInfoButtonDidTap: studyInfoButton.rx.tap.asObservable(), selectedSidebar: sidebarView.tableView.rx.itemSelected.asObservable(), @@ -123,7 +124,6 @@ final class ChatViewController: ViewController { inputViewText: messageInputView.messageInputTextView.rx.text.orEmpty.asObservable(), pagination: collectionView.refreshControl?.rx.controlEvent(.valueChanged).asObservable() ) - let output = viewModel.transform(input: input) Driver<[ChatSidebarMenu]>.just(ChatSidebarMenu.allCases) @@ -133,9 +133,7 @@ final class ChatViewController: ViewController { for: IndexPath(row: index, section: 0)) as? ChatSidebarTableViewCell else { return UITableViewCell() } - cell.menuLabel.text = menu.rawValue - return cell } .disposed(by: disposeBag) diff --git a/Mogakco/Sources/Presentation/Chat/ViewModel/ChatViewModel.swift b/Mogakco/Sources/Presentation/Chat/ViewModel/ChatViewModel.swift index 59a95b3d..11480968 100644 --- a/Mogakco/Sources/Presentation/Chat/ViewModel/ChatViewModel.swift +++ b/Mogakco/Sources/Presentation/Chat/ViewModel/ChatViewModel.swift @@ -20,6 +20,10 @@ enum ChatRoomNavigation { final class ChatViewModel: ViewModel { struct Input { + let viewWillAppear: Observable + let viewWillDisappear: Observable + let willEnterForeground: Observable + let didEnterBackground: Observable let backButtonDidTap: Observable let studyInfoButtonDidTap: Observable let selectedSidebar: Observable @@ -40,6 +44,8 @@ final class ChatViewModel: ViewModel { var chatRoomID: String = "" var chatUseCase: ChatUseCaseProtocol? var leaveStudyUseCase: LeaveStudyUseCaseProtocol? + var subscribePushNotificationUseCase: SubscribePushNotificationUseCaseProtocol? + var unsubscribePushNotificationUseCase: UnsubscribePushNotificationUseCaseProtocol? private var chatArray: [Chat] = [] var messages = BehaviorRelay<[Chat]>(value: []) let navigation = PublishSubject() @@ -55,6 +61,8 @@ final class ChatViewModel: ViewModel { let showMemberTap = PublishSubject() let refreshFinished = PublishSubject() + bindPushNotification(input: input) + observeFirebase() fetchChats() backButtonDidTap(input: input) @@ -123,7 +131,8 @@ final class ChatViewModel: ViewModel { message: message, chatRoomID: self.chatRoomID, date: Date().toInt(dateFormat: Format.chatDateFormat), - readUserIDs: [user.id] + readUserIDs: [user.id], + user: user ), to: self.chatRoomID ) @@ -143,6 +152,30 @@ final class ChatViewModel: ViewModel { ) } + private func bindPushNotification(input: Input) { + // 채팅방 들어올 시 -> 푸쉬 알림 구독 해제 + Observable.merge( + input.viewWillAppear, + input.willEnterForeground + ) + .withUnretained(self) + .flatMap { $0.0.unsubscribePushNotificationUseCase?.excute(topic: $0.0.chatRoomID) ?? .empty() } + .subscribe(onNext: { _ in + }) + .disposed(by: disposeBag) + + // 채팅방 나갈 시 -> 푸쉬 알림 구독 + Observable.merge( + input.viewWillDisappear, + input.didEnterBackground + ) + .withUnretained(self) + .flatMap { $0.0.subscribePushNotificationUseCase?.excute(topic: $0.0.chatRoomID) ?? .empty() } + .subscribe(onNext: { _ in + }) + .disposed(by: disposeBag) + } + private func observeFirebase() { chatUseCase?.observe(chatRoomID: chatRoomID) .withLatestFrom(messages) { ($0, $1) }