mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-21 17:05:39 +03:00
View all threads screen implementation & UI
Add user friendly message thread summary on the SDK side Fix not encrypted rooms thread summaries
This commit is contained in:
parent
586b3d8caa
commit
722f367690
28 changed files with 654 additions and 186 deletions
|
@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
||||
|
@ -98,6 +99,13 @@ class FlowRoom(private val room: Room) {
|
|||
fun liveNotificationState(): Flow<RoomNotificationState> {
|
||||
return room.getLiveRoomNotificationState().asFlow()
|
||||
}
|
||||
|
||||
fun liveThreadList(): Flow<List<TimelineEvent>> {
|
||||
return room.getAllThreadsLive().asFlow()
|
||||
.startWith(room.coroutineDispatchers.io) {
|
||||
room.getAllThreads()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Room.flow(): FlowRoom {
|
||||
|
|
|
@ -27,11 +27,13 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
|||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.api.session.threads.ThreadDetails
|
||||
import org.matrix.android.sdk.api.util.ContentUtils
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.session.presence.model.PresenceContent
|
||||
import org.matrix.android.sdk.internal.session.room.send.removeInReplyFallbacks
|
||||
import timber.log.Timber
|
||||
|
||||
typealias Content = JsonDict
|
||||
|
@ -188,14 +190,39 @@ data class Event(
|
|||
return contentMap?.let { JSONObject(adapter.toJson(it)).toString(4) }
|
||||
}
|
||||
|
||||
fun getDecryptedMessageText(): String {
|
||||
return getValueFromPayload(mxDecryptionResult?.payload).orEmpty()
|
||||
/**
|
||||
* Returns a user friendly content depending on the message type.
|
||||
* It can be used especially for message summaries.
|
||||
* It will return a decrypted text message or an empty string otherwise.
|
||||
*/
|
||||
fun getDecryptedUserFriendlyTextSummary(): String {
|
||||
val text = getDecryptedValue().orEmpty()
|
||||
return when {
|
||||
isReply() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text)
|
||||
isFileMessage() -> "sent a file."
|
||||
isAudioMessage() -> "sent an audio file."
|
||||
isImageMessage() -> "sent an image."
|
||||
isVideoMessage() -> "sent a video."
|
||||
else -> text
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun getValueFromPayload(payload: JsonDict?, key: String = "body"): String? {
|
||||
val content = payload?.get("content") as? JsonDict
|
||||
return content?.get(key) as? String
|
||||
private fun Event.isQuote(): Boolean {
|
||||
if (isReply()) return false
|
||||
return getDecryptedValue("formatted_body")?.contains("<blockquote>") ?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the message, or return the pure payload value if there is no encryption
|
||||
*/
|
||||
private fun getDecryptedValue(key: String = "body"): String? {
|
||||
return if (isEncrypted()) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val content = mxDecryptionResult?.payload?.get("content") as? JsonDict
|
||||
content?.get(key) as? String
|
||||
} else {
|
||||
content?.get(key) as? String
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -55,4 +55,17 @@ interface TimelineService {
|
|||
* Returns a snapshot list of TimelineEvent with EventType.MESSAGE and MessageType.MSGTYPE_IMAGE or MessageType.MSGTYPE_VIDEO.
|
||||
*/
|
||||
fun getAttachmentMessages(): List<TimelineEvent>
|
||||
|
||||
/**
|
||||
* Get a live list of all the thread for the specified roomId
|
||||
* @return the [LiveData] of [TimelineEvent]
|
||||
*/
|
||||
fun getAllThreadsLive(): LiveData<List<TimelineEvent>>
|
||||
|
||||
/**
|
||||
* Get a list of all the thread for the specified roomId
|
||||
* @return the [LiveData] of [TimelineEvent]
|
||||
*/
|
||||
fun getAllThreads(): List<TimelineEvent>
|
||||
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.matrix.android.sdk.internal.database.helper
|
||||
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.RealmResults
|
||||
import io.realm.Sort
|
||||
import org.matrix.android.sdk.BuildConfig
|
||||
|
@ -82,7 +83,17 @@ internal fun EventEntity.findAllThreadsForRootEventId(realm: Realm, rootThreadEv
|
|||
TimelineEventEntity
|
||||
.whereRoomId(realm, roomId = roomId)
|
||||
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId)
|
||||
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
|
||||
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
||||
.findAll()
|
||||
|
||||
|
||||
/**
|
||||
* Find all TimelineEventEntity that are root threads for the specified room
|
||||
* @param roomId The room that all stored root threads will be returned
|
||||
*/
|
||||
internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery<TimelineEventEntity> =
|
||||
TimelineEventEntity
|
||||
.whereRoomId(realm, roomId = roomId)
|
||||
.equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD,true)
|
||||
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
||||
|
||||
|
|
|
@ -22,11 +22,9 @@ import org.matrix.android.sdk.api.session.events.model.Event
|
|||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.UnsignedData
|
||||
import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId
|
||||
import org.matrix.android.sdk.api.session.events.model.isThread
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||
import org.matrix.android.sdk.api.session.threads.ThreadDetails
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
|
@ -113,7 +111,7 @@ internal object EventMapper {
|
|||
avatarUrl = timelineEventEntity.senderAvatar
|
||||
)
|
||||
},
|
||||
threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedMessageText().orEmpty()
|
||||
threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedUserFriendlyTextSummary().orEmpty()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,7 +168,11 @@ internal class DefaultTimeline(
|
|||
TimelineEventEntity
|
||||
.whereRoomId(realm, roomId = roomId)
|
||||
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, it)
|
||||
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
|
||||
.or()
|
||||
.equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, rootThreadEventId)
|
||||
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
||||
.findAll()
|
||||
|
||||
} ?: buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
|
||||
|
||||
timelineEvents.addChangeListener(eventsChangeListener)
|
||||
|
|
|
@ -31,9 +31,11 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
|||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId
|
||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||
|
@ -102,4 +104,17 @@ internal class DefaultTimelineService @AssistedInject constructor(
|
|||
.orEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAllThreadsLive(): LiveData<List<TimelineEvent>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
||||
{ timelineEventMapper.map(it) }
|
||||
)
|
||||
}
|
||||
override fun getAllThreads(): List<TimelineEvent> {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
||||
{ timelineEventMapper.map(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/coordinatorLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -58,7 +58,7 @@ import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
|
|||
import im.vector.app.features.home.room.detail.TimelineFragment
|
||||
import im.vector.app.features.home.room.detail.search.SearchFragment
|
||||
import im.vector.app.features.home.room.list.RoomListFragment
|
||||
import im.vector.app.features.home.room.threads.detail.ThreadListFragment
|
||||
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
|
||||
import im.vector.app.features.login.LoginCaptchaFragment
|
||||
import im.vector.app.features.login.LoginFragment
|
||||
import im.vector.app.features.login.LoginGenericTextInputFormFragment
|
||||
|
|
|
@ -68,6 +68,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||
import com.jakewharton.rxbinding3.view.focusChanges
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import com.vanniktech.emoji.EmojiPopup
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.dialogs.ConfirmationDialogBuilder
|
||||
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
|
||||
|
@ -971,7 +972,7 @@ class TimelineFragment @Inject constructor(
|
|||
true
|
||||
}
|
||||
R.id.threads -> {
|
||||
requireActivity().toast("View All Threads")
|
||||
navigateToThreadList()
|
||||
true
|
||||
}
|
||||
R.id.search -> {
|
||||
|
@ -1776,7 +1777,7 @@ class TimelineFragment @Inject constructor(
|
|||
roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId))
|
||||
}
|
||||
}
|
||||
if (isRootThreadEvent) {
|
||||
if (BuildConfig.THREADING_ENABLED && isRootThreadEvent && !isThreadTimeLine()) {
|
||||
navigateToThreadTimeline(informationData.eventId)
|
||||
}
|
||||
}
|
||||
|
@ -2136,8 +2137,8 @@ class TimelineFragment @Inject constructor(
|
|||
}
|
||||
|
||||
/**
|
||||
* Navigate to Threads timeline for the specified threadRootEventId
|
||||
* using the RoomThreadDetailActivity
|
||||
* Navigate to Threads timeline for the specified rootThreadEventId
|
||||
* using the ThreadsActivity
|
||||
*/
|
||||
|
||||
private fun navigateToThreadTimeline(rootThreadEventId: String) {
|
||||
|
@ -2151,6 +2152,21 @@ class TimelineFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to Threads list for the current room
|
||||
* using the ThreadsActivity
|
||||
*/
|
||||
|
||||
private fun navigateToThreadList() {
|
||||
context?.let {
|
||||
val roomThreadDetailArgs = ThreadTimelineArgs(
|
||||
roomId = timelineArgs.roomId,
|
||||
displayName = roomDetailViewModel.getRoomSummary()?.displayName,
|
||||
avatarUrl = roomDetailViewModel.getRoomSummary()?.avatarUrl)
|
||||
navigator.openThreadList(it, roomThreadDetailArgs)
|
||||
}
|
||||
}
|
||||
|
||||
// VectorInviteView.Callback
|
||||
|
||||
override fun onAcceptInvite() {
|
||||
|
|
|
@ -27,6 +27,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
|
|||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
|
@ -105,14 +106,17 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||
holder.eventSendingIndicator.isVisible = attributes.informationData.sendStateDecoration == SendStateDecoration.SENDING_MEDIA
|
||||
|
||||
// Threads
|
||||
attributes.threadDetails?.let { threadDetails ->
|
||||
threadDetails.isRootThread
|
||||
holder.threadSummaryConstraintLayout.isVisible = threadDetails.isRootThread
|
||||
holder.threadSummaryCounterTextView.text = threadDetails.numberOfThreads.toString()
|
||||
holder.threadSummaryInfoTextView.text = threadDetails.threadSummaryLatestTextMessage
|
||||
threadDetails.threadSummarySenderInfo?.let { senderInfo ->
|
||||
attributes.avatarRenderer.render(MatrixItem.UserItem(senderInfo.userId, senderInfo.displayName, senderInfo.avatarUrl), holder.threadSummaryAvatarImageView)
|
||||
if(BuildConfig.THREADING_ENABLED) {
|
||||
attributes.threadDetails?.let { threadDetails ->
|
||||
holder.threadSummaryConstraintLayout.isVisible = threadDetails.isRootThread
|
||||
holder.threadSummaryCounterTextView.text = threadDetails.numberOfThreads.toString()
|
||||
holder.threadSummaryInfoTextView.text = threadDetails.threadSummaryLatestTextMessage
|
||||
threadDetails.threadSummarySenderInfo?.let { senderInfo ->
|
||||
attributes.avatarRenderer.render(MatrixItem.UserItem(senderInfo.userId, senderInfo.displayName, senderInfo.avatarUrl), holder.threadSummaryAvatarImageView)
|
||||
}
|
||||
}
|
||||
}else{
|
||||
holder.threadSummaryConstraintLayout.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,14 +25,13 @@ import im.vector.app.core.di.ScreenComponent
|
|||
import im.vector.app.core.extensions.replaceFragment
|
||||
import im.vector.app.core.platform.ToolbarConfigurable
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
import im.vector.app.databinding.ActivityThreadsBinding
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
|
||||
import im.vector.app.features.home.room.detail.TimelineFragment
|
||||
import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
|
||||
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
|
||||
import im.vector.app.features.home.room.threads.detail.ThreadListFragment
|
||||
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
|
||||
import javax.inject.Inject
|
||||
|
||||
class ThreadsActivity : VectorBaseActivity<ActivityThreadsBinding>(), ToolbarConfigurable {
|
||||
|
|
|
@ -21,5 +21,7 @@ import kotlinx.parcelize.Parcelize
|
|||
|
||||
@Parcelize
|
||||
data class ThreadListArgs(
|
||||
val roomId: String
|
||||
val roomId: String,
|
||||
val displayName: String?,
|
||||
val avatarUrl: String?,
|
||||
) : Parcelable
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 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.home.room.threads.detail
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.airbnb.mvrx.args
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentThreadListBinding
|
||||
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import javax.inject.Inject
|
||||
|
||||
class ThreadListFragment @Inject constructor(
|
||||
private val session: Session
|
||||
) : VectorBaseFragment<FragmentThreadListBinding>() {
|
||||
|
||||
private val threadTimelineArgs: ThreadTimelineArgs by args()
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentThreadListBinding {
|
||||
return FragmentThreadListBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initTextComposer()
|
||||
// lifecycleScope.launch(Dispatchers.IO) {
|
||||
// Realm.getInstance(realmConfiguration).executeTransaction {
|
||||
// val eventId = roomThreadDetailArgs.eventId ?: return@executeTransaction
|
||||
// val r = EventEntity.where(it, eventId = eventId)
|
||||
// .findFirst() ?: return@executeTransaction
|
||||
// Timber.i("------> $eventId isThread: ${EventMapper.map(r).isThread()}")
|
||||
// }
|
||||
// }
|
||||
//// views.testTextVeiwddasda.text = "${roomThreadDetailArgs.eventId} -- ${roomThreadDetailArgs.roomId}"
|
||||
}
|
||||
|
||||
private fun initTextComposer(){
|
||||
// views.roomThreadDetailTextComposerView.views.sendButton.isVisible = true
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright 2019 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.home.room.threads.list.model
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.features.displayname.getBestName
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_thread_summary)
|
||||
abstract class ThreadSummaryModel : VectorEpoxyModel<ThreadSummaryModel.Holder>() {
|
||||
|
||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||
@EpoxyAttribute lateinit var title: String
|
||||
@EpoxyAttribute lateinit var date: String
|
||||
@EpoxyAttribute lateinit var rootMessage: String
|
||||
@EpoxyAttribute lateinit var lastMessage: String
|
||||
@EpoxyAttribute lateinit var lastMessageCounter: String
|
||||
@EpoxyAttribute lateinit var lastMessageMatrixItem: MatrixItem
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||
holder.avatarImageView.contentDescription = matrixItem.getBestName()
|
||||
holder.titleTextView.text = title
|
||||
holder.dateTextView.text = date
|
||||
holder.rootMessageTextView.text = rootMessage
|
||||
|
||||
// Last message summary
|
||||
avatarRenderer.render(lastMessageMatrixItem, holder.lastMessageAvatarImageView)
|
||||
holder.lastMessageAvatarImageView.contentDescription = lastMessageMatrixItem.getBestName()
|
||||
holder.lastMessageTextView.text = lastMessage
|
||||
holder.lastMessageCounterTextView.text = lastMessageCounter
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val avatarImageView by bind<ImageView>(R.id.threadSummaryAvatarImageView)
|
||||
val titleTextView by bind<TextView>(R.id.threadSummaryTitleTextView)
|
||||
val dateTextView by bind<TextView>(R.id.threadSummaryDateTextView)
|
||||
val rootMessageTextView by bind<TextView>(R.id.threadSummaryRootMessageTextView)
|
||||
val lastMessageAvatarImageView by bind<ImageView>(R.id.messageThreadSummaryAvatarImageView)
|
||||
val lastMessageCounterTextView by bind<TextView>(R.id.messageThreadSummaryCounterTextView)
|
||||
val lastMessageTextView by bind<TextView>(R.id.messageThreadSummaryInfoTextView)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2020 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.home.room.threads.list.viewmodel
|
||||
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.threads.list.model.threadSummary
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class ThreadSummaryController @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer
|
||||
) : EpoxyController() {
|
||||
|
||||
var listener: Listener? = null
|
||||
|
||||
private var viewState: ThreadSummaryViewState? = null
|
||||
|
||||
init {
|
||||
// We are requesting a model build directly as the first build of epoxy is on the main thread.
|
||||
// It avoids to build the whole list of breadcrumbs on the main thread.
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
fun update(viewState: ThreadSummaryViewState) {
|
||||
this.viewState = viewState
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
override fun buildModels() {
|
||||
val safeViewState = viewState ?: return
|
||||
val host = this
|
||||
// Add a ZeroItem to avoid automatic scroll when the breadcrumbs are updated from another client
|
||||
// zeroItem {
|
||||
// id("top")
|
||||
// }
|
||||
|
||||
// An empty breadcrumbs list can only be temporary because when entering in a room,
|
||||
// this one is added to the breadcrumbs
|
||||
safeViewState.rootThreadEventList.invoke()
|
||||
?.forEach { timelineEvent ->
|
||||
threadSummary {
|
||||
id(timelineEvent.eventId)
|
||||
avatarRenderer(host.avatarRenderer)
|
||||
matrixItem(timelineEvent.senderInfo.toMatrixItem())
|
||||
title(timelineEvent.senderInfo.displayName)
|
||||
date(timelineEvent.root.ageLocalTs.toString())
|
||||
rootMessage(timelineEvent.root.getDecryptedUserFriendlyTextSummary())
|
||||
lastMessage(timelineEvent.root.threadDetails?.threadSummaryLatestTextMessage.orEmpty())
|
||||
lastMessageCounter(timelineEvent.root.threadDetails?.numberOfThreads.toString())
|
||||
lastMessageMatrixItem(timelineEvent.root.threadDetails?.threadSummarySenderInfo?.toMatrixItem())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onBreadcrumbClicked(roomId: String)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2020 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.home.room.threads.list.viewmodel
|
||||
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.platform.EmptyAction
|
||||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
|
||||
class ThreadSummaryViewModel @AssistedInject constructor(@Assisted val initialState: ThreadSummaryViewState,
|
||||
private val session: Session) :
|
||||
VectorViewModel<ThreadSummaryViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
private val room = session.getRoom(initialState.roomId)
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(initialState: ThreadSummaryViewState): ThreadSummaryViewModel
|
||||
}
|
||||
|
||||
companion object : MavericksViewModelFactory<ThreadSummaryViewModel, ThreadSummaryViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: ThreadSummaryViewState): ThreadSummaryViewModel? {
|
||||
val fragment: ThreadListFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
return fragment.threadSummaryViewModelFactory.create(state)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
observeThreadsSummary()
|
||||
}
|
||||
|
||||
override fun handle(action: EmptyAction) {
|
||||
// No op
|
||||
}
|
||||
|
||||
|
||||
private fun observeThreadsSummary() {
|
||||
room?.flow()
|
||||
?.liveThreadList()
|
||||
?.execute { asyncThreads ->
|
||||
copy(rootThreadEventList = asyncThreads)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2020 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.home.room.threads.list.viewmodel
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
|
||||
data class ThreadSummaryViewState(
|
||||
val rootThreadEventList: Async<List<TimelineEvent>> = Uninitialized,
|
||||
val roomId: String
|
||||
) : MavericksState{
|
||||
|
||||
constructor(args: ThreadListArgs) : this(roomId = args.roomId)
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.home.room.threads.list.views
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentThreadListBinding
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsAnimator
|
||||
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsViewModel
|
||||
import im.vector.app.features.home.room.detail.RoomDetailSharedActionViewModel
|
||||
import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
|
||||
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadSummaryController
|
||||
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadSummaryViewModel
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class ThreadListFragment @Inject constructor(
|
||||
private val session: Session,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val threadSummaryController: ThreadSummaryController,
|
||||
val threadSummaryViewModelFactory: ThreadSummaryViewModel.Factory
|
||||
) : VectorBaseFragment<FragmentThreadListBinding>() {
|
||||
|
||||
private val threadSummaryViewModel: ThreadSummaryViewModel by fragmentViewModel()
|
||||
|
||||
private val threadListArgs: ThreadListArgs by args()
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentThreadListBinding {
|
||||
return FragmentThreadListBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun getMenuRes() = R.menu.menu_thread_list
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initToolbar()
|
||||
views.threadListRecyclerView.configureWith(threadSummaryController, BreadcrumbsAnimator(), hasFixedSize = false)
|
||||
// threadSummaryController.listener = this
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
views.threadListRecyclerView.cleanup()
|
||||
// breadcrumbsController.listener = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
private fun initToolbar(){
|
||||
setupToolbar(views.threadListToolbar)
|
||||
renderToolbar()
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(threadSummaryViewModel) { state ->
|
||||
threadSummaryController.update(state)
|
||||
}
|
||||
|
||||
private fun renderToolbar() {
|
||||
views.includeThreadListToolbar.roomToolbarThreadConstraintLayout.isVisible = true
|
||||
val matrixItem = MatrixItem.RoomItem(threadListArgs.roomId, threadListArgs.displayName, threadListArgs.avatarUrl)
|
||||
avatarRenderer.render(matrixItem, views.includeThreadListToolbar.roomToolbarThreadImageView)
|
||||
views.includeThreadListToolbar.roomToolbarThreadSubtitleTextView.text = threadListArgs.displayName
|
||||
}
|
||||
}
|
|
@ -517,4 +517,14 @@ class DefaultNavigator @Inject constructor(
|
|||
threadTimelineArgs = threadTimelineArgs,
|
||||
threadListArgs =null))
|
||||
}
|
||||
override fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs) {
|
||||
context.startActivity(ThreadsActivity.newIntent(
|
||||
context = context,
|
||||
threadTimelineArgs = null,
|
||||
threadListArgs = ThreadListArgs(
|
||||
roomId = threadTimelineArgs.roomId,
|
||||
displayName = threadTimelineArgs.displayName,
|
||||
avatarUrl = threadTimelineArgs.avatarUrl
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,5 +143,6 @@ interface Navigator {
|
|||
fun openCallTransfer(context: Context, callId: String)
|
||||
|
||||
fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs)
|
||||
fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs)
|
||||
|
||||
}
|
||||
|
|
40
vector/src/main/res/layout/fragment_thread_list.xml
Normal file
40
vector/src/main/res/layout/fragment_thread_list.xml
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?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="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/threadListAppBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/threadListToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:transitionName="toolbar">
|
||||
|
||||
<include
|
||||
android:id="@+id/includeThreadListToolbar"
|
||||
layout="@layout/view_room_detail_thread_toolbar" />
|
||||
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/threadListRecyclerView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/threadListAppBarLayout"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:background="?android:colorBackground"
|
||||
tools:listitem="@layout/item_thread_summary" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
91
vector/src/main/res/layout/item_thread_summary.xml
Normal file
91
vector/src/main/res/layout/item_thread_summary.xml
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="0dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/threadSummaryAvatarImageView"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:contentDescription="@string/avatar"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@sample/user_round_avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/threadSummaryTitleTextView"
|
||||
style="@style/Widget.Vector.TextView.Body.Medium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/element_name_04"
|
||||
app:layout_constraintEnd_toStartOf="@id/threadSummaryDateTextView"
|
||||
app:layout_constraintStart_toEndOf="@id/threadSummaryAvatarImageView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Aris" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/threadSummaryDateTextView"
|
||||
style="@style/Widget.Vector.TextView.Caption"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?vctr_content_secondary"
|
||||
app:layout_constraintBottom_toBottomOf="@id/threadSummaryTitleTextView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/threadSummaryTitleTextView"
|
||||
tools:text="10 minutes ago" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/threadSummaryRootMessageTextView"
|
||||
style="@style/Widget.Vector.TextView.Body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textColor="?vctr_content_primary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/threadSummaryTitleTextView"
|
||||
app:layout_constraintTop_toBottomOf="@id/threadSummaryTitleTextView"
|
||||
tools:text="It’s really encouraging to feel like you are a part of something greater than yourself. I It’s really encouraging to feel like you are a part of something greater than yourself. I " />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/threadSummaryConstraintLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:contentDescription="@string/room_threads_filter"
|
||||
android:maxWidth="496dp"
|
||||
android:minWidth="144dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/threadSummaryTitleTextView"
|
||||
app:layout_constraintTop_toBottomOf="@id/threadSummaryRootMessageTextView"
|
||||
tools:visibility="visible">
|
||||
|
||||
<include layout="@layout/view_thread_room_summary" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:background="?vctr_content_quinary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/threadSummaryConstraintLayout"
|
||||
app:layout_constraintTop_toBottomOf="@id/threadSummaryConstraintLayout" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -200,7 +200,27 @@
|
|||
</com.google.android.flexbox.FlexboxLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
layout="@layout/view_thread_room_summary" />
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/messageThreadSummaryConstraintLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/informationBottom"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:paddingStart="13dp"
|
||||
android:paddingEnd="13dp"
|
||||
android:layout_toEndOf="@id/messageStartGuideline"
|
||||
android:background="@drawable/rounded_rect_shape_8"
|
||||
android:contentDescription="@string/room_threads_filter"
|
||||
android:maxWidth="496dp"
|
||||
android:minWidth="144dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<include layout="@layout/view_thread_room_summary" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</RelativeLayout>
|
|
@ -1,74 +1,58 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/messageThreadSummaryConstraintLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/informationBottom"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_toEndOf="@id/messageStartGuideline"
|
||||
android:background="@drawable/rounded_rect_shape_8"
|
||||
android:contentDescription="@string/room_threads_filter"
|
||||
android:maxWidth="496dp"
|
||||
android:minWidth="144dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageThreadSummaryImageView"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginStart="13dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:contentDescription="@string/room_threads_filter"
|
||||
android:src="@drawable/ic_thread_summary" />
|
||||
android:src="@drawable/ic_thread_summary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageThreadSummaryCounterTextView"
|
||||
style="@style/Widget.Vector.TextView.Caption"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/messageThreadSummaryImageView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:textColor="?vctr_content_secondary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/messageThreadSummaryImageView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="187" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageThreadSummaryAvatarImageView"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/messageThreadSummaryCounterTextView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="13dp"
|
||||
android:contentDescription="@string/avatar"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/messageThreadSummaryCounterTextView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@sample/user_round_avatars" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageThreadSummaryInfoTextView"
|
||||
style="@style/Widget.Vector.TextView.Caption"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?vctr_content_secondary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toEndOf="@id/messageThreadSummaryAvatarImageView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_default="wrap"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="13dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?vctr_content_secondary"
|
||||
tools:text="Hello There, whats up! Its a large centence" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
tools:text="Hello There, whats up! Its a large centence, whats up! Its a large centence" />
|
||||
</merge>
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_filter"
|
||||
android:icon="@drawable/ic_filter"
|
||||
android:title="@string/room_threads_filter"
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
13
vector/src/main/res/menu/menu_thread_list.xml
Normal file
13
vector/src/main/res/menu/menu_thread_list.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_thread_list_filter"
|
||||
android:icon="@drawable/ic_filter"
|
||||
android:title="@string/room_threads_filter"
|
||||
app:iconTint="?vctr_content_secondary"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
</menu>
|
|
@ -1,36 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/test"
|
||||
android:icon="@drawable/ic_search"
|
||||
android:title="@string/action_thread_view_in_room"
|
||||
app:showAsAction="always">
|
||||
|
||||
<menu>
|
||||
<item
|
||||
android:id="@+id/menuWithIconText"
|
||||
android:icon="@drawable/ic_thread_menu_item"
|
||||
android:title="@string/action_thread_share"
|
||||
/>
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_thread_timeline_view_in_room"
|
||||
android:icon="@drawable/ic_settings_x"
|
||||
android:title="@string/action_thread_view_in_room"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_thread_timeline_copy"
|
||||
android:title="@string/action_thread_copy_link_to_thread"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_thread_timeline_share"
|
||||
android:title="@string/action_thread_share"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
Loading…
Reference in a new issue