fix(content): 성인 콘텐츠 설정 동기화와 국가별 인증 분기를 적용한다
This commit is contained in:
@@ -6,28 +6,183 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
final class ContentSettingsViewModel: ObservableObject {
|
||||
@Published var isAdultContentVisible = UserDefaults.isAdultContentVisible() {
|
||||
didSet {
|
||||
if oldValue != isAdultContentVisible {
|
||||
UserDefaults.set(isAdultContentVisible, forKey: .isAdultContentVisible)
|
||||
AppState.shared.isRestartApp = true
|
||||
|
||||
if !isAdultContentVisible {
|
||||
adultContentPreference = .ALL
|
||||
UserDefaults.set(ContentType.ALL.rawValue, forKey: .contentPreference)
|
||||
private let userRepository = UserRepository()
|
||||
private var subscription = Set<AnyCancellable>()
|
||||
private let contentPreferenceSubject = PassthroughSubject<ContentPreferenceState, Never>()
|
||||
private var latestRequestToken = UUID()
|
||||
private var isApplyingServerState = false
|
||||
private var lastSyncedState: ContentPreferenceState
|
||||
|
||||
@Published var isLoading = false
|
||||
@Published var errorMessage = ""
|
||||
@Published var isShowPopup = false
|
||||
@Published var isShowAdultContentAgeCheckDialog = false
|
||||
|
||||
@Published var isAdultContentVisible: Bool
|
||||
@Published var adultContentPreference: ContentType
|
||||
|
||||
init() {
|
||||
let isAdultContentVisible = UserDefaults.isAdultContentVisible()
|
||||
let contentPreference = ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? .ALL
|
||||
let initialState = ContentPreferenceState(
|
||||
isAdultContentVisible: isAdultContentVisible,
|
||||
contentType: isAdultContentVisible ? contentPreference : .ALL
|
||||
)
|
||||
|
||||
_isAdultContentVisible = Published(initialValue: isAdultContentVisible)
|
||||
_adultContentPreference = Published(initialValue: isAdultContentVisible ? contentPreference : .ALL)
|
||||
lastSyncedState = initialState
|
||||
|
||||
bindContentPreference()
|
||||
}
|
||||
|
||||
private func bindContentPreference() {
|
||||
$isAdultContentVisible
|
||||
.dropFirst()
|
||||
.removeDuplicates()
|
||||
.sink { [weak self] isAdultContentVisible in
|
||||
guard let self = self else { return }
|
||||
if self.isApplyingServerState { return }
|
||||
|
||||
if !isAdultContentVisible && self.adultContentPreference != .ALL {
|
||||
self.adultContentPreference = .ALL
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
|
||||
Publishers.CombineLatest($isAdultContentVisible, $adultContentPreference)
|
||||
.map { isAdultContentVisible, adultContentPreference in
|
||||
ContentPreferenceState(
|
||||
isAdultContentVisible: isAdultContentVisible,
|
||||
contentType: isAdultContentVisible ? adultContentPreference : .ALL
|
||||
)
|
||||
}
|
||||
.removeDuplicates()
|
||||
.dropFirst()
|
||||
.sink { [weak self] state in
|
||||
guard let self = self else { return }
|
||||
if self.isApplyingServerState { return }
|
||||
|
||||
self.applyLocalState(state)
|
||||
self.contentPreferenceSubject.send(state)
|
||||
}
|
||||
.store(in: &subscription)
|
||||
|
||||
contentPreferenceSubject
|
||||
.removeDuplicates()
|
||||
.debounce(for: .seconds(0.3), scheduler: RunLoop.main)
|
||||
.sink { [weak self] state in
|
||||
self?.updateContentPreference(state: state)
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
private func updateContentPreference(state: ContentPreferenceState) {
|
||||
let request = makeUpdateContentPreferenceRequest(from: lastSyncedState, to: state)
|
||||
if request.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
let requestToken = UUID()
|
||||
latestRequestToken = requestToken
|
||||
isLoading = true
|
||||
|
||||
userRepository
|
||||
.updateContentPreference(
|
||||
request: request
|
||||
)
|
||||
.sink { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
guard self.latestRequestToken == requestToken else { return }
|
||||
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self = self else { return }
|
||||
guard self.latestRequestToken == requestToken else { return }
|
||||
|
||||
do {
|
||||
let decoded = try JSONDecoder().decode(ApiResponse<UpdateContentPreferenceResponse>.self, from: response.data)
|
||||
|
||||
if let data = decoded.data, decoded.success {
|
||||
let serverState = ContentPreferenceState(
|
||||
isAdultContentVisible: data.isAdultContentVisible,
|
||||
contentType: data.isAdultContentVisible ? data.contentType : .ALL
|
||||
)
|
||||
self.applyServerState(serverState)
|
||||
} else {
|
||||
self.errorMessage = decoded.message ?? I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
self.isLoading = false
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
private func applyLocalState(_ state: ContentPreferenceState) {
|
||||
UserDefaults.set(state.isAdultContentVisible, forKey: .isAdultContentVisible)
|
||||
UserDefaults.set(state.contentType.rawValue, forKey: .contentPreference)
|
||||
AppState.shared.isRestartApp = true
|
||||
}
|
||||
|
||||
private func applyServerState(_ state: ContentPreferenceState) {
|
||||
isApplyingServerState = true
|
||||
isAdultContentVisible = state.isAdultContentVisible
|
||||
adultContentPreference = state.contentType
|
||||
applyLocalState(state)
|
||||
lastSyncedState = state
|
||||
isApplyingServerState = false
|
||||
}
|
||||
|
||||
private func makeUpdateContentPreferenceRequest(from previousState: ContentPreferenceState, to currentState: ContentPreferenceState) -> UpdateContentPreferenceRequest {
|
||||
let isAdultContentVisible = previousState.isAdultContentVisible != currentState.isAdultContentVisible
|
||||
? currentState.isAdultContentVisible
|
||||
: nil
|
||||
let contentType = previousState.contentType != currentState.contentType
|
||||
? currentState.contentType
|
||||
: nil
|
||||
|
||||
return UpdateContentPreferenceRequest(
|
||||
isAdultContentVisible: isAdultContentVisible,
|
||||
contentType: contentType
|
||||
)
|
||||
}
|
||||
|
||||
func handleAdultContentToggleTap() {
|
||||
if isAdultContentVisible {
|
||||
isAdultContentVisible = false
|
||||
} else {
|
||||
isShowAdultContentAgeCheckDialog = true
|
||||
}
|
||||
}
|
||||
|
||||
@Published var adultContentPreference = ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL {
|
||||
didSet {
|
||||
if oldValue != adultContentPreference {
|
||||
UserDefaults.set(adultContentPreference.rawValue, forKey: .contentPreference)
|
||||
AppState.shared.isRestartApp = true
|
||||
}
|
||||
}
|
||||
|
||||
func confirmAdultContentAgeCheck() {
|
||||
isShowAdultContentAgeCheckDialog = false
|
||||
isAdultContentVisible = true
|
||||
}
|
||||
|
||||
func cancelAdultContentAgeCheck() {
|
||||
isShowAdultContentAgeCheckDialog = false
|
||||
}
|
||||
}
|
||||
|
||||
private struct ContentPreferenceState: Equatable {
|
||||
let isAdultContentVisible: Bool
|
||||
let contentType: ContentType
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user