ganfra
10 months ago
31 changed files with 571 additions and 149 deletions
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.poll.api.actions |
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId |
||||
|
||||
interface EndPollAction { |
||||
suspend fun execute(pollStartId: EventId): Result<Unit> |
||||
} |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.poll.api.actions |
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId |
||||
|
||||
interface SendPollResponseAction { |
||||
suspend fun execute(pollStartId: EventId, answerId: String): Result<Unit> |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.poll.api.pollcontent |
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId |
||||
import io.element.android.libraries.matrix.api.poll.PollKind |
||||
import kotlinx.collections.immutable.ImmutableList |
||||
|
||||
data class PollContentState( |
||||
val eventId: EventId?, |
||||
val question: String, |
||||
val answerItems: ImmutableList<PollAnswerItem>, |
||||
val pollKind: PollKind, |
||||
val isPollEditable: Boolean, |
||||
val isPollEnded: Boolean, |
||||
val isMine: Boolean, |
||||
) |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.poll.api.pollcontent |
||||
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem |
||||
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent |
||||
|
||||
interface PollContentStateFactory { |
||||
suspend fun create(event: EventTimelineItem, content: PollContent): PollContentState |
||||
} |
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.poll.impl.actions |
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding |
||||
import im.vector.app.features.analytics.plan.PollEnd |
||||
import io.element.android.features.poll.api.actions.EndPollAction |
||||
import io.element.android.libraries.di.RoomScope |
||||
import io.element.android.libraries.matrix.api.core.EventId |
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom |
||||
import io.element.android.services.analytics.api.AnalyticsService |
||||
import javax.inject.Inject |
||||
|
||||
@ContributesBinding(RoomScope::class) |
||||
class DefaultEndPollAction @Inject constructor( |
||||
private val room: MatrixRoom, |
||||
private val analyticsService: AnalyticsService, |
||||
) : EndPollAction { |
||||
|
||||
override suspend fun execute(pollStartId: EventId): Result<Unit> { |
||||
return room.endPoll( |
||||
pollStartId = pollStartId, |
||||
text = "The poll with event id: $pollStartId has ended." |
||||
).onSuccess { |
||||
analyticsService.capture(PollEnd()) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.poll.impl.actions |
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding |
||||
import im.vector.app.features.analytics.plan.PollVote |
||||
import io.element.android.features.poll.api.actions.SendPollResponseAction |
||||
import io.element.android.libraries.di.RoomScope |
||||
import io.element.android.libraries.matrix.api.core.EventId |
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom |
||||
import io.element.android.services.analytics.api.AnalyticsService |
||||
import javax.inject.Inject |
||||
|
||||
@ContributesBinding(RoomScope::class) |
||||
class DefaultSendPollResponseAction @Inject constructor( |
||||
private val room: MatrixRoom, |
||||
private val analyticsService: AnalyticsService, |
||||
) : SendPollResponseAction { |
||||
|
||||
override suspend fun execute(pollStartId: EventId, answerId: String): Result<Unit> { |
||||
return room.sendPollResponse( |
||||
pollStartId = pollStartId, |
||||
answers = listOf(answerId), |
||||
).onSuccess { |
||||
analyticsService.capture(PollVote()) |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.poll.impl.history |
||||
|
||||
import io.element.android.features.poll.api.pollcontent.PollContentState |
||||
|
||||
sealed interface PollHistoryItem { |
||||
data class PollContent(val formattedDate: String, val state: PollContentState) : PollHistoryItem |
||||
} |
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.poll.impl.history |
||||
|
||||
import io.element.android.features.poll.api.pollcontent.PollContentStateFactory |
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers |
||||
import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter |
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem |
||||
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent |
||||
import kotlinx.coroutines.withContext |
||||
import javax.inject.Inject |
||||
|
||||
class PollHistoryItemsFactory @Inject constructor( |
||||
private val pollContentStateFactory: PollContentStateFactory, |
||||
private val daySeparatorFormatter: DaySeparatorFormatter, |
||||
private val dispatchers: CoroutineDispatchers, |
||||
) { |
||||
|
||||
suspend fun create(timelineItems: List<MatrixTimelineItem>): List<PollHistoryItem> = withContext(dispatchers.computation) { |
||||
timelineItems.mapNotNull { create(it) }.reversed() |
||||
} |
||||
|
||||
private suspend fun create(timelineItem: MatrixTimelineItem): PollHistoryItem? { |
||||
return when (timelineItem) { |
||||
is MatrixTimelineItem.Event -> { |
||||
val pollContent = timelineItem.event.content as? PollContent ?: return null |
||||
val pollContentState = pollContentStateFactory.create(timelineItem.event, pollContent) |
||||
PollHistoryItem.PollContent( |
||||
formattedDate = daySeparatorFormatter.format(timelineItem.event.timestamp), |
||||
state = pollContentState |
||||
) |
||||
} |
||||
else -> null |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.poll.impl.model |
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding |
||||
import io.element.android.features.poll.api.pollcontent.PollAnswerItem |
||||
import io.element.android.features.poll.api.pollcontent.PollContentState |
||||
import io.element.android.features.poll.api.pollcontent.PollContentStateFactory |
||||
import io.element.android.libraries.di.RoomScope |
||||
import io.element.android.libraries.matrix.api.MatrixClient |
||||
import io.element.android.libraries.matrix.api.poll.isDisclosed |
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem |
||||
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent |
||||
import kotlinx.collections.immutable.toImmutableList |
||||
import javax.inject.Inject |
||||
|
||||
@ContributesBinding(RoomScope::class) |
||||
class DefaultPollContentStateFactory @Inject constructor( |
||||
private val matrixClient: MatrixClient, |
||||
) : PollContentStateFactory { |
||||
|
||||
override suspend fun create( |
||||
event: EventTimelineItem, |
||||
content: PollContent |
||||
): PollContentState { |
||||
val totalVoteCount = content.votes.flatMap { it.value }.size |
||||
val myVotes = content.votes.filter { matrixClient.sessionId in it.value }.keys |
||||
val isPollEnded = content.endTime != null |
||||
val winnerIds = if (!isPollEnded) { |
||||
emptyList() |
||||
} else { |
||||
content.answers |
||||
.map { answer -> answer.id } |
||||
.groupBy { answerId -> content.votes[answerId]?.size ?: 0 } // Group by votes count |
||||
.maxByOrNull { (votes, _) -> votes } // Keep max voted answers |
||||
?.takeIf { (votes, _) -> votes > 0 } // Ignore if no option has been voted |
||||
?.value |
||||
.orEmpty() |
||||
} |
||||
val answerItems = content.answers.map { answer -> |
||||
val answerVoteCount = content.votes[answer.id]?.size ?: 0 |
||||
val isSelected = answer.id in myVotes |
||||
val isWinner = answer.id in winnerIds |
||||
val percentage = if (totalVoteCount > 0) answerVoteCount.toFloat() / totalVoteCount.toFloat() else 0f |
||||
PollAnswerItem( |
||||
answer = answer, |
||||
isSelected = isSelected, |
||||
isEnabled = !isPollEnded, |
||||
isWinner = isWinner, |
||||
isDisclosed = content.kind.isDisclosed || isPollEnded, |
||||
votesCount = answerVoteCount, |
||||
percentage = percentage, |
||||
) |
||||
} |
||||
|
||||
return PollContentState( |
||||
eventId = event.eventId, |
||||
isMine = event.isOwn, |
||||
question = content.question, |
||||
answerItems = answerItems.toImmutableList(), |
||||
pollKind = content.kind, |
||||
isPollEditable = event.isEditable, |
||||
isPollEnded = isPollEnded, |
||||
) |
||||
} |
||||
} |
Loading…
Reference in new issue