Add StateStoreViewModel and publisher extensions for convenienec.

This commit is contained in:
David Langley
2021-09-14 22:28:25 +01:00
parent 3d65fbd48f
commit da5fcd5d4f
8 changed files with 159 additions and 31 deletions
@@ -39,8 +39,8 @@ final class TemplateUserProfileCoordinator: Coordinator {
@available(iOS 14.0, *)
init(parameters: TemplateUserProfileCoordinatorParameters) {
self.parameters = parameters
let viewModel = TemplateUserProfileViewModel(templateUserProfileService: TemplateUserProfileService(session: parameters.session))
let view = TemplateUserProfile(viewModel: viewModel)
let viewModel = TemplateUserProfileViewModel.makeTemplateUserProfileViewModel(templateUserProfileService: TemplateUserProfileService(session: parameters.session))
let view = TemplateUserProfile(viewModel: viewModel.context)
.addDependency(AvatarService.instantiate(mediaManager: parameters.session.mediaManager))
templateUserProfileViewModel = viewModel
templateUserProfileHostingController = VectorHostingController(rootView: view)
@@ -16,7 +16,7 @@
import Foundation
struct TemplateUserProfileViewState {
struct TemplateUserProfileViewState: BindableState {
let avatar: AvatarInputProtocol?
let displayName: String?
var presence: TemplateUserProfilePresence
@@ -50,11 +50,11 @@ enum MockTemplateUserProfileScreenState: MockScreenState, CaseIterable {
case .longDisplayName(let displayName):
service = MockTemplateUserProfileService(displayName: displayName)
}
let viewModel = TemplateUserProfileViewModel(templateUserProfileService: service)
let viewModel = TemplateUserProfileViewModel.makeTemplateUserProfileViewModel(templateUserProfileService: service)
// can simulate service and viewModel actions here if needs be.
return AnyView(TemplateUserProfile(viewModel: viewModel)
return AnyView(TemplateUserProfile(viewModel: viewModel.context)
.addDependency(MockAvatarService.example))
}
}
@@ -27,9 +27,10 @@ struct TemplateUserProfile: View {
// MARK: Public
@ObservedObject var viewModel: TemplateUserProfileViewModel
@ObservedObject var viewModel: TemplateUserProfileViewModel.Context
var body: some View {
EmptyView()
VStack {
TemplateUserProfileHeader(
avatar: viewModel.viewState.avatar,
@@ -50,12 +51,12 @@ struct TemplateUserProfile: View {
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(VectorL10n.done) {
viewModel.process(viewAction: .cancel)
viewModel.inputActions.send(.done)
}
}
ToolbarItem(placement: .cancellationAction) {
Button(VectorL10n.cancel) {
viewModel.process(viewAction: .cancel)
viewModel.inputActions.send(.cancel)
}
}
}
@@ -16,33 +16,36 @@
import SwiftUI
import Combine
@available(iOS 14.0, *)
class TemplateUserProfileViewModel: ObservableObject, TemplateUserProfileViewModelProtocol {
@available(iOS 14, *)
typealias TemplateUserProfileViewModelType = StateStoreViewModel<TemplateUserProfileViewState,
TemplateUserProfileStateAction,
TemplateUserProfileViewAction>
@available(iOS 14, *)
class TemplateUserProfileViewModel: TemplateUserProfileViewModelType, TemplateUserProfileViewModelProtocol {
// MARK: - Properties
// MARK: Private
private let templateUserProfileService: TemplateUserProfileServiceProtocol
private var cancellables = Set<AnyCancellable>()
// MARK: Public
@Published private(set) var viewState: TemplateUserProfileViewState
var completion: ((TemplateUserProfileViewModelResult) -> Void)?
// MARK: - Setup
init(templateUserProfileService: TemplateUserProfileServiceProtocol, initialState: TemplateUserProfileViewState? = nil) {
static func makeTemplateUserProfileViewModel(templateUserProfileService: TemplateUserProfileServiceProtocol) -> TemplateUserProfileViewModelProtocol {
return TemplateUserProfileViewModel(templateUserProfileService: templateUserProfileService)
}
fileprivate init(templateUserProfileService: TemplateUserProfileServiceProtocol) {
self.templateUserProfileService = templateUserProfileService
self.viewState = initialState ?? Self.defaultState(templateUserProfileService: templateUserProfileService)
templateUserProfileService.presenceSubject
.map(TemplateUserProfileStateAction.updatePresence)
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] action in
self?.dispatch(action:action)
})
.store(in: &cancellables)
super.init(initialViewState: Self.defaultState(templateUserProfileService: templateUserProfileService))
setupPresenceObserving()
}
private static func defaultState(templateUserProfileService: TemplateUserProfileServiceProtocol) -> TemplateUserProfileViewState {
@@ -53,8 +56,15 @@ class TemplateUserProfileViewModel: ObservableObject, TemplateUserProfileViewMod
)
}
private func setupPresenceObserving() {
templateUserProfileService.presenceSubject
.map(TemplateUserProfileStateAction.updatePresence)
.sinkDispatchTo(self)
}
// MARK: - Public
func process(viewAction: TemplateUserProfileViewAction) {
override func process(viewAction: TemplateUserProfileViewAction) {
switch viewAction {
case .cancel:
cancel()
@@ -62,13 +72,6 @@ class TemplateUserProfileViewModel: ObservableObject, TemplateUserProfileViewMod
done()
}
}
// MARK: - Private
/// Send state actions to mutate the state.
/// - Parameter action: The `TemplateUserProfileStateAction` to trigger the state change.
private func dispatch(action: TemplateUserProfileStateAction) {
Self.reducer(state: &self.viewState, action: action)
}
/// A redux style reducer
///
@@ -17,5 +17,10 @@
import Foundation
protocol TemplateUserProfileViewModelProtocol {
var completion: ((TemplateUserProfileViewModelResult) -> Void)? { get set }
@available(iOS 14, *)
static func makeTemplateUserProfileViewModel(templateUserProfileService: TemplateUserProfileServiceProtocol) -> TemplateUserProfileViewModelProtocol
@available(iOS 14, *)
var context: TemplateUserProfileViewModelType.Context { get }
}