Suggested Space support

This commit is contained in:
Valere 2021-03-18 09:42:21 +01:00
parent 0da9be327a
commit 802853d205
22 changed files with 430 additions and 21 deletions

View file

@ -21,14 +21,17 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
interface Space {
fun asRoom() : Room
fun asRoom(): Room
/**
* A current snapshot of [RoomSummary] associated with the space
*/
fun spaceSummary(): RoomSummary?
suspend fun addChildren(roomId: String, viaServers: List<String>, order: String?, autoJoin: Boolean = false)
suspend fun addChildren(roomId: String, viaServers: List<String>,
order: String?,
autoJoin: Boolean = false,
suggested: Boolean? = false)
suspend fun removeRoom(roomId: String)

View file

@ -46,7 +46,14 @@ data class SpaceChildContent(
* be automatically joined by members of that space.
* (This is not a force-join, which are descoped for a future MSC; the user can subsequently part these room if they desire.)
*/
@Json(name = "auto_join") val autoJoin: Boolean? = false
@Json(name = "auto_join") val autoJoin: Boolean? = false,
/**
* If `suggested` is set to `true`, that indicates that the child should be advertised to
* members of the space by the client. This could be done by showing them eagerly
* in the room list. This is should be ignored if `auto_join` is set to `true`.
*/
@Json(name = "suggested") val suggested: Boolean? = false
) {
/**
* Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7F (~),

View file

@ -36,14 +36,18 @@ internal class DefaultSpace(private val room: Room, private val spaceSummaryData
return spaceSummaryDataSource.getSpaceSummary(asRoom().roomId)
}
override suspend fun addChildren(roomId: String, viaServers: List<String>, order: String?, autoJoin: Boolean) {
override suspend fun addChildren(roomId: String, viaServers: List<String>,
order: String?,
autoJoin: Boolean,
suggested: Boolean?) {
asRoom().sendStateEvent(
eventType = EventType.STATE_SPACE_CHILD,
stateKey = roomId,
body = SpaceChildContent(
via = viaServers,
autoJoin = autoJoin,
order = order
order = order,
suggested = suggested
).toContent()
)
}

View file

@ -35,7 +35,7 @@ internal interface SpaceApi {
*
* MSC 2946 https://github.com/matrix-org/matrix-doc/blob/kegan/spaces-summary/proposals/2946-spaces-summary.md
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/spaces")
@POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2946/rooms/{roomId}/spaces")
fun getSpaces(@Path("roomId") spaceId: String,
@Body params: SpaceSummaryParams
): Call<SpacesResponse>

View file

@ -26,5 +26,8 @@ internal data class SpaceSummaryParams(
/** The maximum number of rooms/subspaces to return, server can override this, default: 100 */
@Json(name = "limit") val limit: Int = 100,
/** A token to use if this is a subsequent HTTP hit, default: "".*/
@Json(name = "batch") val batch: String = ""
@Json(name = "batch") val batch: String = "",
/** whether we should only return children with the "suggested" flag set.*/
@Json(name = "suggested_only") val suggestedOnly: Boolean = false
)

View file

@ -143,7 +143,7 @@ internal class ReadReceiptHandler @Inject constructor(
@Suppress("UNCHECKED_CAST")
val content = dataFromFile
.events
.firstOrNull { it.type == EventType.RECEIPT }
?.firstOrNull { it.type == EventType.RECEIPT }
?.content as? ReadReceiptContent
if (content == null) {

View file

@ -33,6 +33,17 @@ class AppStateHandler @Inject constructor() : LifecycleObserver {
private val compositeDisposable = CompositeDisposable()
init {
// restore current space from ui state
sessionDataSource.currentValue?.orNull()?.let { session ->
uiStateRepository.getSelectedSpace(session.sessionId)?.let { selectedSpaceId ->
session.getRoomSummary(selectedSpaceId)?.let {
selectedSpaceDataSource.post(Option.just(it))
}
}
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun entersForeground() {
}

View file

@ -37,6 +37,8 @@ import im.vector.app.features.crypto.verification.IncomingVerificationRequestHan
import im.vector.app.features.grouplist.SelectedGroupDataSource
import im.vector.app.features.grouplist.SelectedSpaceDataSource
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.CurrentSpaceSuggestedRoomListDataSource
import im.vector.app.features.home.HomeRoomListDataSource
import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder
@ -118,6 +120,8 @@ interface VectorComponent {
fun selectedSpaceStore(): SelectedSpaceDataSource
fun currentSpaceSuggestedRoomListDataSource(): CurrentSpaceSuggestedRoomListDataSource
fun roomDetailPendingActionStore(): RoomDetailPendingActionStore
fun activeSessionObservableStore(): ActiveSessionDataSource

View file

@ -0,0 +1,25 @@
/*
* 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
import im.vector.app.core.utils.BehaviorDataSource
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class CurrentSpaceSuggestedRoomListDataSource @Inject constructor() : BehaviorDataSource<List<SpaceChildInfo>>()

View file

@ -58,7 +58,6 @@ import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import timber.log.Timber
import javax.inject.Inject
private const val INDEX_PEOPLE = 0
@ -363,7 +362,7 @@ class HomeDetailFragment @Inject constructor(
}
override fun invalidate() = withState(viewModel) {
Timber.v(it.toString())
// Timber.v(it.toString())
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople)
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)

View file

@ -29,4 +29,5 @@ sealed class RoomListAction : VectorViewModelAction {
data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : RoomListAction()
data class ToggleTag(val roomId: String, val tag: String) : RoomListAction()
data class LeaveRoom(val roomId: String) : RoomListAction()
data class JoinSuggestedRoom(val roomId: String, val viaServers: List<String>?) : RoomListAction()
}

View file

@ -52,6 +52,7 @@ import im.vector.app.features.notifications.NotificationDrawerManager
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.extensions.orTrue
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
import javax.inject.Inject
@ -421,6 +422,10 @@ class RoomListFragment @Inject constructor(
roomListViewModel.handle(RoomListAction.AcceptInvitation(room))
}
override fun onJoinSuggestedRoom(room: SpaceChildInfo) {
roomListViewModel.handle(RoomListAction.JoinSuggestedRoom(room.childRoomId, room.viaServers))
}
override fun onRejectRoomInvitation(room: RoomSummary) {
notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId)
roomListViewModel.handle(RoomListAction.RejectInvitation(room))

View file

@ -18,8 +18,11 @@ package im.vector.app.features.home.room.list
import androidx.annotation.StringRes
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext
import im.vector.app.R
import im.vector.app.core.extensions.exhaustive
@ -166,6 +169,7 @@ class RoomListViewModel @Inject constructor(
is RoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
is RoomListAction.ToggleTag -> handleToggleTag(action)
is RoomListAction.ToggleSection -> handleToggleSection(action.section)
is RoomListAction.JoinSuggestedRoom -> handleJoinSuggestedRoom(action)
}.exhaustive
}
@ -316,6 +320,38 @@ class RoomListViewModel @Inject constructor(
}
}
private fun handleJoinSuggestedRoom(action: RoomListAction.JoinSuggestedRoom) {
setState {
copy(
suggestedRoomJoiningState = this.suggestedRoomJoiningState.toMutableMap().apply {
this[action.roomId] = Loading()
}.toMap()
)
}
viewModelScope.launch {
try {
awaitCallback<Unit> {
session.joinRoom(action.roomId, null, action.viaServers ?: emptyList(), it)
}
setState {
copy(
suggestedRoomJoiningState = this.suggestedRoomJoiningState.toMutableMap().apply {
this[action.roomId] = Success(Unit)
}.toMap()
)
}
} catch (failure: Throwable) {
setState {
copy(
suggestedRoomJoiningState = this.suggestedRoomJoiningState.toMutableMap().apply {
this[action.roomId] = Fail(failure)
}.toMap()
)
}
}
}
}
private fun handleToggleTag(action: RoomListAction.ToggleTag) {
session.getRoom(action.roomId)?.let { room ->
viewModelScope.launch(Dispatchers.IO) {
@ -342,7 +378,7 @@ class RoomListViewModel @Inject constructor(
private fun String.otherTag(): String? {
return when (this) {
RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY
RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY
RoomTag.ROOM_TAG_LOW_PRIORITY -> RoomTag.ROOM_TAG_FAVOURITE
else -> null
}

View file

@ -16,6 +16,9 @@
package im.vector.app.features.home.room.list
import android.view.View
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Loading
import im.vector.app.R
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
@ -28,6 +31,8 @@ import im.vector.app.features.home.room.typing.TypingHelper
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
@ -50,6 +55,19 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
}
}
fun createSuggestion(spaceChildInfo: SpaceChildInfo,
suggestedRoomJoiningStates: Map<String, Async<Unit>>,
onJoinClick: View.OnClickListener) : VectorEpoxyModel<*> {
return SuggestedRoomItem_()
.id("sug_${spaceChildInfo.childRoomId}")
.matrixItem(MatrixItem.RoomItem(spaceChildInfo.childRoomId, spaceChildInfo.name, spaceChildInfo.avatarUrl))
.avatarRenderer(avatarRenderer)
.topic(spaceChildInfo.topic)
.loading(suggestedRoomJoiningStates[spaceChildInfo.childRoomId] is Loading)
.memberCount(spaceChildInfo.activeMemberCount ?: 0)
.buttonClickListener(onJoinClick)
}
private fun createInvitationItem(roomSummary: RoomSummary,
changeMembershipState: ChangeMembershipState,
listener: RoomListListener?): VectorEpoxyModel<*> {

View file

@ -0,0 +1,117 @@
/*
* 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.list
import android.view.HapticFeedbackConstants
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
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.home.AvatarRenderer
import im.vector.app.features.themes.ThemeUtils
import me.gujun.android.span.image
import me.gujun.android.span.span
import org.matrix.android.sdk.api.util.MatrixItem
@EpoxyModelClass(layout = R.layout.item_suggested_room)
abstract class SuggestedRoomItem : VectorEpoxyModel<SuggestedRoomItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem
// Used only for diff calculation
@EpoxyAttribute var topic: String? = null
@EpoxyAttribute var memberCount: Int = 0
@EpoxyAttribute var loading: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemLongClickListener: View.OnLongClickListener? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var buttonClickListener: View.OnClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.rootView.setOnClickListener(itemClickListener)
holder.rootView.setOnLongClickListener {
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
itemLongClickListener?.onLongClick(it) ?: false
}
holder.titleView.text = matrixItem.getBestName()
avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.descriptionText.text = span {
span {
apply {
val tintColor = ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_secondary)
ContextCompat.getDrawable(holder.view.context, R.drawable.ic_room_profile_member_list)
?.apply {
ThemeUtils.tintDrawableWithColor(this, tintColor)
}?.let {
image(it)
}
}
+" $memberCount"
apply {
topic?.let {
+" - $topic"
}
}
}
}
if (loading) {
holder.joinButtonLoading.isVisible = true
holder.joinButton.isInvisible = true
} else {
holder.joinButtonLoading.isVisible = false
holder.joinButton.isVisible = true
}
holder.joinButton.setOnClickListener {
// local echo
holder.joinButtonLoading.isVisible = true
holder.joinButton.isInvisible = true
buttonClickListener?.onClick(it)
}
}
override fun unbind(holder: Holder) {
holder.rootView.setOnClickListener(null)
holder.rootView.setOnLongClickListener(null)
avatarRenderer.clear(holder.avatarImageView)
super.unbind(holder)
}
class Holder : VectorEpoxyHolder() {
val titleView by bind<TextView>(R.id.roomNameView)
val joinButton by bind<Button>(R.id.joinSuggestedRoomButton)
val joinButtonLoading by bind<ProgressBar>(R.id.joinSuggestedLoading)
val descriptionText by bind<TextView>(R.id.suggestedRoomDescription)
val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
val rootView by bind<ViewGroup>(R.id.itemRoomLayout)
}
}

View file

@ -36,6 +36,7 @@ import im.vector.app.features.createdirect.DirectRoomHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.room.model.Membership
@ -47,6 +48,7 @@ import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
import org.matrix.android.sdk.internal.util.awaitCallback
import javax.net.ssl.HttpsURLConnection
class MatrixToBottomSheetViewModel @AssistedInject constructor(
@Assisted initialState: MatrixToBottomSheetState,
@ -122,8 +124,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
} else {
session.getRoom(permalinkData.roomIdOrAlias)
}?.roomSummary()
if (knownRoom != null) {
val forceRefresh = true
if (!forceRefresh && knownRoom != null) {
setState {
copy(
roomPeekResult = Success(
@ -141,7 +143,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
)
}
} else {
val result = when (val peekResult = tryOrNull { resolveRoom(permalinkData.roomIdOrAlias) }) {
val result = when (val peekResult = tryOrNull { resolveSpace(permalinkData) }) {
is PeekResult.Success -> {
RoomInfoResult.FullInfo(
roomItem = MatrixItem.RoomItem(peekResult.roomId, peekResult.name, peekResult.avatarUrl),
@ -188,6 +190,30 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
}
}
private suspend fun resolveSpace(permalinkData: PermalinkData.RoomLink) : PeekResult {
try {
return session.spaceService().querySpaceChildren(permalinkData.roomIdOrAlias).let {
val roomSummary = it.first
PeekResult.Success(
roomId = roomSummary.roomId,
alias = roomSummary.canonicalAlias,
avatarUrl = roomSummary.avatarUrl,
name = roomSummary.name,
topic = roomSummary.topic,
numJoinedMembers = roomSummary.joinedMembersCount,
roomType = roomSummary.roomType,
viaServers = emptyList()
)
}
} catch (failure: Throwable) {
if (failure is Failure.OtherServerError && failure.httpCode == HttpsURLConnection.HTTP_NOT_FOUND) {
return resolveRoom(permalinkData.roomIdOrAlias)
} else {
throw failure
}
}
}
private suspend fun resolveUser(userId: String): User {
return tryOrNull { session.resolveUser(userId) }
// Create raw user in case the user is not searchable

View file

@ -33,6 +33,7 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.grouplist.SelectedSpaceDataSource
import im.vector.app.features.ui.UiStateRepository
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import kotlinx.coroutines.launch
@ -70,7 +71,8 @@ data class SpaceListViewState(
class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: SpaceListViewState,
private val selectedSpaceDataSource: SelectedSpaceDataSource,
private val session: Session,
private val stringProvider: StringProvider
private val stringProvider: StringProvider,
private val uiStateRepository: UiStateRepository
) : VectorViewModel<SpaceListViewState, SpaceListAction, SpaceListViewEvents>(initialState) {
@AssistedFactory
@ -152,6 +154,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
// selectedSpaceDataSource.post(Option.just(state.selectedSpace))
// }
setState { copy(selectedSpace = action.spaceSummary) }
uiStateRepository.storeSelectedSpace(action.spaceSummary.roomId, session.sessionId)
}
}
}

View file

@ -18,7 +18,6 @@ package im.vector.app.features.spaces.create
import android.net.Uri
import im.vector.app.core.platform.ViewModelTask
import im.vector.app.core.resources.StringProvider
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
@ -45,8 +44,8 @@ data class CreateSpaceTaskParams(
)
class CreateSpaceViewModelTask @Inject constructor(
private val session: Session,
private val stringProvider: StringProvider
private val session: Session
// private val stringProvider: StringProvider
) : ViewModelTask<CreateSpaceTaskParams, CreateSpaceTaskResult> {
override suspend fun execute(params: CreateSpaceTaskParams): CreateSpaceTaskResult {
@ -77,10 +76,17 @@ class CreateSpaceViewModelTask @Inject constructor(
timeout.roomID
}
val via = session.sessionParams.homeServerHost?.let { listOf(it) } ?: emptyList()
createdSpace!!.addChildren(roomId, via, null, true)
createdSpace!!.addChildren(roomId, via, null, autoJoin = false, suggested = true)
// set canonical
session.spaceService().setSpaceParent(
roomId,
createdSpace.asRoom().roomId,
true,
via
)
childIds.add(roomId)
} catch (failure: Throwable) {
Timber.d("Failed to create child room in $spaceID")
Timber.d("Space: Failed to create child room in $spaceID")
childErrors[roomName] = failure
}
}

View file

@ -39,7 +39,7 @@ class SharedPreferencesUiStateRepository @Inject constructor(
override fun getDisplayMode(): RoomListDisplayMode {
return when (sharedPreferences.getInt(KEY_DISPLAY_MODE, VALUE_DISPLAY_MODE_CATCHUP)) {
VALUE_DISPLAY_MODE_PEOPLE -> RoomListDisplayMode.PEOPLE
VALUE_DISPLAY_MODE_ROOMS -> RoomListDisplayMode.ROOMS
VALUE_DISPLAY_MODE_ROOMS -> RoomListDisplayMode.ROOMS
else -> if (vectorPreferences.labAddNotificationTab()) {
RoomListDisplayMode.NOTIFICATIONS
} else {
@ -59,10 +59,22 @@ class SharedPreferencesUiStateRepository @Inject constructor(
}
}
override fun storeSelectedSpace(roomId: String?, sessionId: String) {
sharedPreferences.edit {
putString("$KEY_SELECTED_SPACE@$sessionId", roomId)
}
}
override fun getSelectedSpace(sessionId: String): String? {
return sharedPreferences.getString("$KEY_SELECTED_SPACE@$sessionId", null)
}
companion object {
private const val KEY_DISPLAY_MODE = "UI_STATE_DISPLAY_MODE"
private const val VALUE_DISPLAY_MODE_CATCHUP = 0
private const val VALUE_DISPLAY_MODE_PEOPLE = 1
private const val VALUE_DISPLAY_MODE_ROOMS = 2
private const val KEY_SELECTED_SPACE = "UI_STATE_SELECTED_SPACE"
}
}

View file

@ -31,4 +31,8 @@ interface UiStateRepository {
fun getDisplayMode(): RoomListDisplayMode
fun storeDisplayMode(displayMode: RoomListDisplayMode)
fun storeSelectedSpace(roomId: String?, sessionId: String)
fun getSelectedSpace(sessionId: String): String?
}

View file

@ -0,0 +1,123 @@
<?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/itemRoomLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?riotx_background">
<FrameLayout
android:id="@+id/roomAvatarContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/roomAvatarImageView"
android:layout_width="56dp"
android:layout_height="56dp"
android:contentDescription="@string/avatar"
tools:src="@tools:sample/avatars" />
</FrameLayout>
<!-- Margin bottom does not work, so I use space -->
<Space
android:id="@+id/roomAvatarBottomSpace"
android:layout_width="0dp"
android:layout_height="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomAvatarContainer"
tools:layout_marginStart="20dp" />
<TextView
android:id="@+id/roomNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:duplicateParentState="true"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@+id/joinSuggestedRoomButton"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/roomAvatarContainer"
app:layout_constraintTop_toTopOf="parent"
tools:text="@sample/matrix.json/data/displayName" />
<TextView
android:id="@+id/suggestedRoomDescription"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toStartOf="@id/joinSuggestedRoomButton"
app:layout_constraintStart_toStartOf="@+id/roomNameView"
app:layout_constraintTop_toBottomOf="@+id/roomNameView"
tools:text="@sample/matrix.json/data/message" />
<!-- Margin bottom does not work, so I use space -->
<Space
android:id="@+id/roomLastEventBottomSpace"
android:layout_width="0dp"
android:layout_height="7dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/roomLastEventView"
tools:layout_marginStart="120dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/joinSuggestedRoomButton"
style="@style/VectorButtonStyleOutlined"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:maxWidth="@dimen/button_max_width"
android:text="@string/join"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/joinSuggestedLoading"
android:visibility="gone"
tools:visibility="visible"
android:layout_width="20dp"
android:layout_height="20dp"
app:layout_constraintBottom_toBottomOf="@id/joinSuggestedRoomButton"
app:layout_constraintEnd_toEndOf="@id/joinSuggestedRoomButton"
app:layout_constraintStart_toStartOf="@id/joinSuggestedRoomButton"
app:layout_constraintTop_toTopOf="@id/joinSuggestedRoomButton" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/roomBottomBarrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="roomAvatarBottomSpace,roomLastEventBottomSpace" />
<View
android:id="@+id/roomDividerView"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="?riotx_header_panel_border_mobile"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/roomBottomBarrier" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -476,6 +476,7 @@
<string name="invitations_header">Invites</string>
<string name="low_priority_header">Low priority</string>
<string name="system_alerts_header">"System Alerts"</string>
<string name="suggested_header">Suggested Rooms</string>
<!-- People fragment -->
<string name="direct_chats_header">Conversations</string>
@ -3291,5 +3292,6 @@
<string name="join_anyway">Join Anyway</string>
<string name="room_alias_preview_not_found">This alias is not accessible at this time.\nTry again later, or ask a room admin to check if you have access.</string>
<string name="suggested_rooms_pills_on_empty_text">Youre not in any rooms yet. Below are some suggested rooms, but you can see more with the green button bottom right.</string>
</resources>