mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Edit poll UX (#2151)
* Add edit poll on room proxy * Add CreatePollMode * Add “edit poll” presentation flow * Add delete poll section * Inject editing poll * Add submit action * Refactor validation logic * Add edit/delete actions * Fix bubble timestamp for polls * Update localisations * Refactor CreatePoll -> PollForm * Refactor tests * Update rust sdk to 0.0.5-november23 * Update confirmation alerts * Add edit support in TimelineItem menu * Refactor a11y id * Cleanup * Fix failing tests * Add tests * Refine isEditable workaround * Refactor timestamp in TimelineItemBubbledStylerView
This commit is contained in:
@@ -36,7 +36,7 @@ enum RoomScreenCoordinatorAction {
|
||||
case presentMediaUploadPreviewScreen(URL)
|
||||
case presentRoomDetails
|
||||
case presentLocationPicker
|
||||
case presentPollForm
|
||||
case presentPollForm(mode: PollFormMode)
|
||||
case presentLocationViewer(body: String, geoURI: GeoURI, description: String?)
|
||||
case presentEmojiPicker(itemID: TimelineItemIdentifier, selectedEmojis: Set<String>)
|
||||
case presentRoomMemberDetails(member: RoomMemberProxyProtocol)
|
||||
@@ -104,8 +104,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
actionsSubject.send(.presentMediaUploadPicker(.documents))
|
||||
case .displayLocationPicker:
|
||||
actionsSubject.send(.presentLocationPicker)
|
||||
case .displayPollForm:
|
||||
actionsSubject.send(.presentPollForm)
|
||||
case .displayPollForm(let mode):
|
||||
actionsSubject.send(.presentPollForm(mode: mode))
|
||||
case .displayMediaUploadPreviewScreen(let url):
|
||||
actionsSubject.send(.presentMediaUploadPreviewScreen(url))
|
||||
case .displayRoomMemberDetails(let member):
|
||||
|
||||
@@ -24,6 +24,7 @@ enum RoomScreenInteractionHandlerAction {
|
||||
case displayReportContent(itemID: TimelineItemIdentifier, senderID: String)
|
||||
case displayMessageForwarding(itemID: TimelineItemIdentifier)
|
||||
case displayMediaUploadPreviewScreen(url: URL)
|
||||
case displayPollForm(mode: PollFormMode)
|
||||
case displayRoomMemberDetails(member: RoomMemberProxyProtocol)
|
||||
case showActionMenu(TimelineItemActionMenuInfo)
|
||||
case showDebugInfo(TimelineItemDebugInfo)
|
||||
@@ -185,30 +186,18 @@ class RoomScreenInteractionHandler {
|
||||
|
||||
UIPasteboard.general.string = messageTimelineItem.body
|
||||
case .edit:
|
||||
guard let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol else {
|
||||
return
|
||||
}
|
||||
|
||||
let text: String
|
||||
switch messageTimelineItem.contentType {
|
||||
case .text(let textItem):
|
||||
if ServiceLocator.shared.settings.richTextEditorEnabled, let formattedBodyHTMLString = textItem.formattedBodyHTMLString {
|
||||
text = formattedBodyHTMLString
|
||||
} else {
|
||||
text = messageTimelineItem.body
|
||||
}
|
||||
case .emote(let emoteItem):
|
||||
if ServiceLocator.shared.settings.richTextEditorEnabled, let formattedBodyHTMLString = emoteItem.formattedBodyHTMLString {
|
||||
text = "/me " + formattedBodyHTMLString
|
||||
} else {
|
||||
text = "/me " + messageTimelineItem.body
|
||||
switch timelineItem {
|
||||
case let messageTimelineItem as EventBasedMessageTimelineItemProtocol:
|
||||
processEditMessageEvent(messageTimelineItem)
|
||||
case let pollTimelineItem as PollRoomTimelineItem:
|
||||
guard let eventID = pollTimelineItem.id.eventID else {
|
||||
MXLog.error("Cannot edit poll with id: \(timelineItem.id)")
|
||||
return
|
||||
}
|
||||
actionsSubject.send(.displayPollForm(mode: .edit(eventID: eventID, poll: pollTimelineItem.poll)))
|
||||
default:
|
||||
text = messageTimelineItem.body
|
||||
MXLog.error("Cannot edit item with id: \(timelineItem.id)")
|
||||
}
|
||||
|
||||
actionsSubject.send(.composer(action: .setText(text: text)))
|
||||
actionsSubject.send(.composer(action: .setMode(mode: .edit(originalItemId: messageTimelineItem.id))))
|
||||
case .copyPermalink:
|
||||
do {
|
||||
guard let eventID = eventTimelineItem.id.eventID else {
|
||||
@@ -258,6 +247,29 @@ class RoomScreenInteractionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private func processEditMessageEvent(_ messageTimelineItem: EventBasedMessageTimelineItemProtocol) {
|
||||
let text: String
|
||||
switch messageTimelineItem.contentType {
|
||||
case .text(let textItem):
|
||||
if ServiceLocator.shared.settings.richTextEditorEnabled, let formattedBodyHTMLString = textItem.formattedBodyHTMLString {
|
||||
text = formattedBodyHTMLString
|
||||
} else {
|
||||
text = messageTimelineItem.body
|
||||
}
|
||||
case .emote(let emoteItem):
|
||||
if ServiceLocator.shared.settings.richTextEditorEnabled, let formattedBodyHTMLString = emoteItem.formattedBodyHTMLString {
|
||||
text = "/me " + formattedBodyHTMLString
|
||||
} else {
|
||||
text = "/me " + messageTimelineItem.body
|
||||
}
|
||||
default:
|
||||
text = messageTimelineItem.body
|
||||
}
|
||||
|
||||
actionsSubject.send(.composer(action: .setText(text: text)))
|
||||
actionsSubject.send(.composer(action: .setMode(mode: .edit(originalItemId: messageTimelineItem.id))))
|
||||
}
|
||||
|
||||
// MARK: Polls
|
||||
|
||||
func sendPollResponse(pollStartID: String, optionID: String) {
|
||||
|
||||
@@ -28,7 +28,7 @@ enum RoomScreenViewModelAction {
|
||||
case displayMediaPicker
|
||||
case displayDocumentPicker
|
||||
case displayLocationPicker
|
||||
case displayPollForm
|
||||
case displayPollForm(mode: PollFormMode)
|
||||
case displayMediaUploadPreviewScreen(url: URL)
|
||||
case displayRoomMemberDetails(member: RoomMemberProxyProtocol)
|
||||
case displayMessageForwarding(itemID: TimelineItemIdentifier)
|
||||
@@ -66,6 +66,7 @@ enum RoomScreenComposerMode: Equatable {
|
||||
enum RoomScreenViewPollAction {
|
||||
case selectOption(pollStartID: String, optionID: String)
|
||||
case end(pollStartID: String)
|
||||
case edit(pollStartID: String, poll: Poll)
|
||||
}
|
||||
|
||||
enum RoomScreenViewAudioAction {
|
||||
|
||||
@@ -188,8 +188,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
actionsSubject.send(.displayDocumentPicker)
|
||||
case .displayLocationPicker:
|
||||
actionsSubject.send(.displayLocationPicker)
|
||||
case .displayPollForm:
|
||||
actionsSubject.send(.displayPollForm)
|
||||
case .displayNewPollForm:
|
||||
actionsSubject.send(.displayPollForm(mode: .new))
|
||||
case .handlePasteOrDrop(let provider):
|
||||
roomScreenInteractionHandler.handlePasteOrDrop(provider)
|
||||
case .composerModeChanged(mode: let mode):
|
||||
@@ -213,6 +213,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
message: L10n.commonPollEndConfirmation,
|
||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
||||
secondaryButton: .init(title: L10n.actionOk, action: { self.roomScreenInteractionHandler.endPoll(pollStartID: pollStartID) }))
|
||||
case .edit(let pollStartID, let poll):
|
||||
actionsSubject.send(.displayPollForm(mode: .edit(eventID: pollStartID, poll: poll)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,6 +321,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
actionsSubject.send(.displayEmojiPicker(itemID: itemID, selectedEmojis: selectedEmojis))
|
||||
case .displayMessageForwarding(let itemID):
|
||||
actionsSubject.send(.displayMessageForwarding(itemID: itemID))
|
||||
case .displayPollForm(let mode):
|
||||
actionsSubject.send(.displayPollForm(mode: mode))
|
||||
case .displayReportContent(let itemID, let senderID):
|
||||
actionsSubject.send(.displayReportContent(itemID: itemID, senderID: senderID))
|
||||
case .displayMediaUploadPreviewScreen(let url):
|
||||
|
||||
@@ -202,12 +202,8 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
@ViewBuilder
|
||||
var localizedSendInfo: some View {
|
||||
HStack(spacing: 4) {
|
||||
if let timelineItem = timelineItem as? TextBasedRoomTimelineItem {
|
||||
Text(timelineItem.localizedSendInfo)
|
||||
} else {
|
||||
Text(timelineItem.timestamp)
|
||||
}
|
||||
|
||||
Text(timelineItem.localizedSendInfo)
|
||||
|
||||
if adjustedDeliveryStatus == .sendingFailed {
|
||||
CompoundIcon(\.error, size: .xSmall, relativeTo: .compound.bodyXS)
|
||||
.accessibilityLabel(L10n.commonSendingFailed)
|
||||
|
||||
@@ -89,11 +89,11 @@ struct PollRoomTimelineView: View {
|
||||
|
||||
@ViewBuilder
|
||||
private var toolbarView: some View {
|
||||
if !poll.hasEnded, poll.createdByAccountOwner, let eventID {
|
||||
if !poll.hasEnded, poll.createdByAccountOwner {
|
||||
Button {
|
||||
context.send(viewAction: .poll(.end(pollStartID: eventID)))
|
||||
toolbarAction()
|
||||
} label: {
|
||||
Text(L10n.actionEndPoll)
|
||||
Text(timelineItem.isEditable ? L10n.actionEditPoll : L10n.actionEndPoll)
|
||||
.lineLimit(2, reservesSpace: false)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.foregroundColor(.compound.textOnSolidPrimary)
|
||||
@@ -108,6 +108,18 @@ struct PollRoomTimelineView: View {
|
||||
.padding(.top, 8)
|
||||
}
|
||||
}
|
||||
|
||||
private func toolbarAction() {
|
||||
guard let eventID else {
|
||||
return
|
||||
}
|
||||
|
||||
if timelineItem.isEditable {
|
||||
context.send(viewAction: .poll(.edit(pollStartID: eventID, poll: poll)))
|
||||
} else {
|
||||
context.send(viewAction: .poll(.end(pollStartID: eventID)))
|
||||
}
|
||||
}
|
||||
|
||||
private func progressBarColor(for option: Poll.Option) -> Color {
|
||||
if poll.hasEnded {
|
||||
@@ -194,5 +206,15 @@ struct PollRoomTimelineView_Previews: PreviewProvider, TestablePreview {
|
||||
.environment(\.timelineStyle, .plain)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Creator, disclosed, Plain")
|
||||
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .emptyDisclosed, isEditable: true))
|
||||
.environment(\.timelineStyle, .bubbles)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Creator, no votes, Bubble")
|
||||
|
||||
PollRoomTimelineView(timelineItem: .mock(poll: .emptyDisclosed, isEditable: true))
|
||||
.environment(\.timelineStyle, .plain)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Creator, no votes, Plain")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user