filtered room list at home (#6724)

This commit is contained in:
Nikita Fedrunov 2022-08-11 16:26:24 +02:00 committed by GitHub
parent fa8f72c909
commit e0e21d4282
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 336 additions and 26 deletions

1
changelog.d/6505.wip Normal file
View File

@ -0,0 +1 @@
added filter tabs for new App layout's Home screen

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Widget.Vector.TabLayout" parent="Widget.MaterialComponents.TabLayout">
<item name="materialThemeOverlay">@style/ThemeOverlay.Vector.HomeFilterTabLayout</item>
<item name="tabTextAppearance">@style/TextAppearance.Vector.FilterTabTextAppearance</item>
</style>
<style name="TextAppearance.Vector.FilterTabTextAppearance" parent="TextAppearance.Vector.Subtitle">
<item name="textAllCaps">false</item>
</style>
<style name="ThemeOverlay.Vector.HomeFilterTabLayout" parent="Theme.Vector.Launcher">
<item name="colorSurface">?vctr_toolbar_background</item>
<item name="colorOnSurface">?vctr_content_secondary</item>
</style>
</resources>

View File

@ -17,6 +17,7 @@
package im.vector.app.features.home.room.list.home package im.vector.app.features.home.room.list.home
import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
@ -25,4 +26,5 @@ sealed class HomeRoomListAction : VectorViewModelAction {
data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : HomeRoomListAction() data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : HomeRoomListAction()
data class ToggleTag(val roomId: String, val tag: String) : HomeRoomListAction() data class ToggleTag(val roomId: String, val tag: String) : HomeRoomListAction()
data class LeaveRoom(val roomId: String) : HomeRoomListAction() data class LeaveRoom(val roomId: String) : HomeRoomListAction()
data class ChangeRoomFilter(val filter: HomeRoomFilter) : HomeRoomListAction()
} }

View File

@ -37,14 +37,14 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.databinding.FragmentRoomListBinding import im.vector.app.databinding.FragmentRoomListBinding
import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.list.RoomListAnimator import im.vector.app.features.home.room.list.RoomListAnimator
import im.vector.app.features.home.room.list.RoomListListener import im.vector.app.features.home.room.list.RoomListListener
import im.vector.app.features.home.room.list.RoomSummaryItemFactory import im.vector.app.features.home.room.list.RoomSummaryItemFactory
import im.vector.app.features.home.room.list.RoomSummaryPagedController
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.home.room.list.home.filter.HomeFilteredRoomsController
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
import im.vector.app.features.home.room.list.home.recent.RecentRoomCarouselController import im.vector.app.features.home.room.list.home.recent.RecentRoomCarouselController
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -207,28 +207,36 @@ class HomeRoomListFragment @Inject constructor(
.show() .show()
} }
private fun getAdapterForData(data: HomeRoomSection): EpoxyControllerAdapter { private fun getAdapterForData(section: HomeRoomSection): EpoxyControllerAdapter {
return when (data) { return when (section) {
is HomeRoomSection.RoomSummaryData -> { is HomeRoomSection.RoomSummaryData -> {
RoomSummaryPagedController( HomeFilteredRoomsController(
roomSummaryItemFactory, roomSummaryItemFactory,
RoomListDisplayMode.ROOMS showFilters = section.showFilters,
).also { controller -> ).also { controller ->
controller.listener = this controller.listener = this
data.list.observe(viewLifecycleOwner) { list -> controller.onFilterChanged = ::onRoomFilterChanged
section.filtersData.onEach {
controller.submitFiltersData(it)
}.launchIn(lifecycleScope)
section.list.observe(viewLifecycleOwner) { list ->
controller.submitList(list) controller.submitList(list)
} }
}.adapter }.adapter
} }
is HomeRoomSection.RecentRoomsData -> recentRoomCarouselController.also { controller -> is HomeRoomSection.RecentRoomsData -> recentRoomCarouselController.also { controller ->
controller.listener = this controller.listener = this
data.list.observe(viewLifecycleOwner) { list -> section.list.observe(viewLifecycleOwner) { list ->
controller.submitList(list) controller.submitList(list)
} }
}.adapter }.adapter
} }
} }
private fun onRoomFilterChanged(filter: HomeRoomFilter) {
roomListViewModel.handle(HomeRoomListAction.ChangeRoomFilter(filter))
}
private fun handleSelectRoom(event: HomeRoomListViewEvents.SelectRoom, isInviteAlreadyAccepted: Boolean) { private fun handleSelectRoom(event: HomeRoomListViewEvents.SelectRoom, isInviteAlreadyAccepted: Boolean) {
navigator.openRoom( navigator.openRoom(
context = requireActivity(), context = requireActivity(),

View File

@ -27,33 +27,38 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.StateView import im.vector.app.core.platform.StateView
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.query.SpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
import org.matrix.android.sdk.api.query.toActiveSpaceOrNoFilter import org.matrix.android.sdk.api.query.toActiveSpaceOrNoFilter
import org.matrix.android.sdk.api.query.toActiveSpaceOrOrphanRooms
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.state.isPublic import org.matrix.android.sdk.api.session.room.state.isPublic
import org.matrix.android.sdk.flow.flow
class HomeRoomListViewModel @AssistedInject constructor( class HomeRoomListViewModel @AssistedInject constructor(
@Assisted initialState: HomeRoomListViewState, @Assisted initialState: HomeRoomListViewState,
private val session: Session, private val session: Session,
private val spaceStateHandler: SpaceStateHandler, private val spaceStateHandler: SpaceStateHandler,
private val vectorPreferences: VectorPreferences,
) : VectorViewModel<HomeRoomListViewState, HomeRoomListAction, HomeRoomListViewEvents>(initialState) { ) : VectorViewModel<HomeRoomListViewState, HomeRoomListAction, HomeRoomListViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -73,6 +78,8 @@ class HomeRoomListViewModel @AssistedInject constructor(
private val _sections = MutableSharedFlow<Set<HomeRoomSection>>(replay = 1) private val _sections = MutableSharedFlow<Set<HomeRoomSection>>(replay = 1)
val sections = _sections.asSharedFlow() val sections = _sections.asSharedFlow()
private var filteredPagedRoomSummariesLive: UpdatableLivePageResult? = null
init { init {
configureSections() configureSections()
} }
@ -81,7 +88,7 @@ class HomeRoomListViewModel @AssistedInject constructor(
val newSections = mutableSetOf<HomeRoomSection>() val newSections = mutableSetOf<HomeRoomSection>()
newSections.add(getRecentRoomsSection()) newSections.add(getRecentRoomsSection())
newSections.add(getAllRoomsSection()) newSections.add(getFilteredRoomsSection())
viewModelScope.launch { viewModelScope.launch {
_sections.emit(newSections) _sections.emit(newSections)
@ -104,15 +111,21 @@ class HomeRoomListViewModel @AssistedInject constructor(
) )
} }
private fun getAllRoomsSection(): HomeRoomSection.RoomSummaryData { private fun getFilteredRoomsSection(): HomeRoomSection.RoomSummaryData {
val builder = RoomSummaryQueryParams.Builder().also { val builder = RoomSummaryQueryParams.Builder().also {
it.memberships = listOf(Membership.JOIN) it.memberships = listOf(Membership.JOIN)
} }
val filteredPagedRoomSummariesLive = session.roomService().getFilteredPagedRoomSummariesLive( val params = getFilteredQueryParams(HomeRoomFilter.ALL, builder.build())
builder.build(), val sortOrder = RoomSortOrder.ACTIVITY // #6506
pagedListConfig
) val liveResults = session.roomService().getFilteredPagedRoomSummariesLive(
params,
pagedListConfig,
sortOrder
).also {
this.filteredPagedRoomSummariesLive = it
}
spaceStateHandler.getSelectedSpaceFlow() spaceStateHandler.getSelectedSpaceFlow()
.distinctUntilChanged() .distinctUntilChanged()
@ -121,20 +134,83 @@ class HomeRoomListViewModel @AssistedInject constructor(
} }
.onEach { selectedSpaceOption -> .onEach { selectedSpaceOption ->
val selectedSpace = selectedSpaceOption.orNull() val selectedSpace = selectedSpaceOption.orNull()
filteredPagedRoomSummariesLive.queryParams = filteredPagedRoomSummariesLive.queryParams.copy( liveResults.queryParams = liveResults.queryParams.copy(
spaceFilter = getSpaceFilter(selectedSpaceId = selectedSpace?.roomId) spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter()
) )
}.launchIn(viewModelScope) }.launchIn(viewModelScope)
return HomeRoomSection.RoomSummaryData( return HomeRoomSection.RoomSummaryData(
list = filteredPagedRoomSummariesLive.livePagedList list = liveResults.livePagedList,
showFilters = true, // #6506
filtersData = getFiltersDataFlow()
) )
} }
private fun getSpaceFilter(selectedSpaceId: String?): SpaceFilter { private fun getFiltersDataFlow(): SharedFlow<List<HomeRoomFilter>> {
return when { val flow = MutableSharedFlow<List<HomeRoomFilter>>(replay = 1)
vectorPreferences.prefSpacesShowAllRoomInHome() -> selectedSpaceId.toActiveSpaceOrNoFilter()
else -> selectedSpaceId.toActiveSpaceOrOrphanRooms() val favouritesFlow = session.flow()
.liveRoomSummaries(
RoomSummaryQueryParams.Builder().also { builder ->
builder.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
}.build()
)
.map { it.isNotEmpty() }
.distinctUntilChanged()
val dmsFLow = session.flow()
.liveRoomSummaries(
RoomSummaryQueryParams.Builder().also { builder ->
builder.memberships = listOf(Membership.JOIN)
builder.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
}.build()
)
.map { it.isNotEmpty() }
.distinctUntilChanged()
favouritesFlow.combine(dmsFLow) { hasFavourite, hasDm ->
hasFavourite to hasDm
}.onEach { (hasFavourite, hasDm) ->
val filtersData = mutableListOf(
HomeRoomFilter.ALL,
HomeRoomFilter.UNREADS
)
if (hasFavourite) {
filtersData.add(
HomeRoomFilter.FAVOURITES
)
}
if (hasDm) {
filtersData.add(
HomeRoomFilter.PEOPlE
)
}
flow.emit(filtersData)
}.launchIn(viewModelScope)
return flow
}
private fun getFilteredQueryParams(filter: HomeRoomFilter, currentParams: RoomSummaryQueryParams): RoomSummaryQueryParams {
return when (filter) {
HomeRoomFilter.ALL -> currentParams.copy(
roomCategoryFilter = null,
roomTagQueryFilter = null
)
HomeRoomFilter.UNREADS -> currentParams.copy(
roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS,
roomTagQueryFilter = RoomTagQueryFilter(null, false, null)
)
HomeRoomFilter.FAVOURITES ->
currentParams.copy(
roomCategoryFilter = null,
roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
)
HomeRoomFilter.PEOPlE -> currentParams.copy(
roomCategoryFilter = RoomCategoryFilter.ONLY_DM,
roomTagQueryFilter = null
)
} }
} }
@ -144,6 +220,13 @@ class HomeRoomListViewModel @AssistedInject constructor(
is HomeRoomListAction.LeaveRoom -> handleLeaveRoom(action) is HomeRoomListAction.LeaveRoom -> handleLeaveRoom(action)
is HomeRoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action) is HomeRoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
is HomeRoomListAction.ToggleTag -> handleToggleTag(action) is HomeRoomListAction.ToggleTag -> handleToggleTag(action)
is HomeRoomListAction.ChangeRoomFilter -> handleChangeRoomFilter(action)
}
}
private fun handleChangeRoomFilter(action: HomeRoomListAction.ChangeRoomFilter) {
filteredPagedRoomSummariesLive?.let { liveResults ->
liveResults.queryParams = getFilteredQueryParams(action.filter, liveResults.queryParams)
} }
} }

View File

@ -18,11 +18,15 @@ package im.vector.app.features.home.room.list.home
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.paging.PagedList import androidx.paging.PagedList
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
import kotlinx.coroutines.flow.SharedFlow
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
sealed class HomeRoomSection { sealed class HomeRoomSection {
data class RoomSummaryData( data class RoomSummaryData(
val list: LiveData<PagedList<RoomSummary>> val list: LiveData<PagedList<RoomSummary>>,
val showFilters: Boolean,
val filtersData: SharedFlow<List<HomeRoomFilter>>
) : HomeRoomSection() ) : HomeRoomSection()
data class RecentRoomsData( data class RecentRoomsData(

View File

@ -0,0 +1,70 @@
/*
* Copyright (c) 2022 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.list.home.filter
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.paging.PagedListEpoxyController
import im.vector.app.core.utils.createUIHandler
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.list.RoomListListener
import im.vector.app.features.home.room.list.RoomSummaryItemFactory
import im.vector.app.features.home.room.list.RoomSummaryItemPlaceHolder_
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomSummary
class HomeFilteredRoomsController(
private val roomSummaryItemFactory: RoomSummaryItemFactory,
private val showFilters: Boolean,
) : PagedListEpoxyController<RoomSummary>(
// Important it must match the PageList builder notify Looper
modelBuildingHandler = createUIHandler()
) {
private var roomChangeMembershipStates: Map<String, ChangeMembershipState>? = null
set(value) {
field = value
// ideally we could search for visible models and update only those
requestForcedModelBuild()
}
var listener: RoomListListener? = null
var onFilterChanged: ((HomeRoomFilter) -> Unit)? = null
private var filtersData: List<HomeRoomFilter>? = null
override fun addModels(models: List<EpoxyModel<*>>) {
val host = this
if (showFilters) {
roomFilterHeaderItem {
id("filter_header")
filtersData(host.filtersData)
onFilterChangedListener(host.onFilterChanged)
}
}
super.addModels(models)
}
fun submitFiltersData(data: List<HomeRoomFilter>) {
this.filtersData = data
requestForcedModelBuild()
}
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) }
return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener)
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2022 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.list.home.filter
import androidx.annotation.StringRes
import im.vector.app.R
enum class HomeRoomFilter(@StringRes val titleRes: Int) {
ALL(R.string.room_list_filter_all),
UNREADS(R.string.room_list_filter_unreads),
FAVOURITES(R.string.room_list_filter_favourites),
PEOPlE(R.string.room_list_filter_people),
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2022 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.list.home.filter
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.google.android.material.tabs.TabLayout
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
@EpoxyModelClass
abstract class RoomFilterHeaderItem : VectorEpoxyModel<RoomFilterHeaderItem.Holder>(R.layout.item_home_filter_tabs) {
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var onFilterChangedListener: ((HomeRoomFilter) -> Unit)? = null
@EpoxyAttribute
var filtersData: List<HomeRoomFilter>? = null
override fun bind(holder: Holder) {
super.bind(holder)
with(holder.tabLayout) {
removeAllTabs()
filtersData?.forEach { filter ->
addTab(newTab().setText(filter.titleRes).setTag(filter))
}
addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
(tab?.tag as? HomeRoomFilter)?.let { filter ->
onFilterChangedListener?.invoke(filter)
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) = Unit
override fun onTabReselected(tab: TabLayout.Tab?) = Unit
})
}
}
override fun unbind(holder: Holder) {
holder.tabLayout.clearOnTabSelectedListeners()
super.unbind(holder)
}
class Holder : VectorEpoxyHolder() {
val tabLayout by bind<TabLayout>(R.id.home_filter_tabs_tabs)
}
}

View File

@ -0,0 +1,27 @@
<?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"
tools:viewBindingIgnore="true">
<com.google.android.material.tabs.TabLayout
android:id="@+id/home_filter_tabs_tabs"
style="@style/Widget.Vector.TabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?vctr_content_quaternary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1610,6 +1610,11 @@
<!-- Note to translators: for RTL languages, the + will be at the bottom left. Please translate "bottom left" instead of "bottom right". Thanks! --> <!-- Note to translators: for RTL languages, the + will be at the bottom left. Please translate "bottom left" instead of "bottom right". Thanks! -->
<string name="room_list_rooms_empty_body">Your rooms will be displayed here. Tap the + at the bottom right to find existing ones or start some of your own.</string> <string name="room_list_rooms_empty_body">Your rooms will be displayed here. Tap the + at the bottom right to find existing ones or start some of your own.</string>
<string name="room_list_filter_all">All</string>
<string name="room_list_filter_unreads">Unreads</string>
<string name="room_list_filter_favourites">Favourites</string>
<string name="room_list_filter_people">People</string>
<string name="title_activity_emoji_reaction_picker">Reactions</string> <string name="title_activity_emoji_reaction_picker">Reactions</string>
<string name="message_add_reaction">Add Reaction</string> <string name="message_add_reaction">Add Reaction</string>
<string name="message_view_reaction">View Reactions</string> <string name="message_view_reaction">View Reactions</string>