diff --git a/ElementX/Sources/Application/Settings/AppSettings.swift b/ElementX/Sources/Application/Settings/AppSettings.swift index 3662ba22c..88905c281 100644 --- a/ElementX/Sources/Application/Settings/AppSettings.swift +++ b/ElementX/Sources/Application/Settings/AppSettings.swift @@ -81,7 +81,6 @@ final class AppSettings { case linkPreviewsEnabled case focusEventOnNotificationTap case linkNewDeviceEnabled - case liveLocationSharingEnabled case automaticBackPaginationEnabled // Doug's tweaks 🔧 @@ -449,9 +448,6 @@ final class AppSettings { @UserPreference(key: UserDefaultsKeys.linkNewDeviceEnabled, defaultValue: false, storageType: .userDefaults(store)) var linkNewDeviceEnabled - @UserPreference(key: UserDefaultsKeys.liveLocationSharingEnabled, defaultValue: false, storageType: .userDefaults(store)) - var liveLocationSharingEnabled - @UserPreference(key: UserDefaultsKeys.automaticBackPaginationEnabled, defaultValue: false, storageType: .userDefaults(store)) var automaticBackPaginationEnabled diff --git a/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift index 40b1ac6fd..ef0808b9d 100644 --- a/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift @@ -108,7 +108,6 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol { let params = LocationSharingScreenCoordinatorParameters(interactionMode: interactionMode, mapURLBuilder: flowParameters.appSettings.mapTilerConfiguration, - liveLocationSharingEnabled: flowParameters.appSettings.liveLocationSharingEnabled, roomProxy: roomProxy, timelineController: timelineController, liveLocationManager: flowParameters.userSession.liveLocationManager, diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index b2a23eb8f..8d3a08dd2 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -1168,7 +1168,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { let params = LocationSharingScreenCoordinatorParameters(interactionMode: interactionMode, mapURLBuilder: flowParameters.appSettings.mapTilerConfiguration, - liveLocationSharingEnabled: flowParameters.appSettings.liveLocationSharingEnabled, roomProxy: roomProxy, timelineController: timelineController, liveLocationManager: flowParameters.userSession.liveLocationManager, diff --git a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenCoordinator.swift b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenCoordinator.swift index 5a5e03b6e..1a5356d93 100644 --- a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenCoordinator.swift +++ b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenCoordinator.swift @@ -12,7 +12,6 @@ import SwiftUI struct LocationSharingScreenCoordinatorParameters { let interactionMode: LocationSharingInteractionMode let mapURLBuilder: MapTilerURLBuilderProtocol - let liveLocationSharingEnabled: Bool let roomProxy: JoinedRoomProxyProtocol let timelineController: TimelineControllerProtocol let liveLocationManager: LiveLocationManagerProtocol @@ -42,7 +41,6 @@ final class LocationSharingScreenCoordinator: CoordinatorProtocol { viewModel = LocationSharingScreenViewModel(interactionMode: parameters.interactionMode, mapURLBuilder: parameters.mapURLBuilder, - liveLocationSharingEnabled: parameters.liveLocationSharingEnabled, roomProxy: parameters.roomProxy, timelineController: parameters.timelineController, liveLocationManager: parameters.liveLocationManager, diff --git a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift index 3af559547..5e1213c97 100644 --- a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift +++ b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift @@ -32,11 +32,9 @@ enum LocationSharingInteractionMode: Hashable { struct LocationSharingScreenViewState: BindableState { init(interactionMode: LocationSharingInteractionMode, mapURLBuilder: MapTilerURLBuilderProtocol, - showLiveLocationSharingButton: Bool, ownUserID: String) { self.interactionMode = interactionMode self.mapURLBuilder = mapURLBuilder - self.showLiveLocationSharingButton = showLiveLocationSharingButton self.ownUserID = ownUserID let initialProfile: UserProfileProxy = switch interactionMode { @@ -65,7 +63,6 @@ struct LocationSharingScreenViewState: BindableState { let interactionMode: LocationSharingInteractionMode let mapURLBuilder: MapTilerURLBuilderProtocol - let showLiveLocationSharingButton: Bool let ownUserID: String var userProfiles: [String: UserProfileProxy] var liveLocationShares: [LiveLocationShare] = [] diff --git a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift index 76fc56647..d45a695d0 100644 --- a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift +++ b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift @@ -33,7 +33,6 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati init(interactionMode: LocationSharingInteractionMode, mapURLBuilder: MapTilerURLBuilderProtocol, - liveLocationSharingEnabled: Bool, roomProxy: JoinedRoomProxyProtocol, timelineController: TimelineControllerProtocol, liveLocationManager: LiveLocationManagerProtocol, @@ -50,7 +49,6 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati super.init(initialViewState: .init(interactionMode: interactionMode, mapURLBuilder: mapURLBuilder, - showLiveLocationSharingButton: liveLocationSharingEnabled, ownUserID: roomProxy.ownUserID), mediaProvider: mediaProvider) @@ -328,8 +326,7 @@ extension LocationSharingScreenViewModel { } static func mock(type: MockType, - senderID: String = "@dan:matrix.org", - liveLocationSharingEnabled: Bool = true) -> LocationSharingScreenViewModel { + senderID: String = "@dan:matrix.org") -> LocationSharingScreenViewModel { let interactionMode: LocationSharingInteractionMode = switch type { case .picker: .picker @@ -378,7 +375,6 @@ extension LocationSharingScreenViewModel { return LocationSharingScreenViewModel(interactionMode: interactionMode, mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration, - liveLocationSharingEnabled: liveLocationSharingEnabled, roomProxy: roomProxy, timelineController: MockTimelineController(), liveLocationManager: LiveLocationManagerMock(), diff --git a/ElementX/Sources/Screens/LocationSharing/View/LocationPickerSheet.swift b/ElementX/Sources/Screens/LocationSharing/View/LocationPickerSheet.swift index 1e122c18a..cc054a532 100644 --- a/ElementX/Sources/Screens/LocationSharing/View/LocationPickerSheet.swift +++ b/ElementX/Sources/Screens/LocationSharing/View/LocationPickerSheet.swift @@ -12,13 +12,6 @@ struct LocationPickerSheet: View { @Bindable var context: LocationSharingScreenViewModel.Context @State private var height: CGFloat = .zero - /// Fixes an iOS 26 sheet issue - /// if the content doesn't meet a certain size - /// additional insets are added. - private var additionalHeight: CGFloat { - context.viewState.showLiveLocationSharingButton ? 0 : 28 - } - var body: some View { VStack(spacing: 0) { Text(L10n.screenSharingLocationOptionSheetTitle) @@ -26,6 +19,7 @@ struct LocationPickerSheet: View { .font(.compound.bodyLGSemibold) .padding(.top, 29) .padding(.bottom, 25) + Button { context.send(viewAction: .selectLocation) } label: { @@ -39,14 +33,13 @@ struct LocationPickerSheet: View { iconColor: .compound.iconSecondary) } } - if context.viewState.showLiveLocationSharingButton { - Button { - context.send(viewAction: .startLiveLocation) - } label: { - LocationPickerLabel(text: L10n.actionShareLiveLocation, - icon: \.locationPinSolid, - iconColor: .compound.iconAccentPrimary) - } + + Button { + context.send(viewAction: .startLiveLocation) + } label: { + LocationPickerLabel(text: L10n.actionShareLiveLocation, + icon: \.locationPinSolid, + iconColor: .compound.iconAccentPrimary) } } .readHeight($height) @@ -54,7 +47,7 @@ struct LocationPickerSheet: View { .presentationBackground(.compound.bgCanvasDefault) .presentationBackgroundInteraction(.enabled) .presentationDragIndicator(.hidden) - .presentationDetents([.height(height + additionalHeight)]) + .presentationDetents([.height(height)]) } } diff --git a/ElementX/Sources/Screens/LocationSharing/View/LocationSharingScreen.swift b/ElementX/Sources/Screens/LocationSharing/View/LocationSharingScreen.swift index 77b7b7dbd..826d21f61 100644 --- a/ElementX/Sources/Screens/LocationSharing/View/LocationSharingScreen.swift +++ b/ElementX/Sources/Screens/LocationSharing/View/LocationSharingScreen.swift @@ -126,9 +126,7 @@ struct LocationSharingScreen: View { struct LocationSharingScreen_Previews: PreviewProvider, TestablePreview { static let viewModel = LocationSharingScreenViewModel.mock(type: .staticSenderLocation) - - static let withoutLiveSharingViewModel = LocationSharingScreenViewModel.mock(type: .picker, liveLocationSharingEnabled: false) - + static let pinViewModel = LocationSharingScreenViewModel.mock(type: .staticPinLocation) static let pickerViewModel = LocationSharingScreenViewModel.mock(type: .picker) @@ -141,11 +139,6 @@ struct LocationSharingScreen_Previews: PreviewProvider, TestablePreview { } .previewDisplayName("Picker") - ElementNavigationStack { - LocationSharingScreen(context: withoutLiveSharingViewModel.context) - } - .previewDisplayName("Picker without live location sharing") - ElementNavigationStack { LocationSharingScreen(context: viewModel.context) } diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 4cd98475f..b8e8d0f5d 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -171,12 +171,11 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol .weakAssign(to: \.state.isKnockingEnabled, on: self) .store(in: &cancellables) - appSettings.$liveLocationSharingEnabled - .combineLatest(appSettings.$liveLocationSharingTimeoutDatesByRoomID) + appSettings.$liveLocationSharingTimeoutDatesByRoomID .receive(on: DispatchQueue.main) - .sink { [weak self] isEnabled, timeoutDatesByRoomID in + .sink { [weak self] timeoutDatesByRoomID in guard let self else { return } - state.isSharingLiveLocation = isEnabled && timeoutDatesByRoomID.keys.contains(roomProxy.id) + state.isSharingLiveLocation = timeoutDatesByRoomID.keys.contains(roomProxy.id) } .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/Settings/AdvancedSettingsScreen/AdvancedSettingsScreenModels.swift b/ElementX/Sources/Screens/Settings/AdvancedSettingsScreen/AdvancedSettingsScreenModels.swift index 8273bdc08..5dc19d436 100644 --- a/ElementX/Sources/Screens/Settings/AdvancedSettingsScreen/AdvancedSettingsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/AdvancedSettingsScreen/AdvancedSettingsScreenModels.swift @@ -61,7 +61,6 @@ protocol AdvancedSettingsProtocol: AnyObject { var sharePresence: Bool { get set } var optimizeMediaUploads: Bool { get set } var liveLocationMinimumDistanceUpdate: Int { get set } - var liveLocationSharingEnabled: Bool { get set } } extension AppSettings: AdvancedSettingsProtocol { } diff --git a/ElementX/Sources/Screens/Settings/AdvancedSettingsScreen/View/AdvancedSettingsScreen.swift b/ElementX/Sources/Screens/Settings/AdvancedSettingsScreen/View/AdvancedSettingsScreen.swift index f2ab8e17f..e0ab2ba19 100644 --- a/ElementX/Sources/Screens/Settings/AdvancedSettingsScreen/View/AdvancedSettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/AdvancedSettingsScreen/View/AdvancedSettingsScreen.swift @@ -44,9 +44,7 @@ struct AdvancedSettingsScreen: View { moderationAndSafetySection timelineMediaSection - if context.liveLocationSharingEnabled { - liveLocationSection - } + liveLocationSection } .compoundList() .navigationTitle(L10n.commonAdvancedSettings) @@ -164,9 +162,7 @@ private extension AppAppearance { struct AdvancedSettingsScreen_Previews: PreviewProvider, TestablePreview { static let viewModel = { AppSettings.resetAllSettings() - let appSettings = AppSettings() - appSettings.liveLocationSharingEnabled = true - return AdvancedSettingsScreenViewModel(advancedSettings: appSettings, + return AdvancedSettingsScreenViewModel(advancedSettings: AppSettings(), analytics: ServiceLocator.shared.analytics, clientProxy: ClientProxyMock(.init()), userIndicatorController: UserIndicatorControllerMock()) diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index e59a4b391..f15689a2b 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -70,9 +70,7 @@ protocol DeveloperOptionsProtocol: AnyObject { var linkPreviewsEnabled: Bool { get set } var linkNewDeviceEnabled: Bool { get set } - - var liveLocationSharingEnabled: Bool { get set } - + var roomThreadListEnabled: Bool { get set } } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index 4c4244ac7..1a39d797e 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -91,11 +91,6 @@ struct DeveloperOptionsScreen: View { .foregroundStyle(.compound.textCriticalPrimary) } - Toggle(isOn: $context.liveLocationSharingEnabled) { - Text("Live location sharing") - Text("Requires app reboot") - } - Toggle(isOn: $context.knockingEnabled) { Text("Knocking") Text("Ask to join rooms") diff --git a/ElementX/Sources/Services/Location/LiveLocationManager.swift b/ElementX/Sources/Services/Location/LiveLocationManager.swift index db6567be6..62dd8077e 100644 --- a/ElementX/Sources/Services/Location/LiveLocationManager.swift +++ b/ElementX/Sources/Services/Location/LiveLocationManager.swift @@ -196,16 +196,6 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana } .store(in: &cancellables) - appSettings.$liveLocationSharingEnabled - .filter { !$0 } - .sink { [weak self] _ in - guard let self else { return } - appSettings.liveLocationSharingTimeoutDatesByRoomID.removeAll() - activeRoomProxies.removeAll() - self.stopUpdatingLocation() - } - .store(in: &cancellables) - appSettings.$liveLocationMinimumDistanceUpdate .removeDuplicates() .debounce(for: .seconds(1), scheduler: DispatchQueue.main) diff --git a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift index 38fd3503f..394ff22c0 100644 --- a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift +++ b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift @@ -91,7 +91,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { let openRoomSpan = analyticsService.signpost.addSpan(.timelineLoad, toTransaction: .openRoom) timeline = try await TimelineProxy(timeline: room.timelineWithConfiguration(configuration: .init(focus: .live(hideThreadedEvents: appSettings.threadsEnabled), - filter: .eventFilter(filter: Self.excludedEventsFilter(appSettings: appSettings)), + filter: .eventFilter(filter: Self.excludedEventsFilter), internalIdPrefix: nil, dateDividerMode: .daily, trackReadReceipts: .messageLikeEvents, @@ -845,7 +845,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { } } - private static func excludedEventsFilter(appSettings: AppSettings) -> TimelineEventFilter { + private static let excludedEventsFilter: TimelineEventFilter = { var stateEventFilters: [StateEventType] = [.roomCanonicalAlias, .roomGuestAccess, .roomHistoryVisibility, @@ -859,11 +859,6 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { .policyRuleRoom, .policyRuleServer, .policyRuleUser] - - if !appSettings.liveLocationSharingEnabled { - stateEventFilters.append(.beaconInfo) - } - return .excludeEventTypes(eventTypes: stateEventFilters.map { FilterTimelineEventType.state(eventType: $0) }) - } + }() } diff --git a/UnitTests/Sources/LocationSharingScreenViewModelTests.swift b/UnitTests/Sources/LocationSharingScreenViewModelTests.swift index 86ebedf94..f70895f7a 100644 --- a/UnitTests/Sources/LocationSharingScreenViewModelTests.swift +++ b/UnitTests/Sources/LocationSharingScreenViewModelTests.swift @@ -12,7 +12,7 @@ import CoreLocation import Testing @MainActor -final class LocationSharingScreenViewModelTests { +struct LocationSharingScreenViewModelTests { private var timelineProxy: TimelineProxyMock! private var viewModel: LocationSharingScreenViewModel! @@ -20,16 +20,8 @@ final class LocationSharingScreenViewModelTests { viewModel.context } - init() { - AppSettings.resetAllSettings() - } - - deinit { - AppSettings.resetAllSettings() - } - @Test - func userDidPan() { + mutating func userDidPan() { setupViewModel() #expect(context.viewState.isSharingUserLocation) #expect(context.showsUserLocationMode == .showAndFollow) @@ -39,7 +31,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func centerOnUser() { + mutating func centerOnUser() { setupViewModel() #expect(context.viewState.isSharingUserLocation) context.showsUserLocationMode = .show @@ -50,7 +42,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func centerOnUserWithoutAuthorization() { + mutating func centerOnUserWithoutAuthorization() { setupViewModel() context.showsUserLocationMode = .hide context.isLocationAuthorized = nil @@ -59,7 +51,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func centerOnUserWithDeniedAuthorization() { + mutating func centerOnUserWithDeniedAuthorization() { setupViewModel() context.isLocationAuthorized = false context.showsUserLocationMode = .hide @@ -69,7 +61,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func errorMapping() { + mutating func errorMapping() { setupViewModel() let mapError = AlertInfo(alertID: .mapError(.failedLoadingMap)) #expect(mapError.title == L10n.errorFailedLoadingMap(InfoPlistReader.main.bundleDisplayName)) @@ -80,7 +72,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func sendUserLocation() async throws { + mutating func sendUserLocation() async throws { setupViewModel() context.mapCenterLocation = .init(latitude: 0, longitude: 0) context.geolocationUncertainty = 10 @@ -102,7 +94,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func sendPickedLocation() async throws { + mutating func sendPickedLocation() async throws { setupViewModel() context.mapCenterLocation = .init(latitude: 0, longitude: 0) context.isLocationAuthorized = nil @@ -127,7 +119,7 @@ final class LocationSharingScreenViewModelTests { // MARK: - isLocationLoading Tests @Test - func isLocationLoadingInPickerModeWithAuthorizationNotDetermined() { + mutating func isLocationLoadingInPickerModeWithAuthorizationNotDetermined() { setupViewModel() context.isLocationAuthorized = nil context.hasLoadedUserLocation = false @@ -135,7 +127,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func isLocationLoadingInPickerModeWithAuthorizationGranted() { + mutating func isLocationLoadingInPickerModeWithAuthorizationGranted() { setupViewModel() context.isLocationAuthorized = true context.hasLoadedUserLocation = false @@ -143,7 +135,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func isLocationNotLoadingInPickerModeWhenLocationLoaded() { + mutating func isLocationNotLoadingInPickerModeWhenLocationLoaded() { setupViewModel() context.isLocationAuthorized = true context.hasLoadedUserLocation = true @@ -151,7 +143,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func isLocationNotLoadingInPickerModeWhenAuthorizationDenied() { + mutating func isLocationNotLoadingInPickerModeWhenAuthorizationDenied() { setupViewModel() context.isLocationAuthorized = false context.hasLoadedUserLocation = false @@ -159,7 +151,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func isLocationNotLoadingInNonPickerModeWithAuthorizationNotDetermined() { + mutating func isLocationNotLoadingInNonPickerModeWithAuthorizationNotDetermined() { let aliceShare = makeLiveLocationShare(userID: "@alice:matrix.org") let sender = TimelineItemSender(id: "@alice:matrix.org", displayName: "Alice") let liveLocationsSubject = CurrentValueSubject<[LiveLocationShare], Never>([aliceShare]) @@ -170,7 +162,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func isLocationLoadingInNonPickerModeWithAuthorizationGiven() { + mutating func isLocationLoadingInNonPickerModeWithAuthorizationGiven() { let aliceShare = makeLiveLocationShare(userID: "@alice:matrix.org") let sender = TimelineItemSender(id: "@alice:matrix.org", displayName: "Alice") let liveLocationsSubject = CurrentValueSubject<[LiveLocationShare], Never>([aliceShare]) @@ -183,21 +175,21 @@ final class LocationSharingScreenViewModelTests { // MARK: - Live Location Authorization Tests @Test - func startLiveLocationWithDeniedAuthorization() { + mutating func startLiveLocationWithDeniedAuthorization() { setupViewModel(liveLocationManagerConfiguration: .init(authorizationStatus: .denied)) context.send(viewAction: .startLiveLocation) #expect(context.alertInfo?.id == .missingAlwaysAuthorization) } @Test - func startLiveLocationWithRestrictedAuthorization() { + mutating func startLiveLocationWithRestrictedAuthorization() { setupViewModel(liveLocationManagerConfiguration: .init(authorizationStatus: .restricted)) context.send(viewAction: .startLiveLocation) #expect(context.alertInfo?.id == .missingAlwaysAuthorization) } @Test - func startLiveLocationWithWhenInUseAuthorizationAlreadyRequested() { + mutating func startLiveLocationWithWhenInUseAuthorizationAlreadyRequested() { setupViewModel(liveLocationManagerConfiguration: .init(authorizationStatus: .authorizedWhenInUse, requestAlwaysAuthorizationIfPossibleReturnValue: false)) context.send(viewAction: .startLiveLocation) @@ -205,7 +197,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func startLiveLocationWithWhenInUseAuthorizationNotYetRequested() { + mutating func startLiveLocationWithWhenInUseAuthorizationNotYetRequested() { setupViewModel(liveLocationManagerConfiguration: .init(authorizationStatus: .authorizedWhenInUse, requestAlwaysAuthorizationIfPossibleReturnValue: true)) context.send(viewAction: .startLiveLocation) @@ -214,7 +206,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func startLiveLocationWithNotDeterminedAuthorizationTransitionsToWhenInUse() async { + mutating func startLiveLocationWithNotDeterminedAuthorizationTransitionsToWhenInUse() async { let authorizationStatusSubject = CurrentValueSubject(.notDetermined) let liveLocationManagerMock = LiveLocationManagerMock() liveLocationManagerMock.underlyingAuthorizationStatus = .init(authorizationStatusSubject) @@ -243,14 +235,14 @@ final class LocationSharingScreenViewModelTests { // MARK: - Live Location Start Flow Tests @Test - func startLiveLocationShowsDisclaimer() { + mutating func startLiveLocationShowsDisclaimer() { setupViewModel(liveLocationManagerConfiguration: .init(authorizationStatus: .authorizedAlways)) context.send(viewAction: .startLiveLocation) #expect(context.alertInfo?.id == .liveLocationDisclaimer) } @Test - func startLiveLocationDisclaimerDeclineSkipsStart() { + mutating func startLiveLocationDisclaimerDeclineSkipsStart() { let liveLocationManagerMock = LiveLocationManagerMock(.init(authorizationStatus: .authorizedAlways)) setupViewModel(liveLocationManagerMock: liveLocationManagerMock) context.send(viewAction: .startLiveLocation) @@ -259,7 +251,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func startLiveLocationDisclaimerAcceptShowsDurationPicker() async throws { + mutating func startLiveLocationDisclaimerAcceptShowsDurationPicker() async throws { setupViewModel(liveLocationManagerConfiguration: .init(authorizationStatus: .authorizedAlways)) context.send(viewAction: .startLiveLocation) #expect(context.alertInfo?.id == .liveLocationDisclaimer) @@ -269,7 +261,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func startLiveLocationDurationPickerCancelSkipsStart() async throws { + mutating func startLiveLocationDurationPickerCancelSkipsStart() async throws { let liveLocationManagerMock = LiveLocationManagerMock(.init(authorizationStatus: .authorizedAlways)) setupViewModel(liveLocationManagerMock: liveLocationManagerMock) context.send(viewAction: .startLiveLocation) @@ -281,7 +273,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func startLiveLocationSuccess() async throws { + mutating func startLiveLocationSuccess() async throws { let liveLocationManagerMock = LiveLocationManagerMock(.init(authorizationStatus: .authorizedAlways)) setupViewModel(liveLocationManagerMock: liveLocationManagerMock) context.send(viewAction: .startLiveLocation) @@ -299,7 +291,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func startLiveLocationFailureDoesNotClose() async throws { + mutating func startLiveLocationFailureDoesNotClose() async throws { let liveLocationManagerMock = LiveLocationManagerMock(.init(authorizationStatus: .authorizedAlways)) liveLocationManagerMock.startLiveLocationRoomIDDurationReturnValue = .failure(.startFailed) setupViewModel(liveLocationManagerMock: liveLocationManagerMock) @@ -316,7 +308,7 @@ final class LocationSharingScreenViewModelTests { // MARK: - Live Location Share Update Tests @Test - func viewLiveInitialSenderShownCorrectly() { + mutating func viewLiveInitialSenderShownCorrectly() { let aliceShare = makeLiveLocationShare(userID: "@alice:matrix.org", latitude: 51.5, longitude: -0.1) let sender = TimelineItemSender(id: "@alice:matrix.org", displayName: "Alice") let liveLocationsSubject = CurrentValueSubject<[LiveLocationShare], Never>([aliceShare]) @@ -334,7 +326,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func viewLiveReceivesAdditionalLocationUpdates() async throws { + mutating func viewLiveReceivesAdditionalLocationUpdates() async throws { let aliceShare = makeLiveLocationShare(userID: "@alice:matrix.org", latitude: 51.5, longitude: -0.1) let sender = TimelineItemSender(id: "@alice:matrix.org", displayName: "Alice") let liveLocationsSubject = CurrentValueSubject<[LiveLocationShare], Never>([aliceShare]) @@ -358,7 +350,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func viewLiveProfilesResolvedFromRoomMembers() async throws { + mutating func viewLiveProfilesResolvedFromRoomMembers() async throws { let aliceShare = makeLiveLocationShare(userID: "@alice:matrix.org", latitude: 51.5, longitude: -0.1) let sender = TimelineItemSender(id: "@alice:matrix.org", displayName: "Alice") let liveLocationsSubject = CurrentValueSubject<[LiveLocationShare], Never>([aliceShare]) @@ -380,7 +372,7 @@ final class LocationSharingScreenViewModelTests { } @Test - func viewLiveFromBannerAwaitsFirstShareThenCentersOnIt() async throws { + mutating func viewLiveFromBannerAwaitsFirstShareThenCentersOnIt() async throws { // Simulates opening from the banner: no sender info and no initial share are available yet. // The VM should wait for the first live location update and then center on the first share, // which is assumed to belong to the own user. @@ -394,7 +386,6 @@ final class LocationSharingScreenViewModelTests { viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: nil, initialLiveLocationShare: nil), mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration, - liveLocationSharingEnabled: true, roomProxy: roomProxyMock, timelineController: MockTimelineController(timelineProxy: TimelineProxyMock(.init())), liveLocationManager: LiveLocationManagerMock(.init()), @@ -424,11 +415,10 @@ final class LocationSharingScreenViewModelTests { // MARK: - Private - private func setupViewModel(liveLocationManagerConfiguration: LiveLocationManagerMock.Configuration = .init()) { + private mutating func setupViewModel(liveLocationManagerConfiguration: LiveLocationManagerMock.Configuration = .init()) { timelineProxy = TimelineProxyMock(.init()) viewModel = LocationSharingScreenViewModel(interactionMode: .picker, mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration, - liveLocationSharingEnabled: true, roomProxy: JoinedRoomProxyMock(.init()), timelineController: MockTimelineController(timelineProxy: timelineProxy), liveLocationManager: LiveLocationManagerMock(liveLocationManagerConfiguration), @@ -438,11 +428,10 @@ final class LocationSharingScreenViewModelTests { viewModel.state.bindings.isLocationAuthorized = true } - private func setupViewModel(liveLocationManagerMock: LiveLocationManagerMock) { + private mutating func setupViewModel(liveLocationManagerMock: LiveLocationManagerMock) { timelineProxy = TimelineProxyMock(.init()) viewModel = LocationSharingScreenViewModel(interactionMode: .picker, mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration, - liveLocationSharingEnabled: true, roomProxy: JoinedRoomProxyMock(.init()), timelineController: MockTimelineController(timelineProxy: timelineProxy), liveLocationManager: liveLocationManagerMock, @@ -452,10 +441,10 @@ final class LocationSharingScreenViewModelTests { viewModel.state.bindings.isLocationAuthorized = true } - private func setupViewModelForViewLive(sender: TimelineItemSender, - initialShare: LiveLocationShare, - liveLocationsSubject: CurrentValueSubject<[LiveLocationShare], Never>, - members: [RoomMemberProxyMock] = .allMembers) { + private mutating func setupViewModelForViewLive(sender: TimelineItemSender, + initialShare: LiveLocationShare, + liveLocationsSubject: CurrentValueSubject<[LiveLocationShare], Never>, + members: [RoomMemberProxyMock] = .allMembers) { let liveLocationServiceMock = RoomLiveLocationServiceMock() liveLocationServiceMock.liveLocationsPublisher = liveLocationsSubject.asCurrentValuePublisher() @@ -464,7 +453,6 @@ final class LocationSharingScreenViewModelTests { viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: sender, initialLiveLocationShare: initialShare), mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration, - liveLocationSharingEnabled: true, roomProxy: roomProxyMock, timelineController: MockTimelineController(timelineProxy: TimelineProxyMock(.init())), liveLocationManager: LiveLocationManagerMock(.init()),