Made StateStoreViewModel state mutable and removed the reducer for all the features using it.

This commit is contained in:
Stefan Ceriu
2022-01-28 12:58:31 +02:00
committed by Stefan Ceriu
parent 88f39336b5
commit b24d7725ec
42 changed files with 324 additions and 431 deletions
@@ -33,12 +33,7 @@ final class LocationSharingCoordinator: Coordinator, Presentable {
private let parameters: LocationSharingCoordinatorParameters
private let locationSharingHostingController: UIViewController
private var _locationSharingViewModel: Any? = nil
@available(iOS 14.0, *)
fileprivate var locationSharingViewModel: LocationSharingViewModel {
return _locationSharingViewModel as! LocationSharingViewModel
}
private var locationSharingViewModel: LocationSharingViewModelProtocol
// MARK: Public
@@ -58,7 +53,7 @@ final class LocationSharingCoordinator: Coordinator, Presentable {
let view = LocationSharingView(context: viewModel.context)
.addDependency(AvatarService.instantiate(mediaManager: parameters.mediaManager))
_locationSharingViewModel = viewModel
locationSharingViewModel = viewModel
locationSharingHostingController = VectorHostingController(rootView: view)
}
@@ -81,20 +76,18 @@ final class LocationSharingCoordinator: Coordinator, Presentable {
return
}
self.locationSharingViewModel.dispatch(action: .startLoading)
self.locationSharingViewModel.startLoading()
self.parameters.roomDataSource.sendLocation(withLatitude: latitude,
longitude: longitude,
description: nil) { [weak self] _ in
self.parameters.roomDataSource.sendLocation(withLatitude: latitude, longitude: longitude, description: nil) { [weak self] _ in
guard let self = self else { return }
self.locationSharingViewModel.dispatch(action: .stopLoading(nil))
self.locationSharingViewModel.stopLoading()
self.completion?()
} failure: { [weak self] error in
guard let self = self else { return }
MXLog.error("[LocationSharingCoordinator] Failed sharing location with error: \(String(describing: error))")
self.locationSharingViewModel.dispatch(action: .stopLoading(error))
self.locationSharingViewModel.stopLoading(error: .locationSharingError)
}
}
@@ -19,31 +19,23 @@ import SwiftUI
import Combine
import CoreLocation
enum LocationSharingViewError {
case failedLoadingMap
case failedLocatingUser
case invalidLocationAuthorization
case failedSharingLocation
}
enum LocationSharingStateAction {
case error(LocationSharingViewError, LocationSharingViewModelCallback?)
case startLoading
case stopLoading(Error?)
}
enum LocationSharingViewAction {
case cancel
case share
}
typealias LocationSharingViewModelCallback = ((LocationSharingViewModelResult) -> Void)
enum LocationSharingViewModelResult {
case cancel
case share(latitude: Double, longitude: Double)
}
enum LocationSharingViewError {
case failedLoadingMap
case failedLocatingUser
case invalidLocationAuthorization
case failedSharingLocation
}
@available(iOS 14, *)
struct LocationSharingViewState: BindableState {
let tileServerMapURL: URL
@@ -80,6 +72,7 @@ struct LocationSharingErrorAlertInfo: Identifiable {
let id: AlertType
let title: String
var subtitle: String? = nil
let primaryButton: (title: String, action: (() -> Void)?)
let secondaryButton: (title: String, action: (() -> Void)?)?
var secondaryButton: (title: String, action: (() -> Void)?)? = nil
}
@@ -19,11 +19,11 @@ import Combine
import CoreLocation
@available(iOS 14, *)
typealias LocationSharingViewModelType = StateStoreViewModel< LocationSharingViewState,
LocationSharingStateAction,
LocationSharingViewAction >
typealias LocationSharingViewModelType = StateStoreViewModel<LocationSharingViewState,
Never,
LocationSharingViewAction>
@available(iOS 14, *)
class LocationSharingViewModel: LocationSharingViewModelType {
class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingViewModelProtocol {
// MARK: - Properties
@@ -41,7 +41,7 @@ class LocationSharingViewModel: LocationSharingViewModelType {
state.errorSubject.sink { [weak self] error in
guard let self = self else { return }
self.dispatch(action: .error(error, self.completion))
self.processError(error)
}.store(in: &cancellables)
}
@@ -58,7 +58,7 @@ class LocationSharingViewModel: LocationSharingViewModelType {
}
guard let location = state.bindings.userLocation else {
dispatch(action: .error(.failedLocatingUser, completion))
processError(.failedLocatingUser)
return
}
@@ -66,45 +66,54 @@ class LocationSharingViewModel: LocationSharingViewModelType {
}
}
override class func reducer(state: inout LocationSharingViewState, action: LocationSharingStateAction) {
switch action {
case .error(let error, let completion):
switch error {
case .failedLoadingMap:
state.bindings.alertInfo = LocationSharingErrorAlertInfo(id: .mapLoadingError,
title: VectorL10n.locationSharingLoadingMapErrorTitle(AppInfo.current.displayName) ,
primaryButton: (VectorL10n.ok, { completion?(.cancel) }),
secondaryButton: nil)
case .failedLocatingUser:
state.bindings.alertInfo = LocationSharingErrorAlertInfo(id: .userLocatingError,
title: VectorL10n.locationSharingLocatingUserErrorTitle(AppInfo.current.displayName),
primaryButton: (VectorL10n.ok, { completion?(.cancel) }),
secondaryButton: nil)
case .invalidLocationAuthorization:
state.bindings.alertInfo = LocationSharingErrorAlertInfo(id: .authorizationError,
title: VectorL10n.locationSharingInvalidAuthorizationErrorTitle(AppInfo.current.displayName),
primaryButton: (VectorL10n.locationSharingInvalidAuthorizationNotNow, { completion?(.cancel) }),
secondaryButton: (VectorL10n.locationSharingInvalidAuthorizationSettings, {
if let applicationSettingsURL = URL(string:UIApplication.openSettingsURLString) {
UIApplication.shared.open(applicationSettingsURL)
}
}))
default:
break
}
case .startLoading:
state.showLoadingIndicator = true
case .stopLoading(let error):
state.showLoadingIndicator = false
if error != nil {
state.bindings.alertInfo = LocationSharingErrorAlertInfo(id: .locationSharingError,
title: VectorL10n.locationSharingInvalidAuthorizationErrorTitle(AppInfo.current.displayName),
primaryButton: (VectorL10n.ok, nil),
secondaryButton: nil)
}
// MARK: - LocationSharingViewModelProtocol
public func startLoading() {
state.showLoadingIndicator = true
}
func stopLoading(error: LocationSharingErrorAlertInfo.AlertType?) {
state.showLoadingIndicator = false
if let error = error {
state.bindings.alertInfo = LocationSharingErrorAlertInfo(id: error,
title: VectorL10n.locationSharingPostFailureTitle,
subtitle: VectorL10n.locationSharingPostFailureSubtitle(AppInfo.current.displayName),
primaryButton: (VectorL10n.ok, nil))
}
}
// MARK: - Private
private func processError(_ error: LocationSharingViewError) {
guard state.bindings.alertInfo == nil else {
return
}
let primaryButtonCompletion = { [weak self] () -> Void in
self?.completion?(.cancel)
}
switch error {
case .failedLoadingMap:
state.bindings.alertInfo = LocationSharingErrorAlertInfo(id: .mapLoadingError,
title: VectorL10n.locationSharingLoadingMapErrorTitle(AppInfo.current.displayName),
primaryButton: (VectorL10n.ok, primaryButtonCompletion))
case .failedLocatingUser:
state.bindings.alertInfo = LocationSharingErrorAlertInfo(id: .userLocatingError,
title: VectorL10n.locationSharingLocatingUserErrorTitle(AppInfo.current.displayName),
primaryButton: (VectorL10n.ok, primaryButtonCompletion))
case .invalidLocationAuthorization:
state.bindings.alertInfo = LocationSharingErrorAlertInfo(id: .authorizationError,
title: VectorL10n.locationSharingInvalidAuthorizationErrorTitle(AppInfo.current.displayName),
primaryButton: (VectorL10n.locationSharingInvalidAuthorizationNotNow, primaryButtonCompletion),
secondaryButton: (VectorL10n.locationSharingInvalidAuthorizationSettings, {
if let applicationSettingsURL = URL(string:UIApplication.openSettingsURLString) {
UIApplication.shared.open(applicationSettingsURL)
}
}))
default:
break
}
}
}
@@ -0,0 +1,30 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
protocol LocationSharingViewModelProtocol {
var completion: ((LocationSharingViewModelResult) -> Void)? { get set }
func startLoading()
func stopLoading(error: LocationSharingErrorAlertInfo.AlertType?)
}
extension LocationSharingViewModelProtocol {
func stopLoading() {
stopLoading(error: nil)
}
}
@@ -41,7 +41,7 @@ class LocationSharingUITests: XCTestCase {
goToScreenWithIdentifier(MockLocationSharingScreenState.displayExistingLocation.title)
XCTAssertTrue(app.buttons["Cancel"].exists)
XCTAssertTrue(app.buttons["location share icon"].exists)
XCTAssertTrue(app.buttons["LocationSharingView.shareButton"].exists)
XCTAssertTrue(app.otherElements["Map"].exists)
}
@@ -100,12 +100,12 @@ class LocationSharingViewModelTests: XCTestCase {
func testLoading() {
let viewModel = buildViewModel(withLocation: false)
viewModel.dispatch(action: .startLoading)
viewModel.startLoading()
XCTAssertFalse(viewModel.context.viewState.shareButtonEnabled)
XCTAssertTrue(viewModel.context.viewState.showLoadingIndicator)
viewModel.dispatch(action: .stopLoading(nil))
viewModel.stopLoading()
XCTAssertTrue(viewModel.context.viewState.shareButtonEnabled)
XCTAssertFalse(viewModel.context.viewState.showLoadingIndicator)
@@ -68,7 +68,7 @@ class LocationSharingMapViewCoordinator: NSObject, MGLMapViewDelegate {
private let avatarData: AvatarInputProtocol
private let errorSubject: PassthroughSubject<LocationSharingViewError, Never>
@Binding var userLocation: CLLocationCoordinate2D?
@Binding private var userLocation: CLLocationCoordinate2D?
init(avatarData: AvatarInputProtocol,
errorSubject: PassthroughSubject<LocationSharingViewError, Never>,
@@ -89,6 +89,10 @@ class LocationSharingMapViewCoordinator: NSObject, MGLMapViewDelegate {
}
func mapView(_ mapView: MGLMapView, didFailToLocateUserWithError error: Error) {
guard mapView.showsUserLocation else {
return
}
errorSubject.send(.failedLocatingUser)
}
@@ -97,11 +101,15 @@ class LocationSharingMapViewCoordinator: NSObject, MGLMapViewDelegate {
}
func mapView(_ mapView: MGLMapView, didChangeLocationManagerAuthorization manager: MGLLocationManager) {
guard mapView.showsUserLocation else {
return
}
switch manager.authorizationStatus {
case .restricted:
fallthrough
case .denied:
errorSubject.send(.failedLocatingUser)
errorSubject.send(.invalidLocationAuthorization)
default:
break
}
@@ -54,6 +54,7 @@ struct LocationSharingView: View {
context.send(viewAction: .share)
} label: {
Image(uiImage: Asset.Images.locationShareIcon.image)
.accessibilityIdentifier("LocationSharingView.shareButton")
}
.disabled(!context.viewState.shareButtonEnabled)
} else {
@@ -69,6 +70,7 @@ struct LocationSharingView: View {
.alert(item: $context.alertInfo) { info in
if let secondaryButton = info.secondaryButton {
return Alert(title: Text(info.title),
message: subtitleTextForAlertInfo(info),
primaryButton: .default(Text(info.primaryButton.title)) {
info.primaryButton.action?()
},
@@ -77,6 +79,7 @@ struct LocationSharingView: View {
})
} else {
return Alert(title: Text(info.title),
message: subtitleTextForAlertInfo(info),
dismissButton: .default(Text(info.primaryButton.title)) {
info.primaryButton.action?()
})
@@ -93,6 +96,14 @@ struct LocationSharingView: View {
ActivityIndicator()
}
}
private func subtitleTextForAlertInfo(_ alertInfo: LocationSharingErrorAlertInfo) -> Text? {
guard let subtitle = alertInfo.subtitle else {
return nil
}
return Text(subtitle)
}
}
// MARK: - Previews