Adding go to timeline event button

This commit is contained in:
Maxime NATUREL 2023-02-02 15:55:43 +01:00
parent eaa9cc740e
commit 384e7f674d
9 changed files with 203 additions and 10 deletions

View File

@ -3207,6 +3207,7 @@
<string name="room_polls_wait_for_display">Displaying polls</string> <string name="room_polls_wait_for_display">Displaying polls</string>
<string name="room_polls_load_more">Load more polls</string> <string name="room_polls_load_more">Load more polls</string>
<string name="room_polls_loading_error">Error fetching polls.</string> <string name="room_polls_loading_error">Error fetching polls.</string>
<string name="room_poll_details_go_to_timeline">View poll in timeline</string>
<!-- Location --> <!-- Location -->
<string name="location_activity_title_static_sharing">Share location</string> <string name="location_activity_title_static_sharing">Share location</string>

View File

@ -0,0 +1,38 @@
/*
* 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 im.vector.app.features.roomprofile.polls.detail.domain
import im.vector.app.core.di.ActiveSessionHolder
import org.matrix.android.sdk.api.session.events.model.RelationType
import org.matrix.android.sdk.api.session.events.model.isPollEnd
import javax.inject.Inject
// TODO add unit tests
class GetEndedPollEventIdUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder,
) {
fun execute(roomId: String, startPollEventId: String): String? {
return activeSessionHolder.getActiveSession()
.roomService()
.getRoom(roomId)
?.timelineService()
?.getTimelineEventsRelatedTo(RelationType.REFERENCE, startPollEventId)
?.find { it.root.isPollEnd() }
?.eventId
}
}

View File

@ -20,5 +20,6 @@ import im.vector.app.features.poll.PollItemViewState
data class RoomPollDetail( data class RoomPollDetail(
val isEnded: Boolean, val isEnded: Boolean,
val endedPollEventId: String?,
val pollItemViewState: PollItemViewState, val pollItemViewState: PollItemViewState,
) )

View File

@ -17,12 +17,14 @@
package im.vector.app.features.roomprofile.polls.detail.ui package im.vector.app.features.roomprofile.polls.detail.ui
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
class RoomPollDetailController @Inject constructor() : TypedEpoxyController<RoomPollDetailViewState>() { class RoomPollDetailController @Inject constructor() : TypedEpoxyController<RoomPollDetailViewState>() {
interface Callback { interface Callback {
fun vote(pollEventId: String, optionId: String) fun vote(pollEventId: String, optionId: String)
fun goToTimelineEvent(eventId: String)
} }
var callback: Callback? = null var callback: Callback? = null
@ -41,5 +43,17 @@ class RoomPollDetailController @Inject constructor() : TypedEpoxyController<Room
optionViewStates(pollItemViewState.optionViewStates.orEmpty()) optionViewStates(pollItemViewState.optionViewStates.orEmpty())
callback(host.callback) callback(host.callback)
} }
buildGoToTimelineItem(targetEventId = pollDetail.endedPollEventId ?: viewState.pollId)
}
private fun buildGoToTimelineItem(targetEventId: String) {
val host = this
roomPollGoToTimelineItem {
id(UUID.randomUUID().toString())
clickListener {
host.callback?.goToTimelineEvent(targetEventId)
}
}
} }
} }

View File

@ -31,6 +31,7 @@ import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentRoomPollDetailBinding import im.vector.app.databinding.FragmentRoomPollDetailBinding
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@Parcelize @Parcelize
@ -45,6 +46,7 @@ class RoomPollDetailFragment :
VectorBaseFragment<FragmentRoomPollDetailBinding>(), VectorBaseFragment<FragmentRoomPollDetailBinding>(),
RoomPollDetailController.Callback { RoomPollDetailController.Callback {
@Inject lateinit var viewNavigator: RoomPollDetailNavigator
@Inject lateinit var roomPollDetailController: RoomPollDetailController @Inject lateinit var roomPollDetailController: RoomPollDetailController
private val viewModel: RoomPollDetailViewModel by fragmentViewModel() private val viewModel: RoomPollDetailViewModel by fragmentViewModel()
@ -58,7 +60,6 @@ class RoomPollDetailFragment :
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupToolbar(isEnded = roomPollDetailArgs.isEnded) setupToolbar(isEnded = roomPollDetailArgs.isEnded)
setupDetailView() setupDetailView()
// TODO add link to go to timeline message + create a ViewNavigator
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -93,4 +94,12 @@ class RoomPollDetailFragment :
override fun vote(pollEventId: String, optionId: String) { override fun vote(pollEventId: String, optionId: String) {
viewModel.handle(RoomPollDetailAction.Vote(pollEventId = pollEventId, optionId = optionId)) viewModel.handle(RoomPollDetailAction.Vote(pollEventId = pollEventId, optionId = optionId))
} }
override fun goToTimelineEvent(eventId: String) = withState(viewModel) { state ->
viewNavigator.goToTimelineEvent(
context = requireContext(),
roomId = state.roomId,
eventId = eventId,
)
}
} }

View File

@ -19,6 +19,9 @@ package im.vector.app.features.roomprofile.polls.detail.ui
import im.vector.app.core.extensions.getVectorLastMessageContent import im.vector.app.core.extensions.getVectorLastMessageContent
import im.vector.app.features.home.room.detail.timeline.factory.PollItemViewStateFactory import im.vector.app.features.home.room.detail.timeline.factory.PollItemViewStateFactory
import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataFactory import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataFactory
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
import im.vector.app.features.roomprofile.polls.detail.domain.GetEndedPollEventIdUseCase
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import timber.log.Timber import timber.log.Timber
@ -28,6 +31,7 @@ import javax.inject.Inject
class RoomPollDetailMapper @Inject constructor( class RoomPollDetailMapper @Inject constructor(
private val pollResponseDataFactory: PollResponseDataFactory, private val pollResponseDataFactory: PollResponseDataFactory,
private val pollItemViewStateFactory: PollItemViewStateFactory, private val pollItemViewStateFactory: PollItemViewStateFactory,
private val getEndedPollEventIdUseCase: GetEndedPollEventIdUseCase,
) { ) {
fun map(timelineEvent: TimelineEvent): RoomPollDetail? { fun map(timelineEvent: TimelineEvent): RoomPollDetail? {
@ -36,16 +40,13 @@ class RoomPollDetailMapper @Inject constructor(
val content = timelineEvent.getVectorLastMessageContent() val content = timelineEvent.getVectorLastMessageContent()
val pollResponseData = pollResponseDataFactory.create(timelineEvent) val pollResponseData = pollResponseDataFactory.create(timelineEvent)
return if (eventId.isNotEmpty() && content is MessagePollContent) { return if (eventId.isNotEmpty() && content is MessagePollContent) {
// we assume poll message has been sent here val isPollEnded = pollResponseData?.isClosed.orFalse()
val pollItemViewState = pollItemViewStateFactory.create( val endedPollEventId = getEndedPollEventId(
pollContent = content, isPollEnded,
pollResponseData = pollResponseData, startPollEventId = eventId,
isSent = true, roomId = timelineEvent.roomId,
)
RoomPollDetail(
isEnded = pollResponseData?.isClosed == true,
pollItemViewState = pollItemViewState,
) )
convertToRoomPollDetail(content, pollResponseData, isPollEnded, endedPollEventId)
} else { } else {
Timber.w("missing mandatory info about poll event with id=$eventId") Timber.w("missing mandatory info about poll event with id=$eventId")
null null
@ -57,4 +58,35 @@ class RoomPollDetailMapper @Inject constructor(
} }
return result.getOrNull() return result.getOrNull()
} }
private fun convertToRoomPollDetail(
content: MessagePollContent,
pollResponseData: PollResponseData?,
isPollEnded: Boolean,
endedPollEventId: String?,
): RoomPollDetail {
// we assume the poll has been sent
val pollItemViewState = pollItemViewStateFactory.create(
pollContent = content,
pollResponseData = pollResponseData,
isSent = true,
)
return RoomPollDetail(
isEnded = isPollEnded,
pollItemViewState = pollItemViewState,
endedPollEventId = endedPollEventId,
)
}
private fun getEndedPollEventId(
isPollEnded: Boolean,
startPollEventId: String,
roomId: String,
): String? {
return if (isPollEnded) {
getEndedPollEventIdUseCase.execute(startPollEventId = startPollEventId, roomId = roomId)
} else {
null
}
}
} }

View File

@ -0,0 +1,36 @@
/*
* 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 im.vector.app.features.roomprofile.polls.detail.ui
import android.content.Context
import im.vector.app.features.navigation.Navigator
import javax.inject.Inject
// TODO add unit tests
class RoomPollDetailNavigator @Inject constructor(
private val navigator: Navigator,
) {
fun goToTimelineEvent(context: Context, roomId: String, eventId: String) {
navigator.openRoom(
context = context,
roomId = roomId,
eventId = eventId,
buildTask = true,
)
}
}

View File

@ -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 im.vector.app.features.roomprofile.polls.detail.ui
import android.widget.Button
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.onClick
@EpoxyModelClass
abstract class RoomPollGoToTimelineItem : VectorEpoxyModel<RoomPollGoToTimelineItem.Holder>(R.layout.item_poll_go_to_timeline) {
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var clickListener: ClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.goToTimelineButton.onClick(clickListener)
}
class Holder : VectorEpoxyHolder() {
val goToTimelineButton by bind<Button>(R.id.roomPollGoToTimeline)
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/roomPollGoToTimeline"
style="@style/Widget.Vector.Button.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="33dp"
android:layout_marginBottom="46dp"
android:padding="0dp"
android:text="@string/room_poll_details_go_to_timeline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>