mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
- room composer contextual menu for selecting source - coordinator for presenting the different sources - system picker handling and URL passback - file preview after selection - feature flag
This commit is contained in:
committed by
Stefan Ceriu
parent
2359910857
commit
5f6edacc1a
@@ -62,6 +62,12 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
self.displayEmojiPickerScreen(for: itemId)
|
||||
case .displayReportContent(let itemId):
|
||||
self.displayReportContent(for: itemId)
|
||||
case .displayCameraPicker:
|
||||
self.displayMediaPickerWithSource(.camera)
|
||||
case .displayMediaPicker:
|
||||
self.displayMediaPickerWithSource(.photoLibrary)
|
||||
case .displayDocumentPicker:
|
||||
self.displayMediaPickerWithSource(.documents)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,8 +80,32 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(RoomScreen(context: viewModel.context))
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func displayMediaPickerWithSource(_ source: MediaPickerSource) {
|
||||
let mediaPickerCoordinator = MediaPickerCoordinator(source: source) { [weak self] action in
|
||||
switch action {
|
||||
case .cancel:
|
||||
self?.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
case .error:
|
||||
break
|
||||
case .selectMediaAtURL(let url):
|
||||
let mediaPickerPreviewScreenCoordinator = MediaPickerPreviewScreenCoordinator(parameters: .init(url: url, title: url.lastPathComponent)) { action in
|
||||
switch action {
|
||||
case .send:
|
||||
self?.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
case .cancel:
|
||||
self?.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
}
|
||||
|
||||
self?.navigationStackCoordinator.setSheetCoordinator(mediaPickerPreviewScreenCoordinator)
|
||||
}
|
||||
}
|
||||
|
||||
navigationStackCoordinator.setSheetCoordinator(mediaPickerCoordinator)
|
||||
}
|
||||
|
||||
private func displayFilePreview(for file: MediaFileHandleProxy, with title: String?) {
|
||||
let params = FilePreviewCoordinatorParameters(mediaFile: file, title: title)
|
||||
|
||||
@@ -23,6 +23,9 @@ enum RoomScreenViewModelAction {
|
||||
case displayMediaFile(file: MediaFileHandleProxy, title: String?)
|
||||
case displayEmojiPicker(itemId: String)
|
||||
case displayReportContent(itemId: String)
|
||||
case displayCameraPicker
|
||||
case displayMediaPicker
|
||||
case displayDocumentPicker
|
||||
}
|
||||
|
||||
enum RoomScreenComposerMode: Equatable {
|
||||
@@ -55,6 +58,10 @@ enum RoomScreenViewAction {
|
||||
/// Mark the entire room as read - this is heavy handed as a starting point for now.
|
||||
case markRoomAsRead
|
||||
case contextMenuAction(itemID: String, action: TimelineItemContextMenuAction)
|
||||
|
||||
case displayCameraPicker
|
||||
case displayMediaPicker
|
||||
case displayDocumentPicker
|
||||
}
|
||||
|
||||
struct RoomScreenViewState: BindableState {
|
||||
@@ -66,6 +73,7 @@ struct RoomScreenViewState: BindableState {
|
||||
var isBackPaginating = false
|
||||
var showLoading = false
|
||||
var timelineStyle: TimelineStyle
|
||||
var mediaUploadingFlowEnabled: Bool
|
||||
|
||||
var bindings: RoomScreenViewStateBindings
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
roomTitle: roomName ?? "Unknown room 💥",
|
||||
roomAvatarURL: roomAvatarUrl,
|
||||
timelineStyle: ServiceLocator.shared.settings.timelineStyle,
|
||||
mediaUploadingFlowEnabled: ServiceLocator.shared.settings.mediaUploadingFlowEnabled,
|
||||
bindings: .init(composerText: "", composerFocused: false)),
|
||||
imageProvider: mediaProvider)
|
||||
|
||||
@@ -74,6 +75,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
.weakAssign(to: \.state.timelineStyle, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
ServiceLocator.shared.settings.$mediaUploadingFlowEnabled
|
||||
.weakAssign(to: \.state.mediaUploadingFlowEnabled, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
buildTimelineViews()
|
||||
}
|
||||
|
||||
@@ -111,6 +116,12 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
await markRoomAsRead()
|
||||
case .contextMenuAction(let itemID, let action):
|
||||
processContentMenuAction(action, itemID: itemID)
|
||||
case .displayCameraPicker:
|
||||
callback?(.displayCameraPicker)
|
||||
case .displayMediaPicker:
|
||||
callback?(.displayMediaPicker)
|
||||
case .displayDocumentPicker:
|
||||
callback?(.displayDocumentPicker)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,16 @@ struct RoomScreen: View {
|
||||
var body: some View {
|
||||
timeline
|
||||
.background(Color.element.background.ignoresSafeArea()) // Kills the toolbar translucency.
|
||||
.safeAreaInset(edge: .bottom, spacing: 0) { messageComposer }
|
||||
.safeAreaInset(edge: .bottom, spacing: 0) {
|
||||
HStack(spacing: 4.0) {
|
||||
if context.viewState.mediaUploadingFlowEnabled {
|
||||
sendAttachmentButton
|
||||
}
|
||||
|
||||
messageComposer
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar { toolbar }
|
||||
.toolbarRole(.editor) // Hide the back button title.
|
||||
@@ -40,7 +49,7 @@ struct RoomScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
var timeline: some View {
|
||||
private var timeline: some View {
|
||||
TimelineView()
|
||||
.id(context.viewState.roomId)
|
||||
.environmentObject(context)
|
||||
@@ -48,7 +57,7 @@ struct RoomScreen: View {
|
||||
.overlay(alignment: .bottomTrailing) { scrollToBottomButton }
|
||||
}
|
||||
|
||||
var messageComposer: some View {
|
||||
private var messageComposer: some View {
|
||||
MessageComposer(text: $context.composerText,
|
||||
focused: $context.composerFocused,
|
||||
sendingDisabled: context.viewState.sendButtonDisabled,
|
||||
@@ -59,10 +68,9 @@ struct RoomScreen: View {
|
||||
} editCancellationAction: {
|
||||
context.send(viewAction: .cancelEdit)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
var scrollToBottomButton: some View {
|
||||
private var scrollToBottomButton: some View {
|
||||
Button { context.viewState.scrollToBottomPublisher.send(()) } label: {
|
||||
Image(systemName: "chevron.down")
|
||||
.font(.element.body)
|
||||
@@ -84,7 +92,7 @@ struct RoomScreen: View {
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var loadingIndicator: some View {
|
||||
private var loadingIndicator: some View {
|
||||
if context.viewState.showLoading {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
@@ -96,7 +104,7 @@ struct RoomScreen: View {
|
||||
}
|
||||
|
||||
@ToolbarContentBuilder
|
||||
var toolbar: some ToolbarContent {
|
||||
private var toolbar: some ToolbarContent {
|
||||
// .principal + .primaryAction works better than .navigation leading + trailing
|
||||
// as the latter disables interaction in the action button for rooms with long names
|
||||
ToolbarItem(placement: .principal) {
|
||||
@@ -104,6 +112,30 @@ struct RoomScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var sendAttachmentButton: some View {
|
||||
Menu {
|
||||
Button {
|
||||
context.send(viewAction: .displayDocumentPicker)
|
||||
} label: {
|
||||
Label(UntranslatedL10n.mediaUploadDocumentPicker, systemImage: "doc")
|
||||
}
|
||||
Button {
|
||||
context.send(viewAction: .displayMediaPicker)
|
||||
} label: {
|
||||
Label(UntranslatedL10n.mediaUploadPhotoAndVideoPicker, systemImage: "photo")
|
||||
}
|
||||
Button {
|
||||
context.send(viewAction: .displayCameraPicker)
|
||||
} label: {
|
||||
Label(UntranslatedL10n.mediaUploadCameraPicker, systemImage: "camera")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "plus.circle")
|
||||
.font(.element.title1)
|
||||
.foregroundColor(.element.brand)
|
||||
}
|
||||
}
|
||||
|
||||
private func sendMessage() {
|
||||
guard !context.viewState.sendButtonDisabled else { return }
|
||||
context.send(viewAction: .sendMessage)
|
||||
|
||||
Reference in New Issue
Block a user