diff --git a/SOPT-iOS/Projects/Core/Sources/Literals/UserDefaultKeyLIst.swift b/SOPT-iOS/Projects/Core/Sources/Literals/UserDefaultKeyLIst.swift index f29b6512..b89b6f08 100644 --- a/SOPT-iOS/Projects/Core/Sources/Literals/UserDefaultKeyLIst.swift +++ b/SOPT-iOS/Projects/Core/Sources/Literals/UserDefaultKeyLIst.swift @@ -22,7 +22,7 @@ public struct UserDefaultKeyList { @UserDefaultWrapper(key: "sentence") public static var sentence @UserDefaultWrapper(key: "soptampName") public static var soptampName @UserDefaultWrapper(key: "pushToken") public static var pushToken - @UserDefaultWrapper(key: "isFirstVisitToPokeView") public static var isFirstVisitToPokeView + @UserDefaultWrapper(key: "isFirstVisitToPokeView") public static var isFirstVisitToPokeOnboardingView } public struct AppNotice { diff --git a/SOPT-iOS/Projects/Data/Sources/Repository/MainRepository.swift b/SOPT-iOS/Projects/Data/Sources/Repository/MainRepository.swift index 4c55ab3f..ad47b317 100644 --- a/SOPT-iOS/Projects/Data/Sources/Repository/MainRepository.swift +++ b/SOPT-iOS/Projects/Data/Sources/Repository/MainRepository.swift @@ -17,12 +17,15 @@ public class MainRepository { private let userService: UserService private let configService: ConfigService private let descriptionService: DescriptionService + private let pokeService: PokeService + private let cancelBag = CancelBag() - public init(userService: UserService, configService: ConfigService, descriptionService: DescriptionService) { + public init(userService: UserService, configService: ConfigService, descriptionService: DescriptionService, pokeService: PokeService) { self.userService = userService self.configService = configService self.descriptionService = descriptionService + self.pokeService = pokeService } } @@ -77,4 +80,10 @@ extension MainRepository: MainRepositoryInterface { } .eraseToAnyPublisher() } + + public func checkPokeNewUser() -> AnyPublisher { + pokeService.isNewUser() + .map { $0.isNew } + .eraseToAnyPublisher() + } } diff --git a/SOPT-iOS/Projects/Demo/Sources/Dependency/RegisterDependencies.swift b/SOPT-iOS/Projects/Demo/Sources/Dependency/RegisterDependencies.swift index b017d208..af0f6e56 100644 --- a/SOPT-iOS/Projects/Demo/Sources/Dependency/RegisterDependencies.swift +++ b/SOPT-iOS/Projects/Demo/Sources/Dependency/RegisterDependencies.swift @@ -39,7 +39,8 @@ extension AppDelegate { MainRepository( userService: DefaultUserService(), configService: DefaultConfigService(), - descriptionService: DefaultDescriptionService() + descriptionService: DefaultDescriptionService(), + pokeService: DefaultPokeService() ) } ) diff --git a/SOPT-iOS/Projects/Domain/Sources/RepositoryInterface/MainRepositoryInterface.swift b/SOPT-iOS/Projects/Domain/Sources/RepositoryInterface/MainRepositoryInterface.swift index 12070e05..44dc27d9 100644 --- a/SOPT-iOS/Projects/Domain/Sources/RepositoryInterface/MainRepositoryInterface.swift +++ b/SOPT-iOS/Projects/Domain/Sources/RepositoryInterface/MainRepositoryInterface.swift @@ -15,4 +15,5 @@ public protocol MainRepositoryInterface { func getServiceState() -> AnyPublisher func getMainViewDescription() -> AnyPublisher func registerPushToken(with token: String) -> AnyPublisher + func checkPokeNewUser() -> AnyPublisher } diff --git a/SOPT-iOS/Projects/Domain/Sources/UseCase/MainUseCase.swift b/SOPT-iOS/Projects/Domain/Sources/UseCase/MainUseCase.swift index fa5f0cda..0cd12e0b 100644 --- a/SOPT-iOS/Projects/Domain/Sources/UseCase/MainUseCase.swift +++ b/SOPT-iOS/Projects/Domain/Sources/UseCase/MainUseCase.swift @@ -15,10 +15,13 @@ public protocol MainUseCase { var serviceState: PassthroughSubject { get set } var mainDescription: PassthroughSubject { get set } var mainErrorOccurred: PassthroughSubject { get set } + var isPokeNewUser: PassthroughSubject { get set } + func getUserMainInfo() func getServiceState() func getMainViewDescription() func registerPushToken() + func checkPokeNewUser() } public class DefaultMainUseCase { @@ -30,6 +33,7 @@ public class DefaultMainUseCase { public var serviceState = PassthroughSubject() public var mainDescription = PassthroughSubject() public var mainErrorOccurred = PassthroughSubject() + public var isPokeNewUser = PassthroughSubject() public init(repository: MainRepositoryInterface) { self.repository = repository @@ -75,12 +79,23 @@ extension DefaultMainUseCase: MainUseCase { repository.registerPushToken(with: pushToken) .sink { event in - print("DefaultSplashUseCase : \(event)") + print("MainUseCase Register PushToken: \(event)") } receiveValue: { didSucceed in print("푸시 토큰 등록 결과: \(didSucceed)") }.store(in: cancelBag) } + public func checkPokeNewUser() { + repository.checkPokeNewUser() + .catch { [weak self] error in + print("MainUseCase CheckPokeNewUser Error: \(error)") + self?.mainErrorOccurred.send(.networkError(message: "Poke 온보딩 대상 여부 확인 실패")) + return Empty() + }.sink { [weak self] isNewUser in + self?.isPokeNewUser.send(isNewUser) + }.store(in: cancelBag) + } + private func setUserType(with userType: UserType?) { switch userType { case .none, .inactive: diff --git a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/ViewModel/MainViewModel.swift b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/ViewModel/MainViewModel.swift index 1c425885..7aa161e8 100644 --- a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/ViewModel/MainViewModel.swift +++ b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/ViewModel/MainViewModel.swift @@ -89,9 +89,47 @@ extension MainViewModel { }.store(in: cancelBag) input.cellTapped - .sink { [weak self] indexPath in - guard let self = self else { return } - self.bindCellAction(indexPath) + .filter { $0.section == 1 } + .map { $0.item } + .compactMap { [weak self] index in + self?.mainServiceList[index] + }.sink { [weak self] service in + self?.handleMainServiceSectionTap(with: service) + }.store(in: cancelBag) + + input.cellTapped + .filter { $0.section == 2 } + .map { $0.item } + .compactMap { [weak self] index in + self?.otherServiceList[index] + }.sink { [weak self] service in + self?.handleOtherServiceSectionTap(with: service) + }.store(in: cancelBag) + + let appServiceSectionService = input.cellTapped + .filter { [weak self] _ in + guard let self else { return false } + return self.userType != .visitor + } + .filter { $0.section == 3 } + .map { $0.item } + .compactMap { [weak self] index in + return self?.appServiceList[index] + }.eraseToAnyPublisher() + + appServiceSectionService.sink { [weak self] service in + self?.trackAmplitude(event: service.toAmplitudeEventType) + }.store(in: cancelBag) + + appServiceSectionService.filter { $0 == .soptamp } + .sink { [weak self] _ in + self?.onSoptamp?() + }.store(in: cancelBag) + + appServiceSectionService.filter { $0 == .poke } + .sink { [weak self] _ in + output.isLoading.send(true) + self?.useCase.checkPokeNewUser() }.store(in: cancelBag) input.requestUserInfo @@ -159,42 +197,33 @@ extension MainViewModel { self.onNeedSignIn?() } }.store(in: self.cancelBag) + + useCase.isPokeNewUser + .sink { [weak self] isNewUser in + output.isLoading.send(false) + self?.onPoke?(isNewUser) + }.store(in: cancelBag) } - private func bindCellAction(_ indexPath: IndexPath) { - switch (indexPath.section, indexPath.row) { - case (0, _): break - case (1, _): - guard let service = mainServiceList[safe: indexPath.item] else { return } - self.trackAmplitude(event: service.toAmplitudeEventType) - - guard service != .attendance else { - onAttendance?() - return - } - - let needOfficialProject = service == .project && userType == .visitor - let serviceDomainURL = needOfficialProject - ? ExternalURL.SOPT.project - : service.serviceDomainLink - onSafari?(serviceDomainURL) - case (2, _): - guard let service = otherServiceList[safe: indexPath.item] else { return } - self.trackAmplitude(event: service.toAmplitudeEventType) - - onSafari?(service.serviceDomainLink) - case(3, _): - guard userType != .visitor else { return } - guard let service = appServiceList[safe: indexPath.item] else { return } - self.trackAmplitude(event: service.toAmplitudeEventType) - switch service { - case .soptamp: onSoptamp?() - case .poke: - let isFirstVisitToPokeView = UserDefaultKeyList.User.isFirstVisitToPokeView - onPoke?(isFirstVisitToPokeView ?? true) - } - default: break + private func handleMainServiceSectionTap(with service: ServiceType) { + self.trackAmplitude(event: service.toAmplitudeEventType) + + guard service != .attendance else { + onAttendance?() + return } + + let needOfficialProject = service == .project && userType == .visitor + let serviceDomainURL = needOfficialProject + ? ExternalURL.SOPT.project + : service.serviceDomainLink + onSafari?(serviceDomainURL) + } + + private func handleOtherServiceSectionTap(with service: ServiceType) { + self.trackAmplitude(event: service.toAmplitudeEventType) + + onSafari?(service.serviceDomainLink) } private func requestAuthorizationForNotification() { @@ -224,12 +253,15 @@ extension MainViewModel { case .visitor: self.mainServiceList = [.officialHomepage, .review, .project] self.otherServiceList = [.instagram, .youtube, .faq] + self.appServiceList = [.poke, .soptamp] case .active: self.mainServiceList = [.attendance, .group, .playgroundCommunity] self.otherServiceList = [.member, .project, .officialHomepage] + self.appServiceList = [.poke, .soptamp] case .inactive: self.mainServiceList = [.playgroundCommunity, .group, .member] self.otherServiceList = [.project, .officialHomepage, .instagram, .youtube] + self.appServiceList = [.soptamp] } } diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/VC/PokeMainVC.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/VC/PokeMainVC.swift index 599cf4ce..01a01c2f 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/VC/PokeMainVC.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/VC/PokeMainVC.swift @@ -104,6 +104,7 @@ public final class PokeMainVC: UIViewController, PokeMainViewControllable { public override func viewDidLoad() { super.viewDidLoad() self.setUI() + self.setDelegate() self.setStackView() self.setLayout() self.bindViewModel() @@ -118,6 +119,10 @@ extension PokeMainVC { view.backgroundColor = DSKitAsset.Colors.semanticBackground.color } + private func setDelegate() { + self.navigationController?.interactivePopGestureRecognizer?.delegate = self + } + private func setStackView() { self.contentStackView.addArrangedSubviews(pokedSectionGroupView, friendSectionGroupView, diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeOnboardingScene/ViewModel/PokeOnboardingViewModel.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeOnboardingScene/ViewModel/PokeOnboardingViewModel.swift index 466b12e6..1c0c7fc9 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeOnboardingScene/ViewModel/PokeOnboardingViewModel.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeOnboardingScene/ViewModel/PokeOnboardingViewModel.swift @@ -26,7 +26,7 @@ public final class PokeOnboardingViewModel: PokeOnboardingViewModelType { let randomAcquaintance = PassthroughSubject<[PokeUserModel], Never>() let pokedResult = PassthroughSubject() } - + public var onNaviBackTapped: (() -> Void)? public var onFirstVisitInOnboarding: (() -> Void)? public var onAvartarTapped: ((_ playgroundId: String) -> Void)? @@ -54,13 +54,11 @@ extension PokeOnboardingViewModel { }).store(in: cancelBag) input.viewDidLoaded - .map { _ in UserDefaultKeyList.User.isFirstVisitToPokeView ?? true } + .map { _ in UserDefaultKeyList.User.isFirstVisitToPokeOnboardingView ?? true } .sink(receiveValue: { [weak self] isFirstVisit in guard isFirstVisit else { return } - -// UserDefaultKeyList.User.isFirstVisitToPokeView = false -// self?.onFirstVisitInOnboarding?() - self?.onFirstVisitInOnboarding?() + UserDefaultKeyList.User.isFirstVisitToPokeOnboardingView = false + self?.onFirstVisitInOnboarding?() }).store(in: cancelBag) input.pokeButtonTapped @@ -77,7 +75,7 @@ extension PokeOnboardingViewModel { .sink(receiveValue: { [weak self] userModel in self?.onAvartarTapped?(String(describing: userModel.playgroundId)) }).store(in: cancelBag) - + input.pullToRefreshTriggered .withUnretained(self) .sink(receiveValue: { [weak self] _ in diff --git a/SOPT-iOS/Projects/Modules/Networks/Sources/API/PokeAPI.swift b/SOPT-iOS/Projects/Modules/Networks/Sources/API/PokeAPI.swift index f328b923..5a44333e 100644 --- a/SOPT-iOS/Projects/Modules/Networks/Sources/API/PokeAPI.swift +++ b/SOPT-iOS/Projects/Modules/Networks/Sources/API/PokeAPI.swift @@ -13,6 +13,7 @@ import Moya import Core public enum PokeAPI { + case isNewUser case getWhoPokedToMe case getWhoPokedToMeList(pageIndex: String) case getFriend @@ -29,6 +30,8 @@ extension PokeAPI: BaseAPI { public var path: String { switch self { + case .isNewUser: + return "/new" case .getWhoPokedToMe: return "/to/me" case .getWhoPokedToMeList: @@ -50,7 +53,7 @@ extension PokeAPI: BaseAPI { public var method: Moya.Method { switch self { - case .getWhoPokedToMe, .getWhoPokedToMeList, .getFriend, .getFriendListWithRelation, .getFriendRandomUser, + case .isNewUser, .getWhoPokedToMe, .getWhoPokedToMeList, .getFriend, .getFriendListWithRelation, .getFriendRandomUser, .getFriendList, .getRandomUsers, .getPokeMessages: return .get case .poke: diff --git a/SOPT-iOS/Projects/Modules/Networks/Sources/Entity/PokeIsNewUserEntity.swift b/SOPT-iOS/Projects/Modules/Networks/Sources/Entity/PokeIsNewUserEntity.swift new file mode 100644 index 00000000..844c38dd --- /dev/null +++ b/SOPT-iOS/Projects/Modules/Networks/Sources/Entity/PokeIsNewUserEntity.swift @@ -0,0 +1,13 @@ +// +// PokeIsNewUserEntity.swift +// Networks +// +// Created by sejin on 12/27/23. +// Copyright © 2023 SOPT-iOS. All rights reserved. +// + +import Foundation + +public struct PokeIsNewUserEntity: Decodable { + public let isNew: Bool +} diff --git a/SOPT-iOS/Projects/Modules/Networks/Sources/Service/PokeService.swift b/SOPT-iOS/Projects/Modules/Networks/Sources/Service/PokeService.swift index b6cd536d..abee7e25 100644 --- a/SOPT-iOS/Projects/Modules/Networks/Sources/Service/PokeService.swift +++ b/SOPT-iOS/Projects/Modules/Networks/Sources/Service/PokeService.swift @@ -14,6 +14,7 @@ import Moya public typealias DefaultPokeService = BaseService public protocol PokeService { + func isNewUser() -> AnyPublisher func getWhoPokedToMe() -> AnyPublisher func getWhoPokedToMeList(pageIndex: String) -> AnyPublisher func getFriend() -> AnyPublisher<[PokeUserEntity], Error> @@ -26,6 +27,10 @@ public protocol PokeService { } extension DefaultPokeService: PokeService { + public func isNewUser() -> AnyPublisher { + requestObjectInCombine(.isNewUser) + } + public func getWhoPokedToMe() -> AnyPublisher { requestObjectInCombine(.getWhoPokedToMe) } diff --git a/SOPT-iOS/Projects/SOPT-iOS/Sources/Dependency/RegisterDependencies.swift b/SOPT-iOS/Projects/SOPT-iOS/Sources/Dependency/RegisterDependencies.swift index b017d208..af0f6e56 100644 --- a/SOPT-iOS/Projects/SOPT-iOS/Sources/Dependency/RegisterDependencies.swift +++ b/SOPT-iOS/Projects/SOPT-iOS/Sources/Dependency/RegisterDependencies.swift @@ -39,7 +39,8 @@ extension AppDelegate { MainRepository( userService: DefaultUserService(), configService: DefaultConfigService(), - descriptionService: DefaultDescriptionService() + descriptionService: DefaultDescriptionService(), + pokeService: DefaultPokeService() ) } )