mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 19:11:45 +00:00
prevent replies to LLS timeline items, and live location sharing in threads
This commit is contained in:
@@ -705,7 +705,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
stateMachine.tryEvent(.presentEmojiPicker(itemID: itemID, selectedEmojis: selectedEmojis),
|
stateMachine.tryEvent(.presentEmojiPicker(itemID: itemID, selectedEmojis: selectedEmojis),
|
||||||
userInfo: EventUserInfo(animated: animated, timelineController: timelineController))
|
userInfo: EventUserInfo(animated: animated, timelineController: timelineController))
|
||||||
case .presentLocationPicker:
|
case .presentLocationPicker:
|
||||||
stateMachine.tryEvent(.presentMapNavigator(interactionMode: .picker),
|
stateMachine.tryEvent(.presentMapNavigator(interactionMode: .picker(shouldShowLiveLocationOption: true)),
|
||||||
userInfo: EventUserInfo(animated: animated, timelineController: timelineController))
|
userInfo: EventUserInfo(animated: animated, timelineController: timelineController))
|
||||||
case .presentPollForm(let mode):
|
case .presentPollForm(let mode):
|
||||||
stateMachine.tryEvent(.presentPollForm(mode: mode),
|
stateMachine.tryEvent(.presentPollForm(mode: mode),
|
||||||
@@ -814,7 +814,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
stateMachine.tryEvent(.presentMediaUploadPreview(mediaURLs: mediaURLs),
|
stateMachine.tryEvent(.presentMediaUploadPreview(mediaURLs: mediaURLs),
|
||||||
userInfo: EventUserInfo(animated: animated, timelineController: timelineController))
|
userInfo: EventUserInfo(animated: animated, timelineController: timelineController))
|
||||||
case .presentLocationPicker:
|
case .presentLocationPicker:
|
||||||
stateMachine.tryEvent(.presentMapNavigator(interactionMode: .picker),
|
stateMachine.tryEvent(.presentMapNavigator(interactionMode: .picker(shouldShowLiveLocationOption: false)),
|
||||||
userInfo: EventUserInfo(animated: animated, timelineController: timelineController))
|
userInfo: EventUserInfo(animated: animated, timelineController: timelineController))
|
||||||
case .presentLiveLocationViewer(let sender, let initialLiveLocationShare):
|
case .presentLiveLocationViewer(let sender, let initialLiveLocationShare):
|
||||||
stateMachine.tryEvent(.presentMapNavigator(interactionMode: .viewLive(sender: sender, initialLiveLocationShare: initialLiveLocationShare)),
|
stateMachine.tryEvent(.presentMapNavigator(interactionMode: .viewLive(sender: sender, initialLiveLocationShare: initialLiveLocationShare)),
|
||||||
@@ -1167,7 +1167,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
timelineController: TimelineControllerProtocol,
|
timelineController: TimelineControllerProtocol,
|
||||||
animated: Bool) {
|
animated: Bool) {
|
||||||
let stackCoordinator = NavigationStackCoordinator()
|
let stackCoordinator = NavigationStackCoordinator()
|
||||||
|
|
||||||
let params = LocationSharingScreenCoordinatorParameters(interactionMode: interactionMode,
|
let params = LocationSharingScreenCoordinatorParameters(interactionMode: interactionMode,
|
||||||
mapURLBuilder: flowParameters.appSettings.mapTilerConfiguration,
|
mapURLBuilder: flowParameters.appSettings.mapTilerConfiguration,
|
||||||
roomProxy: roomProxy,
|
roomProxy: roomProxy,
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ extension RoomFlowCoordinator {
|
|||||||
case (.mediaEventsTimeline, .presentMessageForwarding(forwardingItem: let forwardingItem)):
|
case (.mediaEventsTimeline, .presentMessageForwarding(forwardingItem: let forwardingItem)):
|
||||||
return .messageForwarding(forwardingItem: forwardingItem, previousState: fromState)
|
return .messageForwarding(forwardingItem: forwardingItem, previousState: fromState)
|
||||||
|
|
||||||
case (.room, .presentMapNavigator(_)):
|
case (.room, .presentMapNavigator):
|
||||||
return .mapNavigator(previousState: fromState)
|
return .mapNavigator(previousState: fromState)
|
||||||
|
|
||||||
case (.room, .presentPollForm):
|
case (.room, .presentPollForm):
|
||||||
@@ -271,7 +271,7 @@ extension RoomFlowCoordinator {
|
|||||||
case (.thread, .presentMessageForwarding(let forwardingItem)):
|
case (.thread, .presentMessageForwarding(let forwardingItem)):
|
||||||
return .messageForwarding(forwardingItem: forwardingItem, previousState: fromState)
|
return .messageForwarding(forwardingItem: forwardingItem, previousState: fromState)
|
||||||
|
|
||||||
case (.thread, .presentMapNavigator(_)):
|
case (.thread, .presentMapNavigator):
|
||||||
return .mapNavigator(previousState: fromState)
|
return .mapNavigator(previousState: fromState)
|
||||||
|
|
||||||
case (.thread, .presentPollForm):
|
case (.thread, .presentPollForm):
|
||||||
|
|||||||
@@ -25,9 +25,21 @@ enum LocationSharingScreenViewModelAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum LocationSharingInteractionMode: Hashable {
|
enum LocationSharingInteractionMode: Hashable {
|
||||||
case picker
|
case picker(shouldShowLiveLocationOption: Bool)
|
||||||
case viewStatic(StaticLocationData)
|
case viewStatic(StaticLocationData)
|
||||||
case viewLive(sender: TimelineItemSender?, initialLiveLocationShare: LiveLocationShare?)
|
case viewLive(sender: TimelineItemSender?, initialLiveLocationShare: LiveLocationShare?)
|
||||||
|
|
||||||
|
var isPicker: Bool {
|
||||||
|
if case .picker = self { return true }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var shouldShowLiveLocationOption: Bool {
|
||||||
|
if case .picker(let shouldShowLiveLocationOption) = self {
|
||||||
|
return shouldShowLiveLocationOption
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LocationSharingScreenViewState: BindableState {
|
struct LocationSharingScreenViewState: BindableState {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
|
|||||||
self.analytics = analytics
|
self.analytics = analytics
|
||||||
self.userIndicatorController = userIndicatorController
|
self.userIndicatorController = userIndicatorController
|
||||||
self.notificationCenter = notificationCenter
|
self.notificationCenter = notificationCenter
|
||||||
|
|
||||||
super.init(initialViewState: .init(interactionMode: interactionMode,
|
super.init(initialViewState: .init(interactionMode: interactionMode,
|
||||||
mapURLBuilder: mapURLBuilder,
|
mapURLBuilder: mapURLBuilder,
|
||||||
ownUserID: roomProxy.ownUserID),
|
ownUserID: roomProxy.ownUserID),
|
||||||
@@ -332,17 +332,20 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
|
|||||||
extension LocationSharingScreenViewModel {
|
extension LocationSharingScreenViewModel {
|
||||||
enum MockType {
|
enum MockType {
|
||||||
case picker
|
case picker
|
||||||
|
case pickerWithoutLiveLocationOption
|
||||||
case staticSenderLocation
|
case staticSenderLocation
|
||||||
case staticPinLocation
|
case staticPinLocation
|
||||||
case viewLive
|
case viewLive
|
||||||
case viewLiveEmpty
|
case viewLiveEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
static func mock(type: MockType,
|
static func mock(type: MockType,
|
||||||
senderID: String = "@dan:matrix.org") -> LocationSharingScreenViewModel {
|
senderID: String = "@dan:matrix.org") -> LocationSharingScreenViewModel {
|
||||||
let interactionMode: LocationSharingInteractionMode = switch type {
|
let interactionMode: LocationSharingInteractionMode = switch type {
|
||||||
case .picker:
|
case .picker:
|
||||||
.picker
|
.picker(shouldShowLiveLocationOption: true)
|
||||||
|
case .pickerWithoutLiveLocationOption:
|
||||||
|
.picker(shouldShowLiveLocationOption: false)
|
||||||
case .staticPinLocation:
|
case .staticPinLocation:
|
||||||
.viewStatic(.init(sender: .init(id: senderID),
|
.viewStatic(.init(sender: .init(id: senderID),
|
||||||
geoURI: .init(latitude: 41.9027835,
|
geoURI: .init(latitude: 41.9027835,
|
||||||
|
|||||||
@@ -11,7 +11,14 @@ import SwiftUI
|
|||||||
struct LocationPickerSheet: View {
|
struct LocationPickerSheet: View {
|
||||||
@Bindable var context: LocationSharingScreenViewModel.Context
|
@Bindable var context: LocationSharingScreenViewModel.Context
|
||||||
@State private var height: CGFloat = .zero
|
@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.interactionMode.shouldShowLiveLocationOption ? 0 : 28
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
Text(L10n.screenSharingLocationOptionSheetTitle)
|
Text(L10n.screenSharingLocationOptionSheetTitle)
|
||||||
@@ -34,12 +41,14 @@ struct LocationPickerSheet: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
if context.viewState.interactionMode.shouldShowLiveLocationOption {
|
||||||
context.send(viewAction: .startLiveLocation)
|
Button {
|
||||||
} label: {
|
context.send(viewAction: .startLiveLocation)
|
||||||
LocationPickerLabel(text: L10n.actionShareLiveLocation,
|
} label: {
|
||||||
icon: \.locationPinSolid,
|
LocationPickerLabel(text: L10n.actionShareLiveLocation,
|
||||||
iconColor: .compound.iconAccentPrimary)
|
icon: \.locationPinSolid,
|
||||||
|
iconColor: .compound.iconAccentPrimary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.readHeight($height)
|
.readHeight($height)
|
||||||
@@ -47,7 +56,7 @@ struct LocationPickerSheet: View {
|
|||||||
.presentationBackground(.compound.bgCanvasDefault)
|
.presentationBackground(.compound.bgCanvasDefault)
|
||||||
.presentationBackgroundInteraction(.enabled)
|
.presentationBackgroundInteraction(.enabled)
|
||||||
.presentationDragIndicator(.hidden)
|
.presentationDragIndicator(.hidden)
|
||||||
.presentationDetents([.height(height)])
|
.presentationDetents([.height(height + additionalHeight)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ struct LocationSharingScreen: View {
|
|||||||
private var mainContent: some View {
|
private var mainContent: some View {
|
||||||
mapView
|
mapView
|
||||||
.ignoresSafeArea(edges: .bottom)
|
.ignoresSafeArea(edges: .bottom)
|
||||||
.track(screen: context.viewState.interactionMode == .picker ? .LocationSend : .LocationView)
|
.track(screen: context.viewState.interactionMode.isPicker ? .LocationSend : .LocationView)
|
||||||
.navigationTitle(L10n.screenViewLocationTitle)
|
.navigationTitle(L10n.screenViewLocationTitle)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar { toolbar }
|
.toolbar { toolbar }
|
||||||
@@ -71,7 +71,7 @@ struct LocationSharingScreen: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var mapSafeAreaEdges: Edge.Set {
|
private var mapSafeAreaEdges: Edge.Set {
|
||||||
context.viewState.interactionMode == .picker ? .horizontal : [.horizontal, .bottom]
|
context.viewState.interactionMode.isPicker ? .horizontal : [.horizontal, .bottom]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ToolbarContentBuilder
|
@ToolbarContentBuilder
|
||||||
@@ -126,19 +126,26 @@ struct LocationSharingScreen: View {
|
|||||||
|
|
||||||
struct LocationSharingScreen_Previews: PreviewProvider, TestablePreview {
|
struct LocationSharingScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
static let viewModel = LocationSharingScreenViewModel.mock(type: .staticSenderLocation)
|
static let viewModel = LocationSharingScreenViewModel.mock(type: .staticSenderLocation)
|
||||||
|
|
||||||
|
static let withoutLiveSharingViewModel = LocationSharingScreenViewModel.mock(type: .pickerWithoutLiveLocationOption)
|
||||||
|
|
||||||
static let pinViewModel = LocationSharingScreenViewModel.mock(type: .staticPinLocation)
|
static let pinViewModel = LocationSharingScreenViewModel.mock(type: .staticPinLocation)
|
||||||
|
|
||||||
static let pickerViewModel = LocationSharingScreenViewModel.mock(type: .picker)
|
static let pickerViewModel = LocationSharingScreenViewModel.mock(type: .picker)
|
||||||
|
|
||||||
static let liveLocationViewModel = LocationSharingScreenViewModel.mock(type: .viewLive)
|
static let liveLocationViewModel = LocationSharingScreenViewModel.mock(type: .viewLive)
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ElementNavigationStack {
|
ElementNavigationStack {
|
||||||
LocationSharingScreen(context: pickerViewModel.context)
|
LocationSharingScreen(context: pickerViewModel.context)
|
||||||
}
|
}
|
||||||
.previewDisplayName("Picker")
|
.previewDisplayName("Picker")
|
||||||
|
|
||||||
|
ElementNavigationStack {
|
||||||
|
LocationSharingScreen(context: withoutLiveSharingViewModel.context)
|
||||||
|
}
|
||||||
|
.previewDisplayName("Picker without live location sharing")
|
||||||
|
|
||||||
ElementNavigationStack {
|
ElementNavigationStack {
|
||||||
LocationSharingScreen(context: viewModel.context)
|
LocationSharingScreen(context: viewModel.context)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -440,7 +440,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
timestamp: eventItemProxy.timestamp,
|
timestamp: eventItemProxy.timestamp,
|
||||||
isOutgoing: isOutgoing,
|
isOutgoing: isOutgoing,
|
||||||
isEditable: eventItemProxy.isEditable,
|
isEditable: eventItemProxy.isEditable,
|
||||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
// Workaround until https://github.com/matrix-org/matrix-rust-sdk/pull/6563 is merged.
|
||||||
|
canBeRepliedTo: false,
|
||||||
sender: eventItemProxy.sender,
|
sender: eventItemProxy.sender,
|
||||||
content: .init(from: liveLocationContent, timestamp: eventItemProxy.timestamp),
|
content: .init(from: liveLocationContent, timestamp: eventItemProxy.timestamp),
|
||||||
properties: .init(replyDetails: buildTimelineItemReplyDetails(messageLikeContent.inReplyTo),
|
properties: .init(replyDetails: buildTimelineItemReplyDetails(messageLikeContent.inReplyTo),
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:64737509400fe27cdb017b8f66cffe8c0807f958827cff84a9ce5484db0ca3fc
|
||||||
|
size 80109
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:64737509400fe27cdb017b8f66cffe8c0807f958827cff84a9ce5484db0ca3fc
|
||||||
|
size 80109
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:08e72ae0c7d34108690371b29952267b1ac06bb62f203515f3c5e4143efd2ca8
|
||||||
|
size 38511
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:08e72ae0c7d34108690371b29952267b1ac06bb62f203515f3c5e4143efd2ca8
|
||||||
|
size 38511
|
||||||
@@ -402,7 +402,7 @@ struct LocationSharingScreenViewModelTests {
|
|||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
userIndicatorController: UserIndicatorControllerMock(),
|
userIndicatorController: UserIndicatorControllerMock(),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||||
|
|
||||||
// Initially no annotations and no map center since sender and share are both nil.
|
// Initially no annotations and no map center since sender and share are both nil.
|
||||||
#expect(context.viewState.annotations.isEmpty)
|
#expect(context.viewState.annotations.isEmpty)
|
||||||
#expect(context.mapCenterLocation == nil)
|
#expect(context.mapCenterLocation == nil)
|
||||||
@@ -428,7 +428,7 @@ struct LocationSharingScreenViewModelTests {
|
|||||||
private mutating func setupViewModel(liveLocationManagerConfiguration: LiveLocationManagerMock.Configuration = .init(),
|
private mutating func setupViewModel(liveLocationManagerConfiguration: LiveLocationManagerMock.Configuration = .init(),
|
||||||
members: [RoomMemberProxyMock] = .allMembersAsAdmin) {
|
members: [RoomMemberProxyMock] = .allMembersAsAdmin) {
|
||||||
timelineProxy = TimelineProxyMock(.init())
|
timelineProxy = TimelineProxyMock(.init())
|
||||||
viewModel = LocationSharingScreenViewModel(interactionMode: .picker,
|
viewModel = LocationSharingScreenViewModel(interactionMode: .picker(shouldShowLiveLocationOption: true),
|
||||||
mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration,
|
mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration,
|
||||||
roomProxy: JoinedRoomProxyMock(.init(members: members)),
|
roomProxy: JoinedRoomProxyMock(.init(members: members)),
|
||||||
timelineController: MockTimelineController(timelineProxy: timelineProxy),
|
timelineController: MockTimelineController(timelineProxy: timelineProxy),
|
||||||
@@ -438,11 +438,11 @@ struct LocationSharingScreenViewModelTests {
|
|||||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||||
viewModel.state.bindings.isLocationAuthorized = true
|
viewModel.state.bindings.isLocationAuthorized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private mutating func setupViewModel(liveLocationManagerMock: LiveLocationManagerMock,
|
private mutating func setupViewModel(liveLocationManagerMock: LiveLocationManagerMock,
|
||||||
members: [RoomMemberProxyMock] = .allMembersAsAdmin) {
|
members: [RoomMemberProxyMock] = .allMembersAsAdmin) {
|
||||||
timelineProxy = TimelineProxyMock(.init())
|
timelineProxy = TimelineProxyMock(.init())
|
||||||
viewModel = LocationSharingScreenViewModel(interactionMode: .picker,
|
viewModel = LocationSharingScreenViewModel(interactionMode: .picker(shouldShowLiveLocationOption: true),
|
||||||
mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration,
|
mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration,
|
||||||
roomProxy: JoinedRoomProxyMock(.init(members: members)),
|
roomProxy: JoinedRoomProxyMock(.init(members: members)),
|
||||||
timelineController: MockTimelineController(timelineProxy: timelineProxy),
|
timelineController: MockTimelineController(timelineProxy: timelineProxy),
|
||||||
|
|||||||
Reference in New Issue
Block a user