Move ComposerToolbar view required dependencies to the view model and read them, similarly to the other views, through the context's view state.

This commit is contained in:
Stefan Ceriu
2025-06-06 14:15:58 +03:00
committed by Stefan Ceriu
parent 4072031f4e
commit af97567edc
7 changed files with 40 additions and 57 deletions
@@ -65,6 +65,8 @@ enum ComposerAttachmentType {
}
struct ComposerToolbarViewState: BindableState {
let wysiwygViewModel: WysiwygComposerViewModel
var composerMode: ComposerMode = .default
var composerEmpty = true
/// Could be false if sending is disabled in the room
@@ -76,6 +78,8 @@ struct ComposerToolbarViewState: BindableState {
var isRoomEncrypted: Bool
var isLocationSharingEnabled: Bool
var keyCommands: [WysiwygKeyCommand] = []
var bindings: ComposerToolbarViewStateBindings
var isUploading: Bool {
@@ -63,13 +63,20 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
mentionBuilder = MentionBuilder()
attributedStringBuilder = AttributedStringBuilder(cacheKey: "Composer", mentionBuilder: mentionBuilder)
super.init(initialViewState: ComposerToolbarViewState(audioPlayerState: .init(id: .recorderPreview, title: L10n.commonVoiceMessage, duration: 0),
super.init(initialViewState: ComposerToolbarViewState(wysiwygViewModel: wysiwygViewModel,
audioPlayerState: .init(id: .recorderPreview, title: L10n.commonVoiceMessage, duration: 0),
audioRecorderState: .init(),
isRoomEncrypted: roomProxy.infoPublisher.value.isEncrypted,
isLocationSharingEnabled: appSettings.mapTilerConfiguration.isEnabled,
bindings: .init()),
mediaProvider: mediaProvider)
state.keyCommands = [
.enter { [weak self] in
self?.process(viewAction: .sendMessage)
}
]
roomProxy.infoPublisher
.map(\.isEncrypted)
.receive(on: DispatchQueue.main)
@@ -100,6 +107,13 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
}
.store(in: &cancellables)
// Needs to be observable or the placeholder and the dictation state will not be managed correctly.
wysiwygViewModel.objectWillChange
.sink { [weak self] _ in
self?.context.objectWillChange.send()
}
.store(in: &cancellables)
context.$viewState
.map(\.bindings.plainComposerText)
.removeDuplicates()
@@ -269,14 +283,6 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
handleSaveDraft(isVolatile: false)
}
var keyCommands: [WysiwygKeyCommand] {
[
.enter { [weak self] in
self?.process(viewAction: .sendMessage)
}
]
}
// MARK: - Private
private func handleLoadDraft(_ draft: ComposerDraftProxy) {
@@ -13,7 +13,6 @@ import WysiwygComposer
protocol ComposerToolbarViewModelProtocol {
var actions: AnyPublisher<ComposerToolbarViewModelAction, Never> { get }
var context: ComposerToolbarViewModelType.Context { get }
var keyCommands: [WysiwygKeyCommand] { get }
func process(timelineAction: TimelineComposerAction)
func loadDraft() async
@@ -13,12 +13,6 @@ import WysiwygComposer
struct ComposerToolbar: View {
@ObservedObject var context: ComposerToolbarViewModel.Context
// Needs to be observable or the placeholder and the dictation state are not managed correctly.
@ObservedObject var wysiwygViewModel: WysiwygComposerViewModel
let keyCommands: [WysiwygKeyCommand]
@FocusState private var composerFocused: Bool
@State private var frame: CGRect = .zero
@Environment(\.verticalSizeClass) private var verticalSizeClass
@@ -249,9 +243,9 @@ struct ComposerToolbar: View {
private var composerView: WysiwygComposerView {
WysiwygComposerView(placeholder: placeholder,
placeholderColor: .compound.textSecondary,
viewModel: wysiwygViewModel,
viewModel: context.viewState.wysiwygViewModel,
itemProviderHelper: ItemProviderHelper(),
keyCommands: keyCommands) { provider in
keyCommands: context.viewState.keyCommands) { provider in
context.send(viewAction: .handlePasteOrDrop(provider: provider))
}
}
@@ -343,9 +337,7 @@ struct ComposerToolbar_Previews: PreviewProvider, TestablePreview {
// Putting them is VStack allows the completion suggestion preview to work properly in tests
VStack(spacing: 8) {
// The mock functon can't be used in this context because it does not hold a reference to the view model, losing the combine subscriptions
ComposerToolbar(context: composerViewModel.context,
wysiwygViewModel: wysiwygViewModel,
keyCommands: [])
ComposerToolbar(context: composerViewModel.context)
}
.previewDisplayName("With Suggestions")
@@ -386,9 +378,7 @@ extension ComposerToolbar {
model.state.composerEmpty = focused
return model
}
return ComposerToolbar(context: composerViewModel.context,
wysiwygViewModel: wysiwygViewModel,
keyCommands: [])
return ComposerToolbar(context: composerViewModel.context)
}
static func textWithVoiceMessage(focused: Bool = true) -> ComposerToolbar {
@@ -405,9 +395,7 @@ extension ComposerToolbar {
model.state.composerEmpty = focused
return model
}
return ComposerToolbar(context: composerViewModel.context,
wysiwygViewModel: wysiwygViewModel,
keyCommands: [])
return ComposerToolbar(context: composerViewModel.context)
}
static func voiceMessageRecordingMock() -> ComposerToolbar {
@@ -424,9 +412,7 @@ extension ComposerToolbar {
model.state.composerMode = .recordVoiceMessage(state: AudioRecorderState())
return model
}
return ComposerToolbar(context: composerViewModel.context,
wysiwygViewModel: wysiwygViewModel,
keyCommands: [])
return ComposerToolbar(context: composerViewModel.context)
}
static func voiceMessagePreviewMock(uploading: Bool) -> ComposerToolbar {
@@ -448,9 +434,7 @@ extension ComposerToolbar {
isUploading: uploading)
return model
}
return ComposerToolbar(context: composerViewModel.context,
wysiwygViewModel: wysiwygViewModel,
keyCommands: [])
return ComposerToolbar(context: composerViewModel.context)
}
static func replyLoadingPreviewMock(isLoading: Bool) -> ComposerToolbar {
@@ -473,9 +457,7 @@ extension ComposerToolbar {
eventID: "", eventContent: .message(.text(.init(body: "Hello World!")))), isThread: false)
return model
}
return ComposerToolbar(context: composerViewModel.context,
wysiwygViewModel: wysiwygViewModel,
keyCommands: [])
return ComposerToolbar(context: composerViewModel.context)
}
static func disabledPreviewMock() -> ComposerToolbar {
@@ -492,8 +474,6 @@ extension ComposerToolbar {
model.state.canSend = false
return model
}
return ComposerToolbar(context: composerViewModel.context,
wysiwygViewModel: wysiwygViewModel,
keyCommands: [])
return ComposerToolbar(context: composerViewModel.context)
}
}