Threads init commit

This commit is contained in:
ariskotsomitopoulos 2021-10-20 18:39:59 +03:00
parent aea22201c3
commit ab87937e5b
24 changed files with 619 additions and 1 deletions

View File

@ -28,6 +28,9 @@ object RelationType {
/** Lets you define an event which references an existing event.*/
const val REFERENCE = "m.reference"
/** Lets you define an event which is a reply to an existing event.*/
const val THREAD = "m.thread"
/** Lets you define an event which adds a response to an existing event.*/
const val RESPONSE = "org.matrix.response"
}

View File

@ -44,6 +44,9 @@ import org.matrix.android.sdk.api.util.Optional
* m.reference - lets you define an event which references an existing event.
* When aggregated, currently doesn't do anything special, but in future could bundle chains of references (i.e. threads).
* These are primarily intended for handling replies (and in future threads).
*
* m.thread - lets you define an event which is a thread reply to an existing event.
* When aggregated, returns the most thread event
*/
interface RelationService {
@ -123,4 +126,16 @@ interface RelationService {
* @return the LiveData of EventAnnotationsSummary
*/
fun getEventAnnotationsSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>>
/**
* Creates a thread reply for an existing timeline event
* The replyInThreadText can be a Spannable and contains special spans (MatrixItemSpan) that will be translated
* by the sdk into pills.
* @param eventToReplyInThread the event referenced by the thread reply
* @param replyInThreadText the reply text
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
*/
fun replyInThread(eventToReplyInThread: TimelineEvent,
replyInThreadText: CharSequence,
autoMarkdown: Boolean = false): Cancelable?
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.session.room.model.relation.threads
interface ThreadContent {
companion object {
const val MSG_TYPE_JSON_KEY = "msgtype"
}
val msgType: String
val body: String
val relatesTo: ThreadRelatesTo?
}

View File

@ -0,0 +1,31 @@
/*
* 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 org.matrix.android.sdk.api.session.room.model.relation.threads
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.events.model.RelationType
import org.matrix.android.sdk.api.session.room.model.relation.RelationContent
import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent
@JsonClass(generateAdapter = true)
data class ThreadRelatesTo(
@Json(name = "rel_type") override val type: String? = RelationType.THREAD,
@Json(name = "event_id") override val eventId: String,
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
@Json(name = "option") override val option: Int? = null
) : RelationContent

View File

@ -0,0 +1,28 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.session.room.model.relation.threads
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
@JsonClass(generateAdapter = true)
data class ThreadTextContent(
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
@Json(name = "body") override val body: String,
@Json(name = "m.relates_to") override val relatesTo: ThreadRelatesTo? = null,
) : ThreadContent

View File

@ -38,6 +38,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
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.send.LocalEchoEventFactory
import org.matrix.android.sdk.internal.session.room.send.TextContent
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
@ -158,6 +159,11 @@ internal class DefaultRelationService @AssistedInject constructor(
}
}
override fun replyInThread(eventToReplyInThread: TimelineEvent, replyInThreadText: CharSequence, autoMarkdown: Boolean): Cancelable? {
val event = eventFactory.createThreadTextEvent(eventToReplyInThread, TextContent(replyInThreadText.toString()))
return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
}
/**
* Saves the event in database as a local echo.
* SendState is set to UNSENT and it's added to a the sendingTimelineEvents list of the room.

View File

@ -51,6 +51,8 @@ import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
import org.matrix.android.sdk.api.session.room.model.relation.ReactionInfo
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent
import org.matrix.android.sdk.api.session.room.model.relation.threads.ThreadTextContent
import org.matrix.android.sdk.api.session.room.model.relation.threads.ThreadRelatesTo
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
import org.matrix.android.sdk.api.session.room.timeline.isReply
@ -58,6 +60,7 @@ import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor
import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils
import timber.log.Timber
import javax.inject.Inject
/**
@ -340,6 +343,15 @@ internal class LocalEchoEventFactory @Inject constructor(
)
}
/**
* Creates a thread event related to the already existing event
*/
fun createThreadTextEvent(eventToReplyInThread: TimelineEvent, textContent: TextContent): Event =
createEvent(
eventToReplyInThread.roomId,
EventType.MESSAGE,
textContent.toThreadTextContent(eventToReplyInThread).toContent())
private fun dummyOriginServerTs(): Long {
return System.currentTimeMillis()
}

View File

@ -16,9 +16,13 @@
package org.matrix.android.sdk.internal.session.room.send
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.relation.threads.ThreadTextContent
import org.matrix.android.sdk.api.session.room.model.relation.threads.ThreadRelatesTo
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromHtmlReply
import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply
@ -41,6 +45,14 @@ fun TextContent.toMessageTextContent(msgType: String = MessageType.MSGTYPE_TEXT)
)
}
fun TextContent.toThreadTextContent(eventToReplyInThread: TimelineEvent, msgType: String = MessageType.MSGTYPE_TEXT): ThreadTextContent {
return ThreadTextContent(
msgType = msgType,
body = text,
relatesTo = ThreadRelatesTo(eventId = eventToReplyInThread.eventId)
)
}
fun TextContent.removeInReplyFallbacks(): TextContent {
return copy(
text = extractUsefulTextFromReply(this.text),

View File

@ -179,6 +179,9 @@
<activity android:name=".features.roomdirectory.RoomDirectoryActivity" />
<activity android:name=".features.roomdirectory.roompreview.RoomPreviewActivity" />
<activity android:name=".features.home.room.filtered.FilteredRoomsActivity" />
<activity android:name=".features.home.room.threads.RoomThreadsActivity" />
<activity android:name=".features.home.room.threads.detail.RoomThreadDetailActivity" />
<activity
android:name=".features.home.room.detail.RoomDetailActivity"
android:parentActivityName=".features.home.HomeActivity">

View File

@ -58,6 +58,7 @@ import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
import im.vector.app.features.home.room.detail.RoomDetailFragment
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.RoomThreadDetailFragment
import im.vector.app.features.login.LoginCaptchaFragment
import im.vector.app.features.login.LoginFragment
import im.vector.app.features.login.LoginGenericTextInputFormFragment
@ -834,4 +835,9 @@ interface FragmentModule {
@IntoMap
@FragmentKey(SpaceLeaveAdvancedFragment::class)
fun bindSpaceLeaveAdvancedFragment(fragment: SpaceLeaveAdvancedFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomThreadDetailFragment::class)
fun bindRoomThreadDetailFragment(fragment: RoomThreadDetailFragment): Fragment
}

View File

@ -52,6 +52,8 @@ import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.app.features.home.room.filtered.FilteredRoomsActivity
import im.vector.app.features.home.room.list.RoomListModule
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.app.features.home.room.threads.RoomThreadsActivity
import im.vector.app.features.home.room.threads.detail.RoomThreadDetailActivity
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.InviteUsersToRoomActivity
import im.vector.app.features.invite.VectorInviteView
@ -174,6 +176,8 @@ interface ScreenComponent {
fun inject(activity: SpaceManageActivity)
fun inject(activity: RoomJoinRuleActivity)
fun inject(activity: SpaceLeaveAdvancedActivity)
fun inject(activity: RoomThreadsActivity)
fun inject(activity: RoomThreadDetailActivity)
/* ==========================================================================================
* BottomSheets

View File

@ -161,6 +161,8 @@ import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
import im.vector.app.features.home.room.detail.views.RoomDetailLazyLoadedViews
import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.app.features.home.room.threads.detail.RoomThreadDetailArgs
import im.vector.app.features.home.room.threads.detail.RoomThreadDetailActivity
import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.PillImageSpan
import im.vector.app.features.html.PillsPostProcessor
@ -1957,6 +1959,16 @@ class RoomDetailFragment @Inject constructor(
requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit)
}
}
is EventSharedAction.ReplyInThread -> {
if (!views.voiceMessageRecorderView.isActive()) {
context?.let {
val roomThreadDetailArgs = RoomThreadDetailArgs(roomDetailArgs.roomId,action.eventId)
startActivity(RoomThreadDetailActivity.newIntent(it, roomThreadDetailArgs))
}
} else {
requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit)
}
}
is EventSharedAction.CopyPermalink -> {
val permalink = session.permalinkService().createPermalink(roomDetailArgs.roomId, action.eventId)
copyToClipboard(requireContext(), permalink, false)

View File

@ -48,6 +48,10 @@ sealed class EventSharedAction(@StringRes val titleRes: Int,
data class Reply(val eventId: String) :
EventSharedAction(R.string.reply, R.drawable.ic_reply)
data class ReplyInThread(val eventId: String) :
// TODO add translations
EventSharedAction(R.string.reply_in_thread, R.drawable.ic_reply_in_thread)
data class Share(val eventId: String, val messageContent: MessageContent) :
EventSharedAction(R.string.share, R.drawable.ic_share)

View File

@ -326,6 +326,11 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
add(EventSharedAction.Reply(eventId))
}
// *** Testing Threads ****
if (canReplyInThread(timelineEvent, messageContent, actionPermissions)) {
add(EventSharedAction.ReplyInThread(eventId))
}
if (canEdit(timelineEvent, session.myUserId, actionPermissions)) {
add(EventSharedAction.Edit(eventId))
}
@ -412,6 +417,22 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
}
}
private fun canReplyInThread(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean {
// Only event of type EventType.MESSAGE are supported for the moment
if (event.root.getClearType() != EventType.MESSAGE) return false
if (!actionPermissions.canSendMessage) return false
return when (messageContent?.msgType) {
MessageType.MSGTYPE_TEXT,
MessageType.MSGTYPE_NOTICE,
MessageType.MSGTYPE_EMOTE,
MessageType.MSGTYPE_IMAGE,
MessageType.MSGTYPE_VIDEO,
MessageType.MSGTYPE_AUDIO,
MessageType.MSGTYPE_FILE -> true
else -> false
}
}
private fun canQuote(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean {
// Only event of type EventType.MESSAGE are supported for the moment
if (event.root.getClearType() != EventType.MESSAGE) return false

View File

@ -0,0 +1,66 @@
/*
* 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
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.widget.SearchView
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityFilteredRoomsBinding
import im.vector.app.databinding.ActivityRoomThreadsBinding
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.list.RoomListFragment
import im.vector.app.features.home.room.list.RoomListParams
class RoomThreadsActivity : VectorBaseActivity<ActivityRoomThreadsBinding>() {
// private val roomListFragment: RoomListFragment?
// get() {
// return supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as? RoomListFragment
// }
override fun getBinding() = ActivityRoomThreadsBinding.inflate(layoutInflater)
override fun getCoordinatorLayout() = views.coordinatorLayout
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
override fun getMenuRes() = R.menu.menu_room_threads
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
configureToolbar(views.roomThreadsToolbar)
// if (isFirstCreation()) {
// val params = RoomListParams(RoomListDisplayMode.FILTERED)
// replaceFragment(R.id.filteredRoomsFragmentContainer, RoomListFragment::class.java, params, FRAGMENT_TAG)
// }
}
companion object {
private const val FRAGMENT_TAG = "RoomListFragment"
fun newIntent(context: Context): Intent {
return Intent(context, RoomThreadsActivity::class.java)
}
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.content.Context
import android.content.Intent
import android.os.Bundle
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityRoomThreadDetailBinding
class RoomThreadDetailActivity : VectorBaseActivity<ActivityRoomThreadDetailBinding>() {
// private val roomThreadDetailFragment: RoomThreadDetailFragment?
// get() {
// return supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as? RoomThreadDetailFragment
// }
override fun getBinding() = ActivityRoomThreadDetailBinding.inflate(layoutInflater)
override fun getCoordinatorLayout() = views.coordinatorLayout
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
override fun getMenuRes() = R.menu.menu_room_threads
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
configureToolbar(views.roomThreadDetailToolbar)
if (isFirstCreation()) {
val roomThreadDetailArgs: RoomThreadDetailArgs? = intent?.extras?.getParcelable(EXTRA_ROOM_THREAD_DETAIL_ARGS)
replaceFragment(R.id.roomThreadDetailFragmentContainer, RoomThreadDetailFragment::class.java, roomThreadDetailArgs, FRAGMENT_TAG)
}
}
companion object {
private const val FRAGMENT_TAG = "RoomThreadDetailFragment"
const val EXTRA_ROOM_THREAD_DETAIL_ARGS = "EXTRA_ROOM_THREAD_DETAIL_ARGS"
fun newIntent(context: Context, roomThreadDetailArgs: RoomThreadDetailArgs): Intent {
return Intent(context, RoomThreadDetailActivity::class.java).apply {
putExtra(EXTRA_ROOM_THREAD_DETAIL_ARGS, roomThreadDetailArgs)
}
}
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.annotation.SuppressLint
import android.os.Bundle
import android.os.Parcelable
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.FragmentRoomThreadDetailBinding
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject
@Parcelize
data class RoomThreadDetailArgs(
val roomId: String,
val eventId: String? = null,
) : Parcelable
class RoomThreadDetailFragment @Inject constructor(
private val session: Session
) :
VectorBaseFragment<FragmentRoomThreadDetailBinding>() {
private val roomThreadDetailArgs: RoomThreadDetailArgs by args()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomThreadDetailBinding {
return FragmentRoomThreadDetailBinding.inflate(inflater, container, false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
views.testTextVeiwddasda.text = "${roomThreadDetailArgs.eventId} -- ${roomThreadDetailArgs.roomId}"
}
}

View File

@ -0,0 +1,24 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="20dp"
android:height="21dp"
android:viewportWidth="20"
android:viewportHeight="21"
>
<group>
<clip-path
android:pathData="M3 1H17C18.1046 1 19 1.89543 19 3V18C19 19.1046 18.1046 20 17 20H3C1.89543 20 1 19.1046 1 18V3C1 1.89543 1.89543 1 3 1Z"
/>
</group>
<path
android:pathData="M3 1H17C18.1046 1 19 1.89543 19 3V18C19 19.1046 18.1046 20 17 20H3C1.89543 20 1 19.1046 1 18V3C1 1.89543 1.89543 1 3 1Z"
android:strokeWidth="2"
android:strokeColor="#737D8C"
/>
</vector>

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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: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">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/roomThreadDetailToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:contentInsetStart="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/roomThreadDetailToolbarConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/roomThreadDetailToolbarTitleTextView"
style="@style/Widget.Vector.TextView.HeadlineMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/a11y_beta"
android:textColor="?vctr_content_primary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/roomThreadDetailToolbarImageView"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_presence_offline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/roomThreadDetailToolbarTitleTextView"
app:layout_constraintTop_toTopOf="parent"
tools:src="@sample/room_round_avatars" />
<TextView
android:id="@+id/roomThreadDetailToolbarSubtitleTextView"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?vctr_content_primary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/roomThreadDetailToolbarImageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="RoomName"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/roomThreadDetailFragmentContainer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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: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">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/roomThreadsToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:contentInsetStart="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/roomThreadsToolbarConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/roomThreadsToolbarTitleTextView"
style="@style/Widget.Vector.TextView.HeadlineMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/a11y_beta"
android:textColor="?vctr_content_primary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/roomThreadsToolbarImageView"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_presence_offline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/roomThreadsToolbarTitleTextView"
app:layout_constraintTop_toTopOf="parent"
tools:src="@sample/room_round_avatars" />
<TextView
android:id="@+id/roomThreadsToolbarSubtitleTextView"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?vctr_content_primary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/roomThreadsToolbarImageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="RoomName"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/roomThreadsFragmentContainer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -18,7 +18,7 @@
android:layout_height="wrap_content"
android:minHeight="48dp"
android:visibility="gone"
tools:visibility="visible" />
tools:visibility="gone" />
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/roomToolbar"

View File

@ -0,0 +1,33 @@
<?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:id="@+id/rootConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/testTextVeiwddasda"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/room_threads_filter"
android:textSize="25sp"
app:layout_constraintBottom_toTopOf="@id/composerLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<im.vector.app.features.home.room.detail.composer.TextComposerView
android:id="@+id/composerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:colorBackground"
android:minHeight="56dp"
android:transitionName="composer"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,9 @@
<?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>

View File

@ -1021,6 +1021,9 @@
<string name="room_details_people_invited_group_name">INVITED</string>
<string name="room_details_people_present_group_name">JOINED</string>
<!-- Room Threads -->
<string name="room_threads_filter">Filter Threads in room</string>
<!-- Room events -->
<string name="room_event_action_report_prompt_reason">Reason for reporting this content</string>
<string name="room_event_action_report_prompt_ignore_user">Do you want to hide all messages from this user?\n\nNote that this action will restart the app and it may take some time.</string>
@ -2171,6 +2174,7 @@
<string name="edit">Edit</string>
<string name="reply">Reply</string>
<string name="reply_in_thread">Reply In Thread</string>
<string name="global_retry">Retry</string>
<string name="room_list_empty">"Join a room to start using the app."</string>