diff --git a/SOPT-iOS/Projects/Core/Sources/Extension/Foundation+/Date+.swift b/SOPT-iOS/Projects/Core/Sources/Extension/Foundation+/Date+.swift new file mode 100644 index 00000000..f1182685 --- /dev/null +++ b/SOPT-iOS/Projects/Core/Sources/Extension/Foundation+/Date+.swift @@ -0,0 +1,28 @@ +// +// Date+.swift +// Core +// +// Created by sejin on 2023/04/17. +// Copyright © 2023 SOPT-iOS. All rights reserved. +// + +import Foundation + +public extension Date { + /// Create a date from specified parameters + /// + /// - Parameters: + /// - year: The desired year + /// - month: The desired month + /// - day: The desired day + /// - Returns: A `Date` object + static func from(year: Int, month: Int, day: Int) -> Date? { + let calendar = Calendar(identifier: .gregorian) + var dateComponents = DateComponents() + dateComponents.timeZone = TimeZone.current + dateComponents.year = year + dateComponents.month = month + dateComponents.day = day + return calendar.date(from: dateComponents) ?? nil + } +} diff --git a/SOPT-iOS/Projects/Domain/Sources/Model/UserMainInfoModel.swift b/SOPT-iOS/Projects/Domain/Sources/Model/UserMainInfoModel.swift index 9284c55b..cdae37cb 100644 --- a/SOPT-iOS/Projects/Domain/Sources/Model/UserMainInfoModel.swift +++ b/SOPT-iOS/Projects/Domain/Sources/Model/UserMainInfoModel.swift @@ -9,13 +9,15 @@ import Foundation public struct UserMainInfoModel { - public let status, name, profileImage: String + public let status, name: String + public let profileImage: String? public let historyList: [Int] - public let attendanceScore: Float - public let announcement: String + public let attendanceScore: Float? + public let announcement: String? public let responseMessage: String? + public var withError: Bool = false - public init(status: String, name: String, profileImage: String, historyList: [Int], attendanceScore: Float, announcement: String, responseMessage: String?) { + public init(status: String, name: String, profileImage: String?, historyList: [Int], attendanceScore: Float?, announcement: String?, responseMessage: String?) { self.status = status self.name = name self.profileImage = profileImage @@ -24,4 +26,9 @@ public struct UserMainInfoModel { self.announcement = announcement self.responseMessage = responseMessage } + + public init(withError: Bool) { + self.init(status: "", name: "", profileImage: nil, historyList: [], attendanceScore: nil, announcement: nil, responseMessage: nil) + self.withError = withError + } } diff --git a/SOPT-iOS/Projects/Domain/Sources/UseCase/MainUseCase.swift b/SOPT-iOS/Projects/Domain/Sources/UseCase/MainUseCase.swift index b76c36b4..dc61cbd7 100644 --- a/SOPT-iOS/Projects/Domain/Sources/UseCase/MainUseCase.swift +++ b/SOPT-iOS/Projects/Domain/Sources/UseCase/MainUseCase.swift @@ -33,6 +33,7 @@ public class DefaultMainUseCase { extension DefaultMainUseCase: MainUseCase { public func getUserMainInfo() { repository.getUserMainInfo() + .replaceError(with: UserMainInfoModel.init(withError: true)) .sink { event in print("MainUseCase: \(event)") } receiveValue: { [weak self] userMainInfoModel in diff --git a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/VC/MainVC.swift b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/VC/MainVC.swift index db89f9d7..4d2aba91 100644 --- a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/VC/MainVC.swift +++ b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/VC/MainVC.swift @@ -18,6 +18,7 @@ import SnapKit import Then import AuthFeatureInterface +import BaseFeatureDependency import MainFeatureInterface import StampFeatureInterface import SettingFeatureInterface @@ -30,6 +31,7 @@ public class MainVC: UIViewController, MainViewControllable { & SettingFeatureViewBuildable & AppMyPageFeatureViewBuildable & AttendanceFeatureViewBuildable + & AlertViewBuildable // MARK: - Properties @@ -37,8 +39,8 @@ public class MainVC: UIViewController, MainViewControllable { public var factory: factoryType! private var cancelBag = CancelBag() - private var userMainInfo: UserMainInfoModel? - + private var requestUserInfo = CurrentValueSubject(()) + // MARK: - UI Components private let naviBar = MainNavigationBar() @@ -96,16 +98,20 @@ extension MainVC { extension MainVC { private func bindViewModels() { - let input = MainViewModel.Input(viewDidLoad: Driver.just(())) + let input = MainViewModel.Input(requestUserInfo: self.requestUserInfo) let output = self.viewModel.transform(from: input, cancelBag: self.cancelBag) output.getUserMainInfoDidComplete .sink { [weak self] _ in + guard let userMainInfo = self?.viewModel.userMainInfo, userMainInfo.withError == false else { + self?.presentNetworkAlertVC() + return + } self?.collectionView.reloadData() }.store(in: self.cancelBag) output.isServiceAvailable - .sink { [weak self] isServiceAvailable in + .sink { isServiceAvailable in print("현재 앱 서비스 사용 가능(심사 X)?: \(isServiceAvailable)") }.store(in: self.cancelBag) } @@ -117,12 +123,6 @@ extension MainVC { .sink { owner, _ in let viewController = owner.factory.makeAppMyPageVC(userType: owner.viewModel.userType).viewController owner.navigationController?.pushViewController(viewController, animated: true) - -// if owner.viewModel.userType == .visitor { -// owner.setRootViewToSignIn() -// return -// } -// owner.pushSettingFeature() }.store(in: self.cancelBag) } @@ -160,6 +160,20 @@ extension MainVC { let navigation = UINavigationController(rootViewController: factory.makeSignInVC().viewController) ViewControllerUtils.setRootViewController(window: self.view.window!, viewController: navigation, withAnimation: true) } + + private func presentNetworkAlertVC() { + let networkAlertVC = factory.makeAlertVC( + type: .titleDescription, + theme: .main, + title: I18N.Default.networkError, + description: I18N.Default.networkErrorDescription, + customButtonTitle: I18N.Default.ok, + customAction:{ [weak self] in + self?.requestUserInfo.send() + }).viewController + + self.present(networkAlertVC, animated: false) + } } // MARK: - UICollectionViewDelegate 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 71524ad9..53513711 100644 --- a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/ViewModel/MainViewModel.swift +++ b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/ViewModel/MainViewModel.swift @@ -28,7 +28,7 @@ public class MainViewModel: ViewModelType { // MARK: - Inputs public struct Input { - let viewDidLoad: Driver + let requestUserInfo: CurrentValueSubject } // MARK: - Outputs @@ -54,7 +54,7 @@ extension MainViewModel { let output = Output() self.bindOutput(output: output, cancelBag: cancelBag) - input.viewDidLoad + input.requestUserInfo .sink { [weak self] _ in guard let self = self else { return } if self.userType != .visitor { @@ -74,7 +74,7 @@ extension MainViewModel { }.store(in: self.cancelBag) useCase.serviceState.asDriver() - .sink { [weak self] serviceState in + .sink { serviceState in output.isServiceAvailable.send(serviceState.isAvailable) }.store(in: self.cancelBag) } @@ -94,37 +94,31 @@ extension MainViewModel { } } + /// 최초 솝트 가입일로부터 몇달이 지났는지 계산 func calculateMonths() -> String? { - guard let userMainInfo = userMainInfo else { return nil } - if userMainInfo.status == "ACTIVE" && userMainInfo.historyList.count > 0 { - guard var currentMonth = getCurrentMonth() else { - return String(userMainInfo.historyList.count * 5) - } - - // 짝수 기수 -> 상반기 - if let recent = userMainInfo.historyList.first { - var currentGenerationTime = 0 - if recent % 2 == 0 { - // 기수 시작 3월 기준으로 계산 - currentGenerationTime = currentMonth - 3 + 1 - } else { - // 현재 1월 일 때 - if currentMonth < 3 { - currentMonth += 12 - } - currentGenerationTime = currentMonth - 9 + 1 - } - return String((userMainInfo.historyList.count-1)*5 + currentGenerationTime) - } - } - return String(userMainInfo.historyList.count * 5) + guard let userMainInfo = userMainInfo, let firstHistory = userMainInfo.historyList.last else { return nil } + guard let joinDate = calculateJoinDateWithFirstHistory(history: firstHistory), let monthDifference = calculateMonthDifference(since: joinDate) else { return nil } + + return String(monthDifference) } - private func getCurrentMonth() -> Int? { - let date = Date() - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "M" - let monthString = dateFormatter.string(from: date) - return Int(monthString) + // 파라미터로 넣은 기수의 시작 날짜를 리턴 + private func calculateJoinDateWithFirstHistory(history: Int) -> Date? { + let yearDifference = history / 2 + let month = (history % 2 == 0) ? 3 : 9 // 짝수 기수는 3월, 홀수 기수는 9월 시작 + // 1기를 2007년으로 계산 + return Date.from(year: yearDifference + 2007, month: month, day: 1) + } + + // 파라미터로 넣은 날짜로 부터 현재 몇달이 지났는지 계산 + private func calculateMonthDifference(since startDate: Date) -> Int? { + let calendar = Calendar.current + + let components = calendar.dateComponents([.month], from: startDate, to: .now) + guard let month = components.month else { + return nil + } + + return month >= 0 ? month + 1 : nil } }