Disable the composer when you don't have the power to post. (#4076)

This commit is contained in:
Doug
2025-04-29 10:12:20 +01:00
committed by GitHub
parent 41b18cfd52
commit 58b982a8f8
15 changed files with 150 additions and 19 deletions
@@ -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
}
}