189 lines
6.8 KiB
Swift
189 lines
6.8 KiB
Swift
//
|
|
// ContentSettingsViewModel.swift
|
|
// SodaLive
|
|
//
|
|
// Created by klaus on 10/10/24.
|
|
//
|
|
|
|
import Foundation
|
|
import Combine
|
|
|
|
final class ContentSettingsViewModel: ObservableObject {
|
|
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
|
|
}
|
|
}
|
|
|
|
func confirmAdultContentAgeCheck() {
|
|
isShowAdultContentAgeCheckDialog = false
|
|
isAdultContentVisible = true
|
|
}
|
|
|
|
func cancelAdultContentAgeCheck() {
|
|
isShowAdultContentAgeCheckDialog = false
|
|
}
|
|
}
|
|
|
|
private struct ContentPreferenceState: Equatable {
|
|
let isAdultContentVisible: Bool
|
|
let contentType: ContentType
|
|
}
|