Allow polls to be edited (#1869)
Polls can be edited if they do not have any votes --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
4e52244b86
commit
8fcec4a006
50 changed files with 827 additions and 173 deletions
|
|
@ -50,6 +50,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
|
||||
import io.element.android.features.poll.api.create.CreatePollEntryPoint
|
||||
import io.element.android.features.poll.api.create.CreatePollMode
|
||||
import io.element.android.libraries.architecture.BackstackNode
|
||||
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
|
|
@ -113,6 +114,9 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
|
||||
@Parcelize
|
||||
data object CreatePoll : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class EditPoll(val eventId: EventId) : NavTarget
|
||||
}
|
||||
|
||||
private val callback = plugins<MessagesEntryPoint.Callback>().firstOrNull()
|
||||
|
|
@ -157,6 +161,10 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
backstack.push(NavTarget.CreatePoll)
|
||||
}
|
||||
|
||||
override fun onEditPollClicked(eventId: EventId) {
|
||||
backstack.push(NavTarget.EditPoll(eventId))
|
||||
}
|
||||
|
||||
override fun onJoinCallClicked(roomId: RoomId) {
|
||||
val inputs = CallType.RoomCall(
|
||||
sessionId = matrixClient.sessionId,
|
||||
|
|
@ -204,7 +212,14 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
sendLocationEntryPoint.createNode(this, buildContext)
|
||||
}
|
||||
NavTarget.CreatePoll -> {
|
||||
createPollEntryPoint.createNode(this, buildContext)
|
||||
createPollEntryPoint.nodeBuilder(this, buildContext)
|
||||
.params(CreatePollEntryPoint.Params(mode = CreatePollMode.NewPoll))
|
||||
.build()
|
||||
}
|
||||
is NavTarget.EditPoll -> {
|
||||
createPollEntryPoint.nodeBuilder(this, buildContext)
|
||||
.params(CreatePollEntryPoint.Params(mode = CreatePollMode.EditPoll(eventId = navTarget.eventId)))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,4 +24,5 @@ interface MessagesNavigator {
|
|||
fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo)
|
||||
fun onForwardEventClicked(eventId: EventId)
|
||||
fun onReportContentClicked(eventId: EventId, senderId: UserId)
|
||||
fun onEditPollClicked(eventId: EventId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ class MessagesNode @AssistedInject constructor(
|
|||
fun onReportMessage(eventId: EventId, senderId: UserId)
|
||||
fun onSendLocationClicked()
|
||||
fun onCreatePollClicked()
|
||||
fun onEditPollClicked(eventId: EventId)
|
||||
fun onJoinCallClicked(roomId: RoomId)
|
||||
}
|
||||
|
||||
|
|
@ -107,6 +108,10 @@ class MessagesNode @AssistedInject constructor(
|
|||
callback?.onReportMessage(eventId, senderId)
|
||||
}
|
||||
|
||||
override fun onEditPollClicked(eventId: EventId) {
|
||||
callback?.onEditPollClicked(eventId)
|
||||
}
|
||||
|
||||
private fun onSendLocationClicked() {
|
||||
callback?.onSendLocationClicked()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ class MessagesPresenter @AssistedInject constructor(
|
|||
private val room: MatrixRoom,
|
||||
private val composerPresenter: MessageComposerPresenter,
|
||||
private val voiceMessageComposerPresenter: VoiceMessageComposerPresenter,
|
||||
private val timelinePresenter: TimelinePresenter,
|
||||
timelinePresenterFactory: TimelinePresenter.Factory,
|
||||
private val actionListPresenter: ActionListPresenter,
|
||||
private val customReactionPresenter: CustomReactionPresenter,
|
||||
private val reactionSummaryPresenter: ReactionSummaryPresenter,
|
||||
|
|
@ -110,6 +110,8 @@ class MessagesPresenter @AssistedInject constructor(
|
|||
private val buildMeta: BuildMeta,
|
||||
) : Presenter<MessagesState> {
|
||||
|
||||
private val timelinePresenter = timelinePresenterFactory.create(navigator = navigator)
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(navigator: MessagesNavigator): MessagesPresenter
|
||||
|
|
@ -294,20 +296,28 @@ class MessagesPresenter @AssistedInject constructor(
|
|||
composerState: MessageComposerState,
|
||||
enableTextFormatting: Boolean,
|
||||
) {
|
||||
val composerMode = MessageComposerMode.Edit(
|
||||
targetEvent.eventId,
|
||||
(targetEvent.content as? TimelineItemTextBasedContent)?.let {
|
||||
if (enableTextFormatting) {
|
||||
it.htmlBody ?: it.body
|
||||
} else {
|
||||
it.body
|
||||
}
|
||||
}.orEmpty(),
|
||||
targetEvent.transactionId,
|
||||
)
|
||||
composerState.eventSink(
|
||||
MessageComposerEvents.SetMode(composerMode)
|
||||
)
|
||||
when (targetEvent.content) {
|
||||
is TimelineItemPollContent -> {
|
||||
if (targetEvent.eventId == null) return
|
||||
navigator.onEditPollClicked(targetEvent.eventId)
|
||||
}
|
||||
else -> {
|
||||
val composerMode = MessageComposerMode.Edit(
|
||||
targetEvent.eventId,
|
||||
(targetEvent.content as? TimelineItemTextBasedContent)?.let {
|
||||
if (enableTextFormatting) {
|
||||
it.htmlBody ?: it.body
|
||||
} else {
|
||||
it.body
|
||||
}
|
||||
}.orEmpty(),
|
||||
targetEvent.transactionId,
|
||||
)
|
||||
composerState.eventSink(
|
||||
MessageComposerEvents.SetMode(composerMode)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleActionReply(targetEvent: TimelineItem.Event, composerState: MessageComposerState) {
|
||||
|
|
|
|||
|
|
@ -112,7 +112,10 @@ class ActionListPresenter @Inject constructor(
|
|||
// Can only reply or forward messages already uploaded to the server
|
||||
add(TimelineItemAction.Reply)
|
||||
}
|
||||
if (!timelineItem.content.isEnded && timelineItem.isRemote && isMineOrCanRedact) {
|
||||
if (timelineItem.isRemote && timelineItem.isEditable) {
|
||||
add(TimelineItemAction.Edit)
|
||||
}
|
||||
if (timelineItem.isRemote && !timelineItem.content.isEnded && isMineOrCanRedact) {
|
||||
add(TimelineItemAction.EndPoll)
|
||||
}
|
||||
if (timelineItem.content.canBeCopied()) {
|
||||
|
|
|
|||
|
|
@ -30,4 +30,8 @@ sealed interface TimelineEvents {
|
|||
data class PollEndClicked(
|
||||
val pollStartId: EventId,
|
||||
) : TimelineEvents
|
||||
|
||||
data class PollEditClicked(
|
||||
val pollStartId: EventId,
|
||||
) : TimelineEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,12 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.PollEnd
|
||||
import im.vector.app.features.analytics.plan.PollVote
|
||||
import io.element.android.features.messages.impl.MessagesNavigator
|
||||
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.session.SessionState
|
||||
|
|
@ -54,16 +58,16 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val BACK_PAGINATION_EVENT_LIMIT = 20
|
||||
private const val BACK_PAGINATION_PAGE_SIZE = 50
|
||||
|
||||
class TimelinePresenter @Inject constructor(
|
||||
class TimelinePresenter @AssistedInject constructor(
|
||||
private val timelineItemsFactory: TimelineItemsFactory,
|
||||
private val room: MatrixRoom,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val appScope: CoroutineScope,
|
||||
@Assisted private val navigator: MessagesNavigator,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val verificationService: SessionVerificationService,
|
||||
private val encryptionService: EncryptionService,
|
||||
|
|
@ -71,6 +75,11 @@ class TimelinePresenter @Inject constructor(
|
|||
private val redactedVoiceMessageManager: RedactedVoiceMessageManager,
|
||||
) : Presenter<TimelineState> {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(navigator: MessagesNavigator): TimelinePresenter
|
||||
}
|
||||
|
||||
private val timeline = room.timeline
|
||||
|
||||
@Composable
|
||||
|
|
@ -135,6 +144,8 @@ class TimelinePresenter @Inject constructor(
|
|||
)
|
||||
analyticsService.capture(PollEnd())
|
||||
}
|
||||
is TimelineEvents.PollEditClicked ->
|
||||
navigator.onEditPollClicked(event.pollStartId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ internal fun aTimelineItemEvent(
|
|||
eventId: EventId = EventId("\$" + Random.nextInt().toString()),
|
||||
transactionId: TransactionId? = null,
|
||||
isMine: Boolean = false,
|
||||
isEditable: Boolean = false,
|
||||
senderDisplayName: String = "Sender",
|
||||
content: TimelineItemEventContent = aTimelineItemTextContent(),
|
||||
groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None,
|
||||
|
|
@ -139,6 +140,7 @@ internal fun aTimelineItemEvent(
|
|||
readReceiptState = readReceiptState,
|
||||
sentTime = "12:34",
|
||||
isMine = isMine,
|
||||
isEditable = isEditable,
|
||||
senderDisplayName = senderDisplayName,
|
||||
groupPosition = groupPosition,
|
||||
localSendState = sendState,
|
||||
|
|
|
|||
|
|
@ -526,6 +526,7 @@ private fun MessageEventBubbleContent(
|
|||
TimelineItemEventContentView(
|
||||
content = event.content,
|
||||
isMine = event.isMine,
|
||||
isEditable = event.isEditable,
|
||||
interactionSource = interactionSource,
|
||||
onClick = onMessageClick,
|
||||
onLongClick = onMessageLongClick,
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ fun TimelineItemStateEventRow(
|
|||
TimelineItemEventContentView(
|
||||
content = event.content,
|
||||
isMine = event.isMine,
|
||||
isEditable = event.isEditable,
|
||||
interactionSource = interactionSource,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import io.element.android.libraries.architecture.Presenter
|
|||
fun TimelineItemEventContentView(
|
||||
content: TimelineItemEventContent,
|
||||
isMine: Boolean,
|
||||
isEditable: Boolean,
|
||||
interactionSource: MutableInteractionSource,
|
||||
extraPadding: ExtraPadding,
|
||||
onClick: () -> Unit,
|
||||
|
|
@ -103,6 +104,7 @@ fun TimelineItemEventContentView(
|
|||
is TimelineItemPollContent -> TimelineItemPollView(
|
||||
content = content,
|
||||
isMine = isMine,
|
||||
isEditable = isEditable,
|
||||
eventSink = eventSink,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
fun TimelineItemPollView(
|
||||
content: TimelineItemPollContent,
|
||||
isMine: Boolean,
|
||||
isEditable: Boolean,
|
||||
eventSink: (TimelineEvents) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -43,15 +44,20 @@ fun TimelineItemPollView(
|
|||
eventSink(TimelineEvents.PollEndClicked(pollStartId))
|
||||
}
|
||||
|
||||
fun onPollEdit(pollStartId: EventId) {
|
||||
eventSink(TimelineEvents.PollEditClicked(pollStartId))
|
||||
}
|
||||
|
||||
PollContentView(
|
||||
eventId = content.eventId,
|
||||
question = content.question,
|
||||
answerItems = content.answerItems.toImmutableList(),
|
||||
pollKind = content.pollKind,
|
||||
isPollEnded = content.isEnded,
|
||||
isPollEditable = isEditable,
|
||||
isMine = isMine,
|
||||
onAnswerSelected = ::onAnswerSelected,
|
||||
onPollEdit = {}, // TODO Polls: Wire up this callback once poll edit screen is done.
|
||||
onPollEdit = ::onPollEdit,
|
||||
onPollEnd = ::onPollEnd,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
|
@ -64,6 +70,7 @@ internal fun TimelineItemPollViewPreview(@PreviewParameter(TimelineItemPollConte
|
|||
TimelineItemPollView(
|
||||
content = content,
|
||||
isMine = false,
|
||||
isEditable = false,
|
||||
eventSink = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -75,6 +82,7 @@ internal fun TimelineItemPollCreatorViewPreview(@PreviewParameter(TimelineItemPo
|
|||
TimelineItemPollView(
|
||||
content = content,
|
||||
isMine = true,
|
||||
isEditable = false,
|
||||
eventSink = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ class TimelineItemEventFactory @Inject constructor(
|
|||
senderAvatar = senderAvatarData,
|
||||
content = contentFactory.create(currentTimelineItem.event),
|
||||
isMine = currentTimelineItem.event.isOwn,
|
||||
isEditable = currentTimelineItem.event.isEditable,
|
||||
sentTime = sentTime,
|
||||
groupPosition = groupPosition,
|
||||
reactionsState = currentTimelineItem.computeReactionsState(),
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ sealed interface TimelineItem {
|
|||
val content: TimelineItemEventContent,
|
||||
val sentTime: String = "",
|
||||
val isMine: Boolean = false,
|
||||
val isEditable: Boolean,
|
||||
val groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None,
|
||||
val reactionsState: TimelineItemReactions,
|
||||
val readReceiptState: TimelineItemReadReceipts,
|
||||
|
|
|
|||
|
|
@ -18,10 +18,6 @@ package io.element.android.features.messages.impl.timeline.model.event
|
|||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.location.api.Location
|
||||
import io.element.android.features.poll.api.PollAnswerItem
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.poll.PollAnswer
|
||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||
|
||||
open class TimelineItemLocationContentProvider : PreviewParameterProvider<TimelineItemLocationContent> {
|
||||
override val values: Sequence<TimelineItemLocationContent>
|
||||
|
|
@ -41,31 +37,3 @@ fun aTimelineItemLocationContent(description: String? = null) = TimelineItemLoca
|
|||
description = description,
|
||||
)
|
||||
|
||||
fun aTimelineItemPollContent(
|
||||
isEnded: Boolean = false,
|
||||
) = TimelineItemPollContent(
|
||||
eventId = EventId("\$anEventId"),
|
||||
question = "Some question?",
|
||||
answerItems = listOf(
|
||||
PollAnswerItem(
|
||||
answer = PollAnswer("id_1", "Answer1"),
|
||||
isSelected = false,
|
||||
isEnabled = false,
|
||||
isWinner = false,
|
||||
isDisclosed = false,
|
||||
votesCount = 0,
|
||||
percentage = 0.0f,
|
||||
),
|
||||
PollAnswerItem(
|
||||
answer = PollAnswer("id_2", "Answer2"),
|
||||
isSelected = false,
|
||||
isEnabled = false,
|
||||
isWinner = false,
|
||||
isDisclosed = false,
|
||||
votesCount = 0,
|
||||
percentage = 0.0f,
|
||||
),
|
||||
),
|
||||
pollKind = PollKind.Disclosed,
|
||||
isEnded = isEnded,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@
|
|||
package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.poll.api.PollAnswerItem
|
||||
import io.element.android.features.poll.api.aPollAnswerItemList
|
||||
import io.element.android.features.poll.api.aPollQuestion
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||
|
||||
|
|
@ -29,12 +31,16 @@ open class TimelineItemPollContentProvider : PreviewParameterProvider<TimelineIt
|
|||
)
|
||||
}
|
||||
|
||||
fun aTimelineItemPollContent(): TimelineItemPollContent {
|
||||
fun aTimelineItemPollContent(
|
||||
question: String = aPollQuestion(),
|
||||
answerItems: List<PollAnswerItem> = aPollAnswerItemList(),
|
||||
isEnded: Boolean = false,
|
||||
): TimelineItemPollContent {
|
||||
return TimelineItemPollContent(
|
||||
eventId = EventId("\$anEventId"),
|
||||
pollKind = PollKind.Disclosed,
|
||||
question = "What type of food should we have at the party?",
|
||||
answerItems = aPollAnswerItemList(),
|
||||
isEnded = false,
|
||||
question = question,
|
||||
answerItems = answerItems,
|
||||
isEnded = isEnded,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue