mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
Disable the composer when you don't have the power to post. (#4076)
This commit is contained in:
@@ -8593,6 +8593,76 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
//MARK: - canUser
|
//MARK: - canUser
|
||||||
|
|
||||||
|
var canUserUserIDSendMessageUnderlyingCallsCount = 0
|
||||||
|
var canUserUserIDSendMessageCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return canUserUserIDSendMessageUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = canUserUserIDSendMessageUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
canUserUserIDSendMessageUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
canUserUserIDSendMessageUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var canUserUserIDSendMessageCalled: Bool {
|
||||||
|
return canUserUserIDSendMessageCallsCount > 0
|
||||||
|
}
|
||||||
|
var canUserUserIDSendMessageReceivedArguments: (userID: String, messageType: MessageLikeEventType)?
|
||||||
|
var canUserUserIDSendMessageReceivedInvocations: [(userID: String, messageType: MessageLikeEventType)] = []
|
||||||
|
|
||||||
|
var canUserUserIDSendMessageUnderlyingReturnValue: Result<Bool, RoomProxyError>!
|
||||||
|
var canUserUserIDSendMessageReturnValue: Result<Bool, RoomProxyError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return canUserUserIDSendMessageUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<Bool, RoomProxyError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = canUserUserIDSendMessageUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
canUserUserIDSendMessageUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
canUserUserIDSendMessageUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var canUserUserIDSendMessageClosure: ((String, MessageLikeEventType) async -> Result<Bool, RoomProxyError>)?
|
||||||
|
|
||||||
|
func canUser(userID: String, sendMessage messageType: MessageLikeEventType) async -> Result<Bool, RoomProxyError> {
|
||||||
|
canUserUserIDSendMessageCallsCount += 1
|
||||||
|
canUserUserIDSendMessageReceivedArguments = (userID: userID, messageType: messageType)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.canUserUserIDSendMessageReceivedInvocations.append((userID: userID, messageType: messageType))
|
||||||
|
}
|
||||||
|
if let canUserUserIDSendMessageClosure = canUserUserIDSendMessageClosure {
|
||||||
|
return await canUserUserIDSendMessageClosure(userID, messageType)
|
||||||
|
} else {
|
||||||
|
return canUserUserIDSendMessageReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//MARK: - canUser
|
||||||
|
|
||||||
var canUserUserIDSendStateEventUnderlyingCallsCount = 0
|
var canUserUserIDSendStateEventUnderlyingCallsCount = 0
|
||||||
var canUserUserIDSendStateEventCallsCount: Int {
|
var canUserUserIDSendStateEventCallsCount: Int {
|
||||||
get {
|
get {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ struct JoinedRoomProxyMockConfiguration {
|
|||||||
var ownUserID = RoomMemberProxyMock.mockMe.userID
|
var ownUserID = RoomMemberProxyMock.mockMe.userID
|
||||||
var inviter: RoomMemberProxyProtocol?
|
var inviter: RoomMemberProxyProtocol?
|
||||||
|
|
||||||
|
var canUserSendMessage = true
|
||||||
var canUserInvite = true
|
var canUserInvite = true
|
||||||
var canUserTriggerRoomNotification = false
|
var canUserTriggerRoomNotification = false
|
||||||
var canUserJoinCall = true
|
var canUserJoinCall = true
|
||||||
@@ -91,6 +92,7 @@ extension JoinedRoomProxyMock {
|
|||||||
return .success(member.role)
|
return .success(member.role)
|
||||||
}
|
}
|
||||||
updatePowerLevelsForUsersReturnValue = .success(())
|
updatePowerLevelsForUsersReturnValue = .success(())
|
||||||
|
canUserUserIDSendMessageReturnValue = .success(configuration.canUserSendMessage)
|
||||||
canUserUserIDSendStateEventClosure = { [weak self] userID, _ in
|
canUserUserIDSendStateEventClosure = { [weak self] userID, _ in
|
||||||
.success(self?.membersPublisher.value.first { $0.userID == userID }?.role ?? .user != .user)
|
.success(self?.membersPublisher.value.first { $0.userID == userID }?.role ?? .user != .user)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ struct RoomScreenViewState: BindableState {
|
|||||||
!pinnedEventsBannerState.isEmpty && lastScrollDirection != .top
|
!pinnedEventsBannerState.isEmpty && lastScrollDirection != .top
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var canSendMessage = true
|
||||||
var canJoinCall = false
|
var canJoinCall = false
|
||||||
var hasOngoingCall: Bool
|
var hasOngoingCall: Bool
|
||||||
var shouldShowCallButton = true
|
var shouldShowCallButton = true
|
||||||
|
|||||||
@@ -340,6 +340,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ownUserID = roomProxy.ownUserID
|
let ownUserID = roomProxy.ownUserID
|
||||||
|
state.canSendMessage = await (try? roomProxy.canUser(userID: ownUserID, sendMessage: .roomMessage).get()) == true
|
||||||
state.canJoinCall = await (try? roomProxy.canUserJoinCall(userID: ownUserID).get()) == true
|
state.canJoinCall = await (try? roomProxy.canUserJoinCall(userID: ownUserID).get()) == true
|
||||||
state.canAcceptKnocks = await (try? roomProxy.canUserInvite(userID: ownUserID).get()) == true
|
state.canAcceptKnocks = await (try? roomProxy.canUserInvite(userID: ownUserID).get()) == true
|
||||||
state.canDeclineKnocks = await (try? roomProxy.canUserKick(userID: ownUserID).get()) == true
|
state.canDeclineKnocks = await (try? roomProxy.canUserKick(userID: ownUserID).get()) == true
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ struct RoomScreen: View {
|
|||||||
roomContext.send(viewAction: .footerViewAction(action))
|
roomContext.send(viewAction: .footerViewAction(action))
|
||||||
}
|
}
|
||||||
|
|
||||||
composerToolbar
|
composer
|
||||||
.padding(.bottom, composerToolbarContext.composerFormattingEnabled ? 8 : 12)
|
.padding(.bottom, composerToolbarContext.composerFormattingEnabled ? 8 : 12)
|
||||||
.background {
|
.background {
|
||||||
if composerToolbarContext.composerFormattingEnabled {
|
if composerToolbarContext.composerFormattingEnabled {
|
||||||
@@ -188,6 +188,19 @@ struct RoomScreen: View {
|
|||||||
timelineContext.isScrolledToBottom && timelineContext.viewState.timelineState.isLive
|
timelineContext.isScrolledToBottom && timelineContext.viewState.timelineState.isLive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var composer: some View {
|
||||||
|
if roomContext.viewState.canSendMessage {
|
||||||
|
composerToolbar
|
||||||
|
} else {
|
||||||
|
Text(L10n.screenRoomTimelineNoPermissionToPost)
|
||||||
|
.font(.compound.bodyLG)
|
||||||
|
.foregroundStyle(.compound.textDisabled)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.padding(.vertical, 10) // Matches the MessageComposerStyleModifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var loadingIndicator: some View {
|
private var loadingIndicator: some View {
|
||||||
if timelineContext.viewState.showLoading {
|
if timelineContext.viewState.showLoading {
|
||||||
@@ -257,28 +270,50 @@ struct RoomScreen: View {
|
|||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct RoomScreen_Previews: PreviewProvider, TestablePreview {
|
struct RoomScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
static let roomProxyMock = JoinedRoomProxyMock(.init(id: "stable_id",
|
static let viewModels = makeViewModels()
|
||||||
name: "Preview room",
|
static let readOnlyViewModels = makeViewModels(canSendMessage: false)
|
||||||
hasOngoingCall: true))
|
|
||||||
static let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
|
|
||||||
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
|
||||||
timelineController: MockTimelineController(),
|
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
|
||||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
|
||||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
|
||||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
|
||||||
appMediator: AppMediatorMock.default,
|
|
||||||
appSettings: ServiceLocator.shared.settings,
|
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
|
||||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
|
||||||
clientProxy: ClientProxyMock(.init()))
|
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
RoomScreen(roomViewModel: roomViewModel,
|
RoomScreen(roomViewModel: viewModels.room,
|
||||||
timelineViewModel: timelineViewModel,
|
timelineViewModel: viewModels.timeline,
|
||||||
composerToolbar: ComposerToolbar.mock())
|
composerToolbar: ComposerToolbar.mock())
|
||||||
}
|
}
|
||||||
|
.previewDisplayName("Normal")
|
||||||
|
|
||||||
|
NavigationStack {
|
||||||
|
RoomScreen(roomViewModel: readOnlyViewModels.room,
|
||||||
|
timelineViewModel: readOnlyViewModels.timeline,
|
||||||
|
composerToolbar: ComposerToolbar.mock())
|
||||||
|
}
|
||||||
|
.previewDisplayName("Read-only")
|
||||||
|
.snapshotPreferences(expect: readOnlyViewModels.room.context.$viewState.map { !$0.canSendMessage })
|
||||||
|
}
|
||||||
|
|
||||||
|
static func makeViewModels(canSendMessage: Bool = true) -> ViewModels {
|
||||||
|
let roomProxyMock = JoinedRoomProxyMock(.init(id: "stable_id",
|
||||||
|
name: "Preview room",
|
||||||
|
hasOngoingCall: true,
|
||||||
|
canUserSendMessage: canSendMessage))
|
||||||
|
let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
|
||||||
|
let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
||||||
|
timelineController: MockTimelineController(),
|
||||||
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
|
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||||
|
appMediator: AppMediatorMock.default,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
|
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||||
|
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||||
|
clientProxy: ClientProxyMock(.init()))
|
||||||
|
|
||||||
|
return .init(room: roomViewModel, timeline: timelineViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ViewModels {
|
||||||
|
let room: RoomScreenViewModelProtocol
|
||||||
|
let timeline: TimelineViewModelProtocol
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -559,6 +559,15 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func canUser(userID: String, sendMessage messageType: MessageLikeEventType) async -> Result<Bool, RoomProxyError> {
|
||||||
|
do {
|
||||||
|
return try await .success(room.canUserSendMessage(userId: userID, message: messageType))
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed checking if the user can send message with error: \(error)")
|
||||||
|
return .failure(.sdkError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func canUser(userID: String, sendStateEvent event: StateEventType) async -> Result<Bool, RoomProxyError> {
|
func canUser(userID: String, sendStateEvent event: StateEventType) async -> Result<Bool, RoomProxyError> {
|
||||||
do {
|
do {
|
||||||
return try await .success(room.canUserSendState(userId: userID, stateEvent: event))
|
return try await .success(room.canUserSendState(userId: userID, stateEvent: event))
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
|
|||||||
func resetPowerLevels() async -> Result<RoomPowerLevels, RoomProxyError>
|
func resetPowerLevels() async -> Result<RoomPowerLevels, RoomProxyError>
|
||||||
func suggestedRole(for userID: String) async -> Result<RoomMemberRole, RoomProxyError>
|
func suggestedRole(for userID: String) async -> Result<RoomMemberRole, RoomProxyError>
|
||||||
func updatePowerLevelsForUsers(_ updates: [(userID: String, powerLevel: Int64)]) async -> Result<Void, RoomProxyError>
|
func updatePowerLevelsForUsers(_ updates: [(userID: String, powerLevel: Int64)]) async -> Result<Void, RoomProxyError>
|
||||||
|
func canUser(userID: String, sendMessage messageType: MessageLikeEventType) async -> Result<Bool, RoomProxyError>
|
||||||
func canUser(userID: String, sendStateEvent event: StateEventType) async -> Result<Bool, RoomProxyError>
|
func canUser(userID: String, sendStateEvent event: StateEventType) async -> Result<Bool, RoomProxyError>
|
||||||
func canUserInvite(userID: String) async -> Result<Bool, RoomProxyError>
|
func canUserInvite(userID: String) async -> Result<Bool, RoomProxyError>
|
||||||
func canUserRedactOther(userID: String) async -> Result<Bool, RoomProxyError>
|
func canUserRedactOther(userID: String) async -> Result<Bool, RoomProxyError>
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:30073fdf3797dd70a1a9a39301e355e46d78ce4ed064215e2222ea1e3ac4eff8
|
||||||
|
size 296108
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6c8f9d9ad5dfe6a949e392eb6a2014a26fa1c99dcd1609eebacf9fa3129f1ced
|
||||||
|
size 306552
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:bcc07c762ebeab3cb9de5c0e516fb47704a7309e28c6bbbd335e07692e55ccfa
|
||||||
|
size 182334
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:cc29ca8433a626ddd150d7852483179592bfe060f6290e6b94820b4b59544eb0
|
||||||
|
size 184921
|
||||||
Reference in New Issue
Block a user