diff --git a/14th-team5-iOS/App/Sources/Presentation/Home/Dependency/MainViewDIContainer.swift b/14th-team5-iOS/App/Sources/Presentation/Home/Dependency/MainViewDIContainer.swift index 977c6ffba..58b981583 100644 --- a/14th-team5-iOS/App/Sources/Presentation/Home/Dependency/MainViewDIContainer.swift +++ b/14th-team5-iOS/App/Sources/Presentation/Home/Dependency/MainViewDIContainer.swift @@ -32,6 +32,7 @@ extension MainViewDIContainer { fetchMainUseCase: makeFetchMainUseCase(), fetchMainNightUseCase: makeFetchMainNightUseCase(), pickUseCase: makePickUseCase(), + checkMissionAlertShowUseCase: makeCheckMissionAlertShowUseCase(), provider: globalState ) } @@ -48,6 +49,10 @@ extension MainViewDIContainer { return MainRepository() } + private func makeMissionUserDefaultsRepository() -> MissionUserDefaultsRepository { + return MissionUserDefaultsRepository() + } + private func makeFetchMainUseCase() -> FetchMainUseCaseProtocol { return FetchMainUseCase(mainRepository: makeMainRepository()) } @@ -55,4 +60,8 @@ extension MainViewDIContainer { private func makeFetchMainNightUseCase() -> FetchMainNightUseCaseProtocol { return FetchMainNightUseCase(mainRepository: makeMainRepository()) } + + private func makeCheckMissionAlertShowUseCase() -> CheckMissionAlertShowUseCaseProtocol { + return CheckMissionAlertShowUseCase(missionUserDefaultsRepository: makeMissionUserDefaultsRepository()) + } } diff --git a/14th-team5-iOS/App/Sources/Presentation/Home/Reactor/MainViewReactor.swift b/14th-team5-iOS/App/Sources/Presentation/Home/Reactor/MainViewReactor.swift index 30a84f5a3..04728cf55 100644 --- a/14th-team5-iOS/App/Sources/Presentation/Home/Reactor/MainViewReactor.swift +++ b/14th-team5-iOS/App/Sources/Presentation/Home/Reactor/MainViewReactor.swift @@ -27,6 +27,7 @@ final class MainViewReactor: Reactor { case cameraViewController(UploadLocation) case survivalAlert case pickAlert(String, String) + case missionUnlockedAlert case weeklycalendarViewController(String) case familyManagementViewController case monthlyCalendarViewController @@ -38,6 +39,7 @@ final class MainViewReactor: Reactor { case fetchMainUseCase case fetchMainNightUseCase + case checkMissionAlert(Bool, Bool) case openNextViewController(TapAction) case didTapSegmentControl(PostType) case pickConfirmButtonTapped(String, String) @@ -91,18 +93,20 @@ final class MainViewReactor: Reactor { private let fetchMainUseCase: FetchMainUseCaseProtocol private let fetchMainNightUseCase: FetchMainNightUseCaseProtocol private let pickUseCase: PickUseCaseProtocol + private let checkMissionAlertShowUseCase: CheckMissionAlertShowUseCaseProtocol private let provider: GlobalStateProviderProtocol - init( - fetchMainUseCase: FetchMainUseCaseProtocol, - fetchMainNightUseCase: FetchMainNightUseCaseProtocol, - pickUseCase: PickUseCaseProtocol, - provider: GlobalStateProviderProtocol) { - self.fetchMainUseCase = fetchMainUseCase - self.fetchMainNightUseCase = fetchMainNightUseCase - self.pickUseCase = pickUseCase - self.provider = provider - } + init(fetchMainUseCase: FetchMainUseCaseProtocol, + fetchMainNightUseCase: FetchMainNightUseCaseProtocol, + pickUseCase: PickUseCaseProtocol, + checkMissionAlertShowUseCase: CheckMissionAlertShowUseCaseProtocol, + provider: GlobalStateProviderProtocol) { + self.fetchMainUseCase = fetchMainUseCase + self.fetchMainNightUseCase = fetchMainNightUseCase + self.pickUseCase = pickUseCase + self.checkMissionAlertShowUseCase = checkMissionAlertShowUseCase + self.provider = provider + } } extension MainViewReactor { @@ -144,7 +148,8 @@ extension MainViewReactor { return Observable.concat( Observable.just(.updateMainData(data)), Observable.just(.setBalloonText), - Observable.just(.setCamerEnabled) + Observable.just(.setCamerEnabled), + self.mutate(action: .checkMissionAlert(data.isMissionUnlocked, data.isMeSurvivalUploadedToday)) ) } case .fetchMainNightUseCase: @@ -163,7 +168,8 @@ extension MainViewReactor { .flatMap {_ in return Observable.concat([ Observable.just(Mutation.setInTime(false)), - self.mutate(action: .fetchMainNightUseCase) + self.mutate(action: .fetchMainNightUseCase), + Observable.just(Mutation.setDescriptionText) ]) } } else { @@ -233,6 +239,19 @@ extension MainViewReactor { case .contributorNextButtonTap: return Observable.just(.showNextView(.weeklycalendarViewController(currentState.contributor.recentPostDate))) } + case .checkMissionAlert(let isUnlocked, let isMeSurvivalUploadedToday): + if isUnlocked && isMeSurvivalUploadedToday { + return checkMissionAlertShowUseCase.execute() + .flatMap { isAlreadyShown in + if !isAlreadyShown { + return Observable.just(.showNextView(.missionUnlockedAlert)) + } else { + return Observable.empty() + } + } + } else { + return Observable.empty() + } } } @@ -298,7 +317,7 @@ extension MainViewReactor { if currentState.pageIndex == 0 { newState.cameraEnabled = !currentState.isMeSurvivalUploadedToday } else { - if (!currentState.isMeMissionUploadedToday) && currentState.isMissionUnlocked { + if (!currentState.isMeMissionUploadedToday) && currentState.isMissionUnlocked { newState.cameraEnabled = true } else { newState.cameraEnabled = false @@ -343,6 +362,12 @@ extension MainViewReactor { private func setDescriptionText(_ state: State) -> State { var newState = state + guard let inTime = currentState.isInTime, + inTime else { + newState.description = .survivalNone + return newState + } + if currentState.pageIndex == 0 { if currentState.isFamilySurvivalUploadedToday { newState.description = .survivalFull diff --git a/14th-team5-iOS/App/Sources/Presentation/Home/ViewControllers/MainViewController.swift b/14th-team5-iOS/App/Sources/Presentation/Home/ViewControllers/MainViewController.swift index 18aa7e4bd..54cbd86b7 100644 --- a/14th-team5-iOS/App/Sources/Presentation/Home/ViewControllers/MainViewController.swift +++ b/14th-team5-iOS/App/Sources/Presentation/Home/ViewControllers/MainViewController.swift @@ -308,6 +308,14 @@ extension MainViewController { guard let self else { return } self.alertConfirmRelay.accept((name, id)) } .present() + case .missionUnlockedAlert: + BibbiAlertBuilder(self) + .alertStyle(.missionKey) + .setConfirmAction { [weak self] in + guard let self else { return } + self.navigationController?.pushViewController(CameraDIContainer(cameraType: .mission).makeViewController(), animated: true) + } + .present() } } diff --git a/14th-team5-iOS/Data/Sources/UserDefaults/MissionUserDefaultsRepository.swift b/14th-team5-iOS/Data/Sources/UserDefaults/MissionUserDefaultsRepository.swift new file mode 100644 index 000000000..262f41de9 --- /dev/null +++ b/14th-team5-iOS/Data/Sources/UserDefaults/MissionUserDefaultsRepository.swift @@ -0,0 +1,43 @@ +// +// MissionUserDefaultsRepository.swift +// Data +// +// Created by 마경미 on 03.06.24. +// + +import Foundation + +import Domain + +import RxSwift + +public final class MissionUserDefaultsRepository: MissionUserdefaultsRepositoryProtocol { + + private let lastMissionUploadDateId = "lastMissionUploadDateId" + + public init() { } + + public func isAlreadyShowMissionAlert() -> Observable { + guard let lastDate = UserDefaults.standard.string(forKey: lastMissionUploadDateId) else { + saveMissionUploadDate() + return .just(false) + } + + if lastDate == Date().toFormatString(with: "yyyy-MM-dd") { + return .just(true) + } else { + saveMissionUploadDate() + return .just(false) + } + } + + private func saveMissionUploadDate() { + let isoDateFormatter = ISO8601DateFormatter() + isoDateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + let extractedDate = dateFormatter.string(from: Date()) + UserDefaults.standard.set(extractedDate, forKey: lastMissionUploadDateId) + } +} diff --git a/14th-team5-iOS/Domain/Sources/Mission/Interfaces/CheckMissionAlertShowUseCaseProtocol.swift b/14th-team5-iOS/Domain/Sources/Mission/Interfaces/CheckMissionAlertShowUseCaseProtocol.swift new file mode 100644 index 000000000..825b5734c --- /dev/null +++ b/14th-team5-iOS/Domain/Sources/Mission/Interfaces/CheckMissionAlertShowUseCaseProtocol.swift @@ -0,0 +1,14 @@ +// +// MissionAlertUseCaseProtocol.swift +// Domain +// +// Created by 마경미 on 03.06.24. +// + +import Foundation + +import RxSwift + +public protocol CheckMissionAlertShowUseCaseProtocol { + func execute() -> Observable +} diff --git a/14th-team5-iOS/Domain/Sources/Mission/Interfaces/MissionUserdefaultsRepositoryProtocol.swift b/14th-team5-iOS/Domain/Sources/Mission/Interfaces/MissionUserdefaultsRepositoryProtocol.swift new file mode 100644 index 000000000..c26c8cc05 --- /dev/null +++ b/14th-team5-iOS/Domain/Sources/Mission/Interfaces/MissionUserdefaultsRepositoryProtocol.swift @@ -0,0 +1,14 @@ +// +// MissionUserdefaultsRepositoryProtocol.swift +// Domain +// +// Created by 마경미 on 03.06.24. +// + +import Foundation + +import RxSwift + +public protocol MissionUserdefaultsRepositoryProtocol { + func isAlreadyShowMissionAlert() -> Observable +} diff --git a/14th-team5-iOS/Domain/Sources/Mission/UseCases/CheckMissionAlertShowUseCase.swift b/14th-team5-iOS/Domain/Sources/Mission/UseCases/CheckMissionAlertShowUseCase.swift new file mode 100644 index 000000000..253667e8b --- /dev/null +++ b/14th-team5-iOS/Domain/Sources/Mission/UseCases/CheckMissionAlertShowUseCase.swift @@ -0,0 +1,22 @@ +// +// CheckMissionUseCase.swift +// Domain +// +// Created by 마경미 on 03.06.24. +// + +import Foundation + +import RxSwift + +public class CheckMissionAlertShowUseCase: CheckMissionAlertShowUseCaseProtocol { + private let missionUserDefaultsRepository: MissionUserdefaultsRepositoryProtocol + + public init(missionUserDefaultsRepository: MissionUserdefaultsRepositoryProtocol) { + self.missionUserDefaultsRepository = missionUserDefaultsRepository + } + + public func execute() -> Observable { + return missionUserDefaultsRepository.isAlreadyShowMissionAlert().asObservable() + } +}