mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
Fix empty states for spaces
This commit is contained in:
parent
201f4c342a
commit
0d0b6a8810
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.core.ui.list
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.ImageViewCompat
|
||||
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.core.extensions.setTextOrHide
|
||||
|
||||
/**
|
||||
* A generic list item to display when there is no results, with an optional CTA
|
||||
*/
|
||||
@EpoxyModelClass(layout = R.layout.item_generic_empty_state)
|
||||
abstract class GenericEmptyWithActionItem : VectorEpoxyModel<GenericEmptyWithActionItem.Holder>() {
|
||||
|
||||
class Action(var title: String) {
|
||||
var perform: Runnable? = null
|
||||
}
|
||||
|
||||
@EpoxyAttribute
|
||||
var title: CharSequence? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var description: CharSequence? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@DrawableRes
|
||||
var iconRes: Int = -1
|
||||
|
||||
@EpoxyAttribute
|
||||
@ColorInt
|
||||
var iconTint: Int? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var buttonAction: Action? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
|
||||
holder.titleText.setTextOrHide(title)
|
||||
holder.descriptionText.setTextOrHide(description)
|
||||
|
||||
if (iconRes != -1) {
|
||||
holder.imageView.setImageResource(iconRes)
|
||||
holder.imageView.isVisible = true
|
||||
if (iconTint != null) {
|
||||
ImageViewCompat.setImageTintList(holder.imageView, ColorStateList.valueOf(iconTint!!))
|
||||
} else {
|
||||
ImageViewCompat.setImageTintList(holder.imageView, null)
|
||||
}
|
||||
} else {
|
||||
holder.imageView.isVisible = false
|
||||
}
|
||||
|
||||
holder.actionButton.setTextOrHide(buttonAction?.title)
|
||||
holder.actionButton.setOnClickListener {
|
||||
buttonAction?.perform?.run()
|
||||
}
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val root by bind<View>(R.id.item_generic_root)
|
||||
val titleText by bind<TextView>(R.id.emptyItemTitleView)
|
||||
val descriptionText by bind<TextView>(R.id.emptyItemMessageView)
|
||||
val imageView by bind<ImageView>(R.id.emptyItemImageView)
|
||||
val actionButton by bind<Button>(R.id.emptyItemButton)
|
||||
}
|
||||
}
|
@ -26,7 +26,8 @@ import im.vector.app.core.epoxy.loadingItem
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.ui.list.genericFooterItem
|
||||
import im.vector.app.core.ui.list.GenericEmptyWithActionItem
|
||||
import im.vector.app.core.ui.list.genericEmptyWithActionItem
|
||||
import im.vector.app.core.ui.list.genericPillItem
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.list.spaceChildInfoItem
|
||||
@ -50,6 +51,7 @@ class SpaceDirectoryController @Inject constructor(
|
||||
fun onSpaceChildClick(spaceChildInfo: SpaceChildInfo)
|
||||
fun onRoomClick(spaceChildInfo: SpaceChildInfo)
|
||||
fun retry()
|
||||
fun addExistingRooms(spaceId: String)
|
||||
}
|
||||
|
||||
var listener: InteractionListener? = null
|
||||
@ -97,9 +99,23 @@ class SpaceDirectoryController @Inject constructor(
|
||||
?: emptyList()
|
||||
|
||||
if (flattenChildInfo.isEmpty()) {
|
||||
genericFooterItem {
|
||||
id("empty_footer")
|
||||
host.stringProvider.getString(R.string.no_result_placeholder)
|
||||
genericEmptyWithActionItem {
|
||||
id("empty_res")
|
||||
title(host.stringProvider.getString(R.string.this_space_has_no_rooms))
|
||||
iconRes(R.drawable.ic_empty_icon_room)
|
||||
iconTint(host.colorProvider.getColorFromAttribute(R.attr.riotx_reaction_background_on))
|
||||
apply {
|
||||
if (data?.canAddRooms == true) {
|
||||
description(host.stringProvider.getString(R.string.this_space_has_no_rooms_admin))
|
||||
val action = GenericEmptyWithActionItem.Action(host.stringProvider.getString(R.string.space_add_existing_rooms))
|
||||
action.perform = Runnable {
|
||||
host.listener?.addExistingRooms(data.spaceId)
|
||||
}
|
||||
buttonAction(action)
|
||||
} else {
|
||||
description(host.stringProvider.getString(R.string.this_space_has_no_rooms_not_admin))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
flattenChildInfo.forEach { info ->
|
||||
|
@ -19,6 +19,8 @@ package im.vector.app.features.spaces.explore
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
@ -26,9 +28,12 @@ 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.extensions.registerStartForActivityResult
|
||||
import im.vector.app.core.platform.OnBackPressed
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding
|
||||
import im.vector.app.features.spaces.manage.ManageType
|
||||
import im.vector.app.features.spaces.manage.SpaceManageActivity
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||
import javax.inject.Inject
|
||||
@ -44,6 +49,8 @@ class SpaceDirectoryFragment @Inject constructor(
|
||||
SpaceDirectoryController.InteractionListener,
|
||||
OnBackPressed {
|
||||
|
||||
override fun getMenuRes() = R.menu.menu_space_directory
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
|
||||
FragmentRoomDirectoryPickerBinding.inflate(layoutInflater, container, false)
|
||||
|
||||
@ -60,6 +67,10 @@ class SpaceDirectoryFragment @Inject constructor(
|
||||
}
|
||||
epoxyController.listener = this
|
||||
views.roomDirectoryPickerList.configureWith(epoxyController)
|
||||
|
||||
viewModel.selectSubscribe(this, SpaceDirectoryState::canAddRooms) {
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@ -77,6 +88,32 @@ class SpaceDirectoryFragment @Inject constructor(
|
||||
views.toolbar.title = title
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) = withState(viewModel) { state ->
|
||||
menu.findItem(R.id.spaceAddRoom)?.let {
|
||||
it.isVisible = state.canAddRooms
|
||||
}
|
||||
menu.findItem(R.id.spaceCreateRoom)?.let {
|
||||
it.isVisible = false // Not yet implemented
|
||||
}
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.spaceAddRoom -> {
|
||||
withState(viewModel) { state ->
|
||||
addExistingRooms(state.spaceId)
|
||||
}
|
||||
return true
|
||||
}
|
||||
R.id.spaceCreateRoom -> {
|
||||
// not implemented yet
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onButtonClick(spaceChildInfo: SpaceChildInfo) {
|
||||
viewModel.handle(SpaceDirectoryViewAction.JoinOrOpen(spaceChildInfo))
|
||||
}
|
||||
@ -97,6 +134,14 @@ class SpaceDirectoryFragment @Inject constructor(
|
||||
override fun retry() {
|
||||
viewModel.handle(SpaceDirectoryViewAction.Retry)
|
||||
}
|
||||
|
||||
private val addExistingRoomActivityResult = registerStartForActivityResult { activityResult ->
|
||||
viewModel.handle(SpaceDirectoryViewAction.Retry)
|
||||
}
|
||||
|
||||
override fun addExistingRooms(spaceId: String) {
|
||||
addExistingRoomActivityResult.launch(SpaceManageActivity.newIntent(requireContext(), spaceId, ManageType.AddRooms))
|
||||
}
|
||||
// override fun navigateToRoom(roomId: String) {
|
||||
// viewModel.handle(SpaceDirectoryViewAction.NavigateToRoom(roomId))
|
||||
// }
|
||||
|
@ -36,7 +36,8 @@ data class SpaceDirectoryState(
|
||||
// Set of joined roomId / spaces,
|
||||
val joinedRoomsIds: Set<String> = emptySet(),
|
||||
// keys are room alias or roomId
|
||||
val changeMembershipStates: Map<String, ChangeMembershipState> = emptyMap()
|
||||
val changeMembershipStates: Map<String, ChangeMembershipState> = emptyMap(),
|
||||
val canAddRooms: Boolean = false
|
||||
) : MvRxState {
|
||||
constructor(args: SpaceDirectoryArgs) : this(
|
||||
spaceId = args.spaceId
|
||||
|
@ -28,12 +28,15 @@ import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.rx.rx
|
||||
import timber.log.Timber
|
||||
@ -70,6 +73,23 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
|
||||
refreshFromApi()
|
||||
observeJoinedRooms()
|
||||
observeMembershipChanges()
|
||||
observePermissions()
|
||||
}
|
||||
|
||||
private fun observePermissions() {
|
||||
val room = session.getRoom(initialState.spaceId) ?: return
|
||||
|
||||
val powerLevelsContentLive = PowerLevelsObservableFactory(room).createObservable()
|
||||
|
||||
powerLevelsContentLive
|
||||
.subscribe {
|
||||
val powerLevelsHelper = PowerLevelsHelper(it)
|
||||
setState {
|
||||
copy(canAddRooms = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true,
|
||||
EventType.STATE_SPACE_CHILD))
|
||||
}
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun refreshFromApi() {
|
||||
|
@ -19,9 +19,12 @@ package im.vector.app.features.spaces.manage
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Incomplete
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.errorWithRetryItem
|
||||
import im.vector.app.core.epoxy.loadingItem
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.ui.list.genericFooterItem
|
||||
import im.vector.app.core.utils.DebouncedClickListener
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
@ -31,7 +34,8 @@ import javax.inject.Inject
|
||||
|
||||
class SpaceManageRoomsController @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val errorFormatter: ErrorFormatter
|
||||
private val errorFormatter: ErrorFormatter,
|
||||
private val stringProvider: StringProvider
|
||||
) : TypedEpoxyController<SpaceManageRoomViewState>() {
|
||||
|
||||
interface Listener {
|
||||
@ -67,17 +71,24 @@ class SpaceManageRoomsController @Inject constructor(
|
||||
matchFilter.filter = data.currentFilter
|
||||
val filteredResult = directChildren.filter { matchFilter.test(it) }
|
||||
|
||||
filteredResult.forEach { childInfo ->
|
||||
roomManageSelectionItem {
|
||||
id(childInfo.childRoomId)
|
||||
matrixItem(childInfo.toMatrixItem())
|
||||
avatarRenderer(host.avatarRenderer)
|
||||
suggested(childInfo.suggested ?: false)
|
||||
space(childInfo.roomType == RoomType.SPACE)
|
||||
selected(data.selectedRooms.contains(childInfo.childRoomId))
|
||||
itemClickListener(DebouncedClickListener({
|
||||
host.listener?.toggleSelection(childInfo)
|
||||
}))
|
||||
if (filteredResult.isEmpty()) {
|
||||
genericFooterItem {
|
||||
id("empty_result")
|
||||
text(host.stringProvider.getString(R.string.no_result_placeholder))
|
||||
}
|
||||
} else {
|
||||
filteredResult.forEach { childInfo ->
|
||||
roomManageSelectionItem {
|
||||
id(childInfo.childRoomId)
|
||||
matrixItem(childInfo.toMatrixItem())
|
||||
avatarRenderer(host.avatarRenderer)
|
||||
suggested(childInfo.suggested ?: false)
|
||||
space(childInfo.roomType == RoomType.SPACE)
|
||||
selected(data.selectedRooms.contains(childInfo.childRoomId))
|
||||
itemClickListener(DebouncedClickListener({
|
||||
host.listener?.toggleSelection(childInfo)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
vector/src/main/res/drawable/ic_empty_icon_room.xml
Normal file
13
vector/src/main/res/drawable/ic_empty_icon_room.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:pathData="M21.5187,26.2723H25.8404L26.3357,21.6964H22.014L21.5187,26.2723Z"
|
||||
android:fillColor="#C1C6CD"/>
|
||||
<path
|
||||
android:pathData="M44,24C44,35.0457 35.0457,44 24,44C12.9543,44 4,35.0457 4,24C4,12.9543 12.9543,4 24,4C35.0457,4 44,12.9543 44,24ZM21.0505,12.0116C22.1487,12.1305 22.9425,13.1171 22.8237,14.2152L22.4469,17.6964H26.7686L27.192,13.7848C27.3109,12.6866 28.2974,11.8928 29.3956,12.0116C30.4938,12.1305 31.2876,13.1171 31.1688,14.2152L30.792,17.6964H32.6C33.7046,17.6964 34.6,18.5918 34.6,19.6964C34.6,20.801 33.7046,21.6964 32.6,21.6964H30.3591L29.8638,26.2723H32.6C33.7046,26.2723 34.6,27.1677 34.6,28.2723C34.6,29.3769 33.7046,30.2723 32.6,30.2723H29.4308L29.0041,34.2152C28.8852,35.3134 27.8986,36.1072 26.8005,35.9884C25.7023,35.8695 24.9084,34.8829 25.0273,33.7848L25.4075,30.2723H21.0857L20.659,34.2152C20.5401,35.3134 19.5535,36.1072 18.4554,35.9884C17.3572,35.8695 16.5633,34.8829 16.6822,33.7848L17.0624,30.2723H15C13.8954,30.2723 13,29.3769 13,28.2723C13,27.1677 13.8954,26.2723 15,26.2723H17.4953L17.9906,21.6964H15.8784C14.7739,21.6964 13.8784,20.801 13.8784,19.6964C13.8784,18.5918 14.7739,17.6964 15.8784,17.6964H18.4235L18.8469,13.7848C18.9658,12.6866 19.9524,11.8928 21.0505,12.0116Z"
|
||||
android:fillColor="#C1C6CD"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
@ -5,7 +5,7 @@
|
||||
android:id="@+id/coordinatorLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?riotx_header_panel_background">
|
||||
android:background="?riotx_background">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
|
70
vector/src/main/res/layout/item_generic_empty_state.xml
Normal file
70
vector/src/main/res/layout/item_generic_empty_state.xml
Normal file
@ -0,0 +1,70 @@
|
||||
<?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:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/emptyItemImageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:tint="?riotx_reaction_background_off"
|
||||
app:layout_constraintBottom_toTopOf="@id/emptyItemTitleView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_empty_space_explore" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/emptyItemTitleView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="30dp"
|
||||
android:gravity="center"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@id/emptyItemMessageView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/emptyItemImageView"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="@string/this_space_has_no_rooms" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/emptyItemMessageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="20dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxWidth="300dp"
|
||||
android:maxLines="10"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/emptyItemButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/emptyItemTitleView"
|
||||
tools:text="@string/this_space_has_no_rooms_admin" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/emptyItemButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="16dp"
|
||||
android:minWidth="190dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/emptyItemMessageView"
|
||||
tools:text="@string/space_add_existing_rooms" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
15
vector/src/main/res/menu/menu_space_directory.xml
Normal file
15
vector/src/main/res/menu/menu_space_directory.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?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/spaceAddRoom"
|
||||
android:title="@string/space_add_existing_rooms"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/spaceCreateRoom"
|
||||
android:title="@string/create_new_room"
|
||||
app:iconTint="?attr/colorAccent"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
@ -3356,6 +3356,7 @@
|
||||
|
||||
|
||||
<string name="space_add_existing_rooms">Add existing rooms and space</string>
|
||||
<string name="space_add_rooms">Add rooms</string>
|
||||
<string name="spaces_beta_welcome_to_spaces">Welcome to Spaces!</string>
|
||||
<string name="spaces_beta_welcome_to_spaces_desc">Spaces are a new way to group rooms and people.</string>
|
||||
<string name="you_are_invited">You are invited</string>
|
||||
@ -3377,5 +3378,10 @@
|
||||
<string name="labs_space_show_orphan_in_home">Experimental Space - Only show orphans in Home</string>
|
||||
<string name="spaces_feeling_experimental_subspace">Feeling experimental?\nYou can add existing spaces to a space.</string>
|
||||
<string name="spaces_no_server_support_title">It looks like your homeserver does not support Spaces yet</string>
|
||||
<string name="spaces_no_server_support_description">Please contact your homserver admin for further information</string>
|
||||
<string name="spaces_no_server_support_description">Please contact your homeserver admin for further information</string>
|
||||
|
||||
<string name="this_space_has_no_rooms">This space has no rooms</string>
|
||||
<string name="this_space_has_no_rooms_not_admin">Some rooms may be hidden because they’re private and you need an invite.\nYou don’t have permission to add rooms.</string>
|
||||
<string name="this_space_has_no_rooms_admin">Some rooms may be hidden because they’re private and you need an invite.</string>
|
||||
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user