diff --git a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift index 39fba9d3a..c15edc1c2 100644 --- a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift +++ b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift @@ -339,6 +339,10 @@ extension AccessibilityTests { try await performAccessibilityAudit(named: "LocationSharingScreen_Previews") } + func testLocationSharingSettingsScreen() async throws { + try await performAccessibilityAudit(named: "LocationSharingSettingsScreen_Previews") + } + func testLoginScreen() async throws { try await performAccessibilityAudit(named: "LoginScreen_Previews") } diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 101def992..3692f3eed 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -112,6 +112,7 @@ 0EBF0A411E1C46D4BFA2B087 /* ManageAuthorizedSpacesScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB9CD3A0A39BC063438E12D4 /* ManageAuthorizedSpacesScreenCoordinator.swift */; }; 0EEC614342F823E5BF966C2C /* AppLockTimerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */; }; 0F6C8033FA60CFD36F7CA205 /* AppLockSetupPINScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A019A12C866D64CF072024B9 /* AppLockSetupPINScreenViewModel.swift */; }; + 0F7F0EFD2E39D8ECF2333498 /* LocationSharingSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE649BC92D304DE6A3056B69 /* LocationSharingSettingsScreenCoordinator.swift */; }; 0F81DEE3E02A6549B20DF09A /* DeactivateAccountScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 633924B26ACCD29C18BEF4E8 /* DeactivateAccountScreenViewModelProtocol.swift */; }; 0FA03F5A33C0857231B32B44 /* ReportRoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1FDB0B87D925AE830E32621 /* ReportRoomScreenViewModel.swift */; }; 101C8BFCAE35F43F37A6D8ED /* NotificationToneManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18678D0A4177D8F0A255BC36 /* NotificationToneManagerMock.swift */; }; @@ -1169,6 +1170,7 @@ B5321A1F5B26A0F3EC54909E /* CollapsibleFlowLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC5F5209279A752D98AAC4B2 /* CollapsibleFlowLayoutTests.swift */; }; B5479997ECC516C121E6625E /* LocationMarkerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFECCE59967018204876D0A5 /* LocationMarkerView.swift */; }; B5899F18AD6C56CE08FE532B /* RoomSummaryProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC83F47D2173B7538AA72E0E /* RoomSummaryProviderMock.swift */; }; + B59C96D3299DB60A05BD8B98 /* LocationSharingSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0656D66E6E18B90EAB79CB /* LocationSharingSettingsScreenViewModel.swift */; }; B5B68C7511DE1E36A89D353A /* SpaceAddRoomsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4338BB055A26094673CDC220 /* SpaceAddRoomsScreenViewModel.swift */; }; B5BCE012F9E7C45D1C76108E /* RoomMembersListScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2520C4F33AA0C061D209C28 /* RoomMembersListScreenTests.swift */; }; B5C40DCFFDFBA0F86E228602 /* Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = AFAD718C6621B94813B960B4 /* Dynamic */; }; @@ -1414,6 +1416,7 @@ DBC3FDE1540B39702A117D8E /* RoomRolesAndPermissionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DDC82DB6A84E700CD5DEC0 /* RoomRolesAndPermissionsTests.swift */; }; DBC8D1DBFE9F9CA7662BC8AA /* RoomPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 974AEAF3FE0C577A6C04AD6E /* RoomPermissions.swift */; }; DC08ADC41E792086A340A8B3 /* AccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */; }; + DC0A7CA37143070F38C190FB /* LocationSharingSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 822287D417AE41E9ADCA3919 /* LocationSharingSettingsScreenViewModelProtocol.swift */; }; DC68E866D6E664B0D2B06E74 /* MockImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1DA29A5A041CC0BACA7CB0 /* MockImageCache.swift */; }; DC77E9DB2CFBE84A2BDF20C5 /* RoomRolesAndPermissionsFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0833F51229E166BCA141D004 /* RoomRolesAndPermissionsFlowCoordinator.swift */; }; DCFE7CB3B9A104330BBB96AD /* AnalyticsPromptScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B67DF223EEB8DCAF178A1D4 /* AnalyticsPromptScreenCoordinator.swift */; }; @@ -1556,6 +1559,7 @@ F103924DED414ADFE398CE99 /* RoomPollsHistoryScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A130A2251A15A7AACC84FD37 /* RoomPollsHistoryScreenViewModelProtocol.swift */; }; F118DD449066E594F63C697D /* RoomMemberProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5E17028C02DFA7DDA3931 /* RoomMemberProxyProtocol.swift */; }; F12F6BED7B6D7EE4BEE55039 /* PlainMentionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE78FA0011E07920AE83135 /* PlainMentionBuilder.swift */; }; + F138A807F969044AB0E41AFD /* LocationSharingSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F59B8EEE7A060438C96BB77 /* LocationSharingSettingsScreen.swift */; }; F16EED85E30B83878FBC6629 /* JoinedMembersBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F320003F490B11F808ECC5E9 /* JoinedMembersBadgeView.swift */; }; F18CA61A58C77C84F551B8E7 /* GeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57916A1578D8043BB0795441 /* GeneratedMocks.swift */; }; F1C68F64FC8A66B6B9510BF7 /* ManageRoomMemberSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B30CD19ED6243FEDFBA8400 /* ManageRoomMemberSheetView.swift */; }; @@ -1644,6 +1648,7 @@ FEF0E0D2EDB8A4A9FE1842EE /* portrait_test_video.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = D48D415C7A4284DEBEB34924 /* portrait_test_video.mp4 */; }; FEFD5290B31FCBA6999912C8 /* RoomChangePermissionsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2721D7B051F0159AA919DA05 /* RoomChangePermissionsScreenViewModelProtocol.swift */; }; FF34BF2AF731340AF9414A18 /* SwipeRightAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4552D3466B1453F287223ADA /* SwipeRightAction.swift */; }; + FF6860DBC000AB5989509126 /* LocationSharingSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6736C29A8A9451E6664C027A /* LocationSharingSettingsScreenModels.swift */; }; FF7E8ECC8E7E1D1851517536 /* PollFormScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */; }; FF9C06BBF6AC6F1CFFBEBFFC /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 90791B9C739C716A40E1B230 /* target.yml */; }; FFD52DCDA6962055A363CC8F /* IdentityResetHandleSDKMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFB1D29B0870AFB6A56E9B8 /* IdentityResetHandleSDKMock.swift */; }; @@ -1964,6 +1969,7 @@ 1B9D191A81FFB0C72CE73E77 /* RoomSelectionScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSelectionScreenModels.swift; sourceTree = ""; }; 1BA5A62DA4B543827FF82354 /* LAContextMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LAContextMock.swift; sourceTree = ""; }; 1BEE119165C106FA83F253EF /* AnalyticsServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsServiceMock.swift; sourceTree = ""; }; + 1C0656D66E6E18B90EAB79CB /* LocationSharingSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingSettingsScreenViewModel.swift; sourceTree = ""; }; 1C21A715237F2B6D6E80998C /* SecureBackupControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupControllerProtocol.swift; sourceTree = ""; }; 1C25B6EBEB414431187D73B7 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = ""; }; 1C4724D5CB16CFA5853EF8F9 /* SearchScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchScreenViewModel.swift; sourceTree = ""; }; @@ -2274,6 +2280,7 @@ 4E600B315B920B9687F8EE1B /* ComposerDraftServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerDraftServiceMock.swift; sourceTree = ""; }; 4E625B0EB2F86B37C14EF7E6 /* SettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenViewModel.swift; sourceTree = ""; }; 4E7F7A975514E850A834B29F /* PaginationIndicatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIndicatorRoomTimelineView.swift; sourceTree = ""; }; + 4F59B8EEE7A060438C96BB77 /* LocationSharingSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingSettingsScreen.swift; sourceTree = ""; }; 4F5F0662483ED69791D63B16 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = et; path = et.lproj/Localizable.stringsdict; sourceTree = ""; }; 4F75EF13F49DD2204E760910 /* FileRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineView.swift; sourceTree = ""; }; 4FA29BAE9B0F2D90E57B261C /* UserSessionFlowCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionFlowCoordinatorTests.swift; sourceTree = ""; }; @@ -2416,6 +2423,7 @@ 66F91544AC136BF6477BDAB8 /* TimelineDeliveryStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineDeliveryStatusView.swift; sourceTree = ""; }; 671C338B7259DC5774816885 /* AuthenticationServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceTests.swift; sourceTree = ""; }; 6722709BD6178E10B70C9641 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/SAS.strings; sourceTree = ""; }; + 6736C29A8A9451E6664C027A /* LocationSharingSettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingSettingsScreenModels.swift; sourceTree = ""; }; 67638D34FB6A9110BE481A6F /* DeactivateAccountScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreenCoordinator.swift; sourceTree = ""; }; 68010886142843705E342645 /* ProgressMaskModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressMaskModifier.swift; sourceTree = ""; }; 682BC7BAF0EFEF512A8C5140 /* AuthenticationStartScreenBackgroundImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenBackgroundImage.swift; sourceTree = ""; }; @@ -2567,6 +2575,7 @@ 81B17DB1BC3B0C62AF84D230 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 81F0325E252B057FAEEE1B2D /* TypingIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicatorView.swift; sourceTree = ""; }; 8201C249AE5023E44267A3A1 /* ActiveCallTimelineItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveCallTimelineItemView.swift; sourceTree = ""; }; + 822287D417AE41E9ADCA3919 /* LocationSharingSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingSettingsScreenViewModelProtocol.swift; sourceTree = ""; }; 822B5573BDF52CF220BF9A17 /* LeaveSpaceModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaveSpaceModels.swift; sourceTree = ""; }; 8296D6FB451E25CEC0767BBA /* RoomNotificationSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenCoordinator.swift; sourceTree = ""; }; 829D3CE4F6C4DF6CC02D33C3 /* TimelineItemProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemProviderProtocol.swift; sourceTree = ""; }; @@ -2939,6 +2948,7 @@ BDBB8E5436E1BE0D6814BB7D /* PlaybackSpeedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackSpeedButton.swift; sourceTree = ""; }; BDE3EDEA7E64D68FEB828F83 /* SpaceSettingsFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceSettingsFlowCoordinator.swift; sourceTree = ""; }; BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationContent.swift; sourceTree = ""; }; + BE649BC92D304DE6A3056B69 /* LocationSharingSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingSettingsScreenCoordinator.swift; sourceTree = ""; }; BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemReplyDetails.swift; sourceTree = ""; }; BE9BBB18FB27F09032AD8769 /* NotificationPermissionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenViewModel.swift; sourceTree = ""; }; BEA38B9851CFCC4D67F5587D /* EmojiPickerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenCoordinator.swift; sourceTree = ""; }; @@ -3794,6 +3804,14 @@ path = PreviewTests; sourceTree = ""; }; + 1ABCE9292F4FC70A52785BCF /* View */ = { + isa = PBXGroup; + children = ( + 4F59B8EEE7A060438C96BB77 /* LocationSharingSettingsScreen.swift */, + ); + path = View; + sourceTree = ""; + }; 1C62F5382CC9D9F7DCEC344A /* UserDiscoveryService */ = { isa = PBXGroup; children = ( @@ -5129,6 +5147,18 @@ path = SupportingFiles; sourceTree = ""; }; + 6512A63354FC9D78FD329DF5 /* LocationSharingSettingsScreen */ = { + isa = PBXGroup; + children = ( + BE649BC92D304DE6A3056B69 /* LocationSharingSettingsScreenCoordinator.swift */, + 6736C29A8A9451E6664C027A /* LocationSharingSettingsScreenModels.swift */, + 1C0656D66E6E18B90EAB79CB /* LocationSharingSettingsScreenViewModel.swift */, + 822287D417AE41E9ADCA3919 /* LocationSharingSettingsScreenViewModelProtocol.swift */, + 1ABCE9292F4FC70A52785BCF /* View */, + ); + path = LocationSharingSettingsScreen; + sourceTree = ""; + }; 6709362D60732DED2069AE0F /* MediaPlayer */ = { isa = PBXGroup; children = ( @@ -5275,6 +5305,7 @@ CBB8EC37B802F3DD74A0205E /* DeactivateAccountScreen */, 1CA6CD0DE6F0445156361B6D /* DeveloperOptionsScreen */, 38A1C74493B816B8753F5BC2 /* LegalInformationScreen */, + 6512A63354FC9D78FD329DF5 /* LocationSharingSettingsScreen */, B92D540BF25A95D647C271BA /* MediaUploadQualitySettingsScreen */, 91D3BE26C39545233959CDCE /* ModerationAndSafetySettingsScreen */, 441115752CA2408F26A72D11 /* NotificationSettingsEditScreen */, @@ -9175,6 +9206,11 @@ 29491EE7AE37E239E839C5A3 /* LocationSharingScreenModels.swift in Sources */, 05B02B9508E922677F0E9451 /* LocationSharingScreenViewModel.swift in Sources */, 358290C2E4574456FE0C2DF6 /* LocationSharingScreenViewModelProtocol.swift in Sources */, + F138A807F969044AB0E41AFD /* LocationSharingSettingsScreen.swift in Sources */, + 0F7F0EFD2E39D8ECF2333498 /* LocationSharingSettingsScreenCoordinator.swift in Sources */, + FF6860DBC000AB5989509126 /* LocationSharingSettingsScreenModels.swift in Sources */, + B59C96D3299DB60A05BD8B98 /* LocationSharingSettingsScreenViewModel.swift in Sources */, + DC0A7CA37143070F38C190FB /* LocationSharingSettingsScreenViewModelProtocol.swift in Sources */, 5EDBDE802761B5ECB54E6787 /* LogLevel.swift in Sources */, 36DE961B784087D5E18EF9BA /* LogViewerScreen.swift in Sources */, CF38B70D8C6DD42C00A56A27 /* LogViewerScreenCoordinator.swift in Sources */, diff --git a/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift b/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift index d1f779ee2..3b97007fb 100644 --- a/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift +++ b/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift @@ -90,6 +90,7 @@ enum TestablePreviewsDictionary { "LocationPickerSheet_Previews" : LocationPickerSheet_Previews.self, "LocationRoomTimelineView_Previews" : LocationRoomTimelineView_Previews.self, "LocationSharingScreen_Previews" : LocationSharingScreen_Previews.self, + "LocationSharingSettingsScreen_Previews" : LocationSharingSettingsScreen_Previews.self, "LongPressWithFeedback_Previews" : LongPressWithFeedback_Previews.self, "ManageAuthorizedSpacesScreen_Previews" : ManageAuthorizedSpacesScreen_Previews.self, "ManageRoomMemberSheetView_Previews" : ManageRoomMemberSheetView_Previews.self, diff --git a/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/LocationSharingSettingsScreenCoordinator.swift b/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/LocationSharingSettingsScreenCoordinator.swift new file mode 100644 index 000000000..f0295b3c4 --- /dev/null +++ b/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/LocationSharingSettingsScreenCoordinator.swift @@ -0,0 +1,25 @@ +// +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. +// + +import Combine +import SwiftUI + +struct LocationSharingSettingsScreenCoordinatorParameters { + let userSettings: UserSettings +} + +final class LocationSharingSettingsScreenCoordinator: CoordinatorProtocol { + private var viewModel: LocationSharingSettingsScreenViewModelProtocol + + init(parameters: LocationSharingSettingsScreenCoordinatorParameters) { + viewModel = LocationSharingSettingsScreenViewModel(userSettings: parameters.userSettings) + } + + func toPresentable() -> AnyView { + AnyView(LocationSharingSettingsScreen(context: viewModel.context)) + } +} diff --git a/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/LocationSharingSettingsScreenModels.swift b/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/LocationSharingSettingsScreenModels.swift new file mode 100644 index 000000000..974df622e --- /dev/null +++ b/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/LocationSharingSettingsScreenModels.swift @@ -0,0 +1,41 @@ +// +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. +// + +import Foundation +import UIKit + +struct LocationSharingSettingsScreenViewState: BindableState { + init(bindings: LocationSharingSettingsScreenViewStateBindings) { + self.bindings = bindings + + let linkPlaceholder = "{link}" + var footerString = AttributedString(L10n.screenAdvancedSettingsLiveLocationSectionFooter(linkPlaceholder)) + var linkString = AttributedString(L10n.screenAdvancedSettingsLiveLocationSectionFooterLink) + linkString.link = URL(string: UIApplication.openSettingsURLString) + linkString.bold() + footerString.replace(linkPlaceholder, with: linkString) + liveLocationUpdateFooterAttributedString = footerString + } + + let liveLocationUpdateFooterAttributedString: AttributedString + var bindings: LocationSharingSettingsScreenViewStateBindings +} + +struct LocationSharingSettingsScreenViewStateBindings { + private let userSettings: UserSettings + + init(userSettings: UserSettings) { + self.userSettings = userSettings + } + + var liveLocationMinimumDistanceUpdate: Int { + get { userSettings.liveLocationMinimumDistanceUpdate } + set { userSettings.liveLocationMinimumDistanceUpdate = newValue } + } +} + +enum LocationSharingSettingsScreenViewAction { } diff --git a/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/LocationSharingSettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/LocationSharingSettingsScreenViewModel.swift new file mode 100644 index 000000000..d6247cc5b --- /dev/null +++ b/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/LocationSharingSettingsScreenViewModel.swift @@ -0,0 +1,20 @@ +// +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. +// + +import Combine +import SwiftUI + +typealias LocationSharingSettingsScreenViewModelType = StateStoreViewModelV2 + +class LocationSharingSettingsScreenViewModel: LocationSharingSettingsScreenViewModelType, LocationSharingSettingsScreenViewModelProtocol { + init(userSettings: UserSettings) { + let state = LocationSharingSettingsScreenViewState(bindings: .init(userSettings: userSettings)) + super.init(initialViewState: state) + } + + override func process(viewAction: LocationSharingSettingsScreenViewAction) { } +} diff --git a/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/LocationSharingSettingsScreenViewModelProtocol.swift b/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/LocationSharingSettingsScreenViewModelProtocol.swift new file mode 100644 index 000000000..448dd69d3 --- /dev/null +++ b/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/LocationSharingSettingsScreenViewModelProtocol.swift @@ -0,0 +1,12 @@ +// +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. +// + +import Combine + +protocol LocationSharingSettingsScreenViewModelProtocol { + var context: LocationSharingSettingsScreenViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/View/LocationSharingSettingsScreen.swift b/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/View/LocationSharingSettingsScreen.swift new file mode 100644 index 000000000..8680f6d76 --- /dev/null +++ b/ElementX/Sources/Screens/Settings/LocationSharingSettingsScreen/View/LocationSharingSettingsScreen.swift @@ -0,0 +1,99 @@ +// +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. +// + +import Compound +import SwiftUI + +struct LocationSharingSettingsScreen: View { + static let measurementFormatter = { + let formatter = MeasurementFormatter() + formatter.unitOptions = .providedUnit + formatter.unitStyle = .short + return formatter + }() + + @Bindable var context: LocationSharingSettingsScreenViewModel.Context + + var body: some View { + Form { + liveLocationSection + } + .compoundList() + .navigationTitle(L10n.commonLocationSharing) + .navigationBarTitleDisplayMode(.inline) + } + + @ViewBuilder + private var liveLocationSection: some View { + let binding = Binding(get: { + Double(context.liveLocationMinimumDistanceUpdate) + }, set: { newValue in + context.liveLocationMinimumDistanceUpdate = Int(newValue) + }) + + Section { + ListRow(kind: .custom { + VStack(alignment: .leading, spacing: 0) { + Text(L10n.screenAdvancedSettingsLiveLocationUpdateDistance(context.liveLocationMinimumDistanceUpdate)) + .font(.compound.bodyLG) + .foregroundStyle(.compound.textPrimary) + // The internal hidden label of the slider will read voice over + .accessibilityHidden(true) + Slider(value: binding, in: 1...100) { + Text(L10n.screenAdvancedSettingsLiveLocationUpdateDistance(context.liveLocationMinimumDistanceUpdate)) + } minimumValueLabel: { + Text(Self.measurementFormatter.string(from: .init(value: 1, + unit: UnitLength.meters))) + .font(.compound.bodyLG) + .foregroundStyle(.compound.textSecondary) + .padding(.trailing, 15) + } maximumValueLabel: { + Text(Self.measurementFormatter.string(from: .init(value: 100, + unit: UnitLength.meters))) + .font(.compound.bodyLG) + .foregroundStyle(.compound.textSecondary) + .padding(.leading, 15) + } + .tint(.compound.iconAccentPrimary) + } + .padding(.horizontal, ListRowPadding.horizontal) + .padding(.vertical, ListRowPadding.vertical) + }) + } header: { + VStack(alignment: .leading, spacing: 4) { + Text(L10n.screenAdvancedSettingsLiveLocationSectionTitle) + .compoundListSectionHeader() + Text(L10n.screenAdvancedSettingsLiveLocationSectionDescription) + .font(.compound.bodyMD) + .foregroundStyle(.compound.textSecondary) + } + } footer: { + Text(context.viewState.liveLocationUpdateFooterAttributedString) + .compoundListSectionFooter() + } + } +} + +// MARK: - Previews + +struct LocationSharingSettingsScreen_Previews: PreviewProvider, TestablePreview { + static let viewModel = LocationSharingSettingsScreenViewModel(userSettings: .mock()) + + static var previews: some View { + ElementNavigationStack { + LocationSharingSettingsScreen(context: viewModel.context) + } + } +} + +private extension TimelineMediaVisibility { + static var items: [(title: String, tag: TimelineMediaVisibility)] { + [(title: L10n.screenAdvancedSettingsShowMediaTimelineAlwaysHide, tag: .never), + (title: L10n.screenAdvancedSettingsShowMediaTimelinePrivateRooms, tag: .privateOnly), + (title: L10n.screenAdvancedSettingsShowMediaTimelineAlwaysShow, tag: .always)] + } +} diff --git a/PreviewTests/Sources/GeneratedPreviewTests.swift b/PreviewTests/Sources/GeneratedPreviewTests.swift index 64cba3601..edcf34a8c 100644 --- a/PreviewTests/Sources/GeneratedPreviewTests.swift +++ b/PreviewTests/Sources/GeneratedPreviewTests.swift @@ -592,6 +592,13 @@ extension PreviewTests { } } + @Test + func locationSharingSettingsScreen() async throws { + for (index, preview) in LocationSharingSettingsScreen_Previews._allPreviews.enumerated() { + try await assertSnapshots(matching: preview, step: index) + } + } + @Test func loginScreen() async throws { for (index, preview) in LoginScreen_Previews._allPreviews.enumerated() { diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingSettingsScreen.iPad-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingSettingsScreen.iPad-en-GB-0.png new file mode 100644 index 000000000..b97c5b75e --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingSettingsScreen.iPad-en-GB-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8059481773e73df87199bf2d0bebab1076bc3d32b12be037571922fd44f0e9e8 +size 120424 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingSettingsScreen.iPad-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingSettingsScreen.iPad-pseudo-0.png new file mode 100644 index 000000000..d29a8c369 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingSettingsScreen.iPad-pseudo-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbca63734f3024891b5fb92ea81e561cb57f8c097498b29a771683b13498b5d2 +size 136468 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingSettingsScreen.iPhone-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingSettingsScreen.iPhone-en-GB-0.png new file mode 100644 index 000000000..e5fef77f4 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingSettingsScreen.iPhone-en-GB-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:010055c8dbb7cf9b85e477b5e2d11123fefc5ddfff0a7d3f1d591d4315175326 +size 73100 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingSettingsScreen.iPhone-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingSettingsScreen.iPhone-pseudo-0.png new file mode 100644 index 000000000..3ff7179e1 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingSettingsScreen.iPhone-pseudo-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba4de989567368e38f2978afc2da6aeb3b6d3f0c03c2a8ddb5a2d3ed3e1c7836 +size 96361