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
|
||||
|
||||
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 canUserUserIDSendStateEventCallsCount: Int {
|
||||
get {
|
||||
|
||||
@@ -36,6 +36,7 @@ struct JoinedRoomProxyMockConfiguration {
|
||||
var ownUserID = RoomMemberProxyMock.mockMe.userID
|
||||
var inviter: RoomMemberProxyProtocol?
|
||||
|
||||
var canUserSendMessage = true
|
||||
var canUserInvite = true
|
||||
var canUserTriggerRoomNotification = false
|
||||
var canUserJoinCall = true
|
||||
@@ -91,6 +92,7 @@ extension JoinedRoomProxyMock {
|
||||
return .success(member.role)
|
||||
}
|
||||
updatePowerLevelsForUsersReturnValue = .success(())
|
||||
canUserUserIDSendMessageReturnValue = .success(configuration.canUserSendMessage)
|
||||
canUserUserIDSendStateEventClosure = { [weak self] userID, _ in
|
||||
.success(self?.membersPublisher.value.first { $0.userID == userID }?.role ?? .user != .user)
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ struct RoomScreenViewState: BindableState {
|
||||
!pinnedEventsBannerState.isEmpty && lastScrollDirection != .top
|
||||
}
|
||||
|
||||
var canSendMessage = true
|
||||
var canJoinCall = false
|
||||
var hasOngoingCall: Bool
|
||||
var shouldShowCallButton = true
|
||||
|
||||
@@ -340,6 +340,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
}
|
||||
|
||||
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.canAcceptKnocks = await (try? roomProxy.canUserInvite(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))
|
||||
}
|
||||
|
||||
composerToolbar
|
||||
composer
|
||||
.padding(.bottom, composerToolbarContext.composerFormattingEnabled ? 8 : 12)
|
||||
.background {
|
||||
if composerToolbarContext.composerFormattingEnabled {
|
||||
@@ -188,6 +188,19 @@ struct RoomScreen: View {
|
||||
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
|
||||
private var loadingIndicator: some View {
|
||||
if timelineContext.viewState.showLoading {
|
||||
@@ -257,28 +270,50 @@ struct RoomScreen: View {
|
||||
// MARK: - Previews
|
||||
|
||||
struct RoomScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let roomProxyMock = JoinedRoomProxyMock(.init(id: "stable_id",
|
||||
name: "Preview room",
|
||||
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 let viewModels = makeViewModels()
|
||||
static let readOnlyViewModels = makeViewModels(canSendMessage: false)
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
RoomScreen(roomViewModel: roomViewModel,
|
||||
timelineViewModel: timelineViewModel,
|
||||
RoomScreen(roomViewModel: viewModels.room,
|
||||
timelineViewModel: viewModels.timeline,
|
||||
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> {
|
||||
do {
|
||||
return try await .success(room.canUserSendState(userId: userID, stateEvent: event))
|
||||
|
||||
@@ -150,6 +150,7 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
|
||||
func resetPowerLevels() async -> Result<RoomPowerLevels, RoomProxyError>
|
||||
func suggestedRole(for userID: String) async -> Result<RoomMemberRole, 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 canUserInvite(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
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bcc07c762ebeab3cb9de5c0e516fb47704a7309e28c6bbbd335e07692e55ccfa
|
||||
size 182334
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cc29ca8433a626ddd150d7852483179592bfe060f6290e6b94820b4b59544eb0
|
||||
size 184921
|
||||
Reference in New Issue
Block a user