Merge branch 'develop' into feature/bca/spaces_quick_fixes

This commit is contained in:
Benoit Marty 2021-05-21 14:49:12 +02:00 committed by GitHub
commit 697b9ff535
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 412 additions and 78 deletions

View File

@ -5,6 +5,6 @@
- [ ] Changes has been tested on an Android device or Android emulator with API 21
- [ ] UI change has been tested on both light and dark themes
- [ ] Pull request is based on the develop branch
- [ ] Pull request updates [CHANGES.md](https://github.com/vector-im/element-android/blob/develop/CHANGES.md)
- [ ] Pull request includes a new file under ./newsfragment. See https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md#changelog
- [ ] Pull request includes screenshots or videos if containing UI changes
- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md#sign-off)

View File

@ -16,6 +16,7 @@ Bugfix 🐛:
- Spaces | Personal spaces add DM - Web Parity (#3271)
- Spaces | Improve 'Leave Space' UX/UI (#3359)
- Don't create private spaces with encryption enabled (#3363)
- #+ button on lower right when looking at an empty space goes to an empty 'Explore rooms' (#3327)
Translations 🗣:
-
@ -1382,36 +1383,3 @@ Changes in RiotX 0.1.0 (2019-07-11)
First release!
Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-android-b17952e8f771
=======================================================
+ TEMPLATE WHEN PREPARING A NEW RELEASE +
=======================================================
Changes in Element 1.1.X (2021-XX-XX)
===================================================
Features ✨:
-
Improvements 🙌:
-
Bugfix 🐛:
-
Translations 🗣:
-
SDK API changes ⚠️:
-
Build 🧱:
-
Test:
-
Other changes:
-

View File

@ -51,9 +51,21 @@ If an issue does not exist yet, it may be relevant to open a new issue and let u
This project is full Kotlin. Please do not write Java classes.
### CHANGES.md
### Changelog
Please add a line to the top of the file `CHANGES.md` describing your change.
Please create at least one file under ./newsfragment containing details about your change. Towncrier will be used when preparing the release.
Towncrier says to use the PR number for the filename, but the issue number is also fine.
Supported filename extensions are:
- ``.feature``: Signifying a new feature in Element Android or in the Matrix SDK.
- ``.bugfix``: Signifying a bug fix.
- ``.doc``: Signifying a documentation improvement.
- ``.removal``: Signifying a deprecation or removal of public API. Can be used to notifying about API change in the Matrix SDK
- ``.misc``: A ticket has been closed, but it is not of interest to users. Note that in this case, the content of the file will not be output, but just the issue/PR number.
See https://github.com/twisted/towncrier#news-fragments if you need more details.
### Code quality

View File

@ -47,14 +47,15 @@ import java.io.FileNotFoundException
import java.io.IOException
import javax.inject.Inject
internal class FileUploader @Inject constructor(@Authenticated
private val okHttpClient: OkHttpClient,
private val globalErrorReceiver: GlobalErrorReceiver,
private val homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService,
private val context: Context,
private val temporaryFileCreator: TemporaryFileCreator,
contentUrlResolver: ContentUrlResolver,
moshi: Moshi) {
internal class FileUploader @Inject constructor(
@Authenticated private val okHttpClient: OkHttpClient,
private val globalErrorReceiver: GlobalErrorReceiver,
private val homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService,
private val context: Context,
private val temporaryFileCreator: TemporaryFileCreator,
contentUrlResolver: ContentUrlResolver,
moshi: Moshi
) {
private val uploadUrl = contentUrlResolver.uploadUrl
private val responseAdapter = moshi.adapter(ContentUploadResponse::class.java)
@ -120,11 +121,17 @@ internal class FileUploader @Inject constructor(@Authenticated
}
}
private suspend fun upload(uploadBody: RequestBody, filename: String?, progressListener: ProgressRequestBody.Listener?): ContentUploadResponse {
private suspend fun upload(uploadBody: RequestBody,
filename: String?,
progressListener: ProgressRequestBody.Listener?): ContentUploadResponse {
val urlBuilder = uploadUrl.toHttpUrlOrNull()?.newBuilder() ?: throw RuntimeException()
val httpUrl = urlBuilder
.addQueryParameter("filename", filename)
.apply {
if (filename != null) {
addQueryParameter("filename", filename)
}
}
.build()
val requestBody = if (progressListener != null) ProgressRequestBody(uploadBody, progressListener) else uploadBody

View File

@ -229,7 +229,6 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
val encryptedFile: File?
val contentUploadResponse = if (params.isEncrypted) {
Timber.v("## Encrypt file")
encryptedFile = temporaryFileCreator.create()
.also { filesToDelete.add(it) }
@ -239,16 +238,22 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong())
}
}
Timber.v("## Uploading file")
fileUploader
.uploadFile(encryptedFile, attachment.name, MimeTypes.OctetStream, progressListener)
fileUploader.uploadFile(
file = encryptedFile,
filename = null,
mimeType = MimeTypes.OctetStream,
progressListener = progressListener
)
} else {
Timber.v("## Clear file")
Timber.v("## Uploading clear file")
encryptedFile = null
fileUploader
.uploadFile(fileToUpload, attachment.name, attachment.getSafeMimeType(), progressListener)
fileUploader.uploadFile(
file = fileToUpload,
filename = attachment.name,
mimeType = attachment.getSafeMimeType(),
progressListener = progressListener
)
}
Timber.v("## Update cache storage for ${contentUploadResponse.contentUri}")
@ -312,7 +317,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType)
val contentUploadResponse = fileUploader.uploadByteArray(
byteArray = encryptionResult.encryptedByteArray,
filename = "thumb_${params.attachment.name}",
filename = null,
mimeType = MimeTypes.OctetStream,
progressListener = thumbnailProgressListener
)

1
newsfragment/3293.misc Normal file
View File

@ -0,0 +1 @@
Setup towncrier tool

View File

@ -0,0 +1,47 @@
{% if top_line %}
{{ top_line }}
{{ top_underline * ((top_line)|length)}}
{% elif versiondata.name %}
{{ versiondata.name }} {{ versiondata.version }} ({{ versiondata.date }})
{{ top_underline * ((versiondata.name + versiondata.version + versiondata.date)|length + 4)}}
{% else %}
{{ versiondata.version }} ({{ versiondata.date }})
{{ top_underline * ((versiondata.version + versiondata.date)|length + 3)}}
{% endif %}
{% for section, _ in sections.items() %}
{% set underline = underlines[0] %}{% if section %}{{section}}
{{ underline * section|length }}{% set underline = underlines[1] %}
{% endif %}
{% if sections[section] %}
{% for category, val in definitions.items() if category in sections[section]%}
{% if definitions[category]['name'] == "Features" %}
Features ✨:
{% elif definitions[category]['name'] == "Bugfixes" %}
Bugfixes 🐛:
{% elif definitions[category]['name'] == "Deprecations and Removals" %}
SDK API changes ⚠️:
{% elif definitions[category]['name'] == "Improved Documentation" %}
Improved Documentation 📚:
{% elif definitions[category]['name'] == "Misc" %}
Other changes:
{% else %}
{{ definitions[category]['name'] }}
{% endif %}
{% if definitions[category]['showcontent'] %}
{% for text, values in sections[section][category].items() %}
- {{ text }} ({{ values|join(', ') }})
{% endfor %}
{% else %}
- {{ sections[section][category]['']|join(', ') }}
{% endif %}
{% if sections[section][category]|length == 0 %}
No significant changes.
{% else %}
{% endif %}
{% endfor %}
{% else %}
No significant changes.
{% endif %}
{% endfor %}

View File

@ -23,7 +23,7 @@ branch=${TRAVIS_BRANCH}
# If not on develop, exit, else we cannot get the list of modified files
# It is ok to check only when on develop branch
if [[ "${branch}" -eq 'develop' ]]; then
echo "Check that the file 'CHANGES.md' has been modified"
echo "Check that a file has been added to /newsfragment"
else
echo "Not on develop branch"
exit 0
@ -37,9 +37,9 @@ listOfModifiedFiles=`git diff --name-only HEAD ${branch}`
# echo ${listOfModifiedFiles}
if [[ ${listOfModifiedFiles} = *"CHANGES.md"* ]]; then
echo "CHANGES.md has been modified!"
if [[ ${listOfModifiedFiles} = *"newsfragment"* ]]; then
echo "A file has been added to /newsfragment!"
else
echo "❌ Please add a line describing your change in CHANGES.md"
echo "❌ Please add a file describing your changes in /newsfragment. See https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md#changelog"
exit 1
fi

7
towncrier.toml Normal file
View File

@ -0,0 +1,7 @@
[tool.towncrier]
directory = "newsfragment"
filename = "CHANGES.md"
name = "Changes in Element"
# Note: there is a bug, if I use title_format, the title is printed twice
# title_format = "Changes in Element {version} ({project_date})"
template="tools/towncrier/template.md"

View File

@ -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)
}
}

View File

@ -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 ->

View File

@ -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,28 @@ class SpaceDirectoryFragment @Inject constructor(
views.toolbar.title = title
}
override fun onPrepareOptionsMenu(menu: Menu) = withState(viewModel) { state ->
menu.findItem(R.id.spaceAddRoom)?.isVisible = state.canAddRooms
menu.findItem(R.id.spaceCreateRoom)?.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 +130,14 @@ class SpaceDirectoryFragment @Inject constructor(
override fun retry() {
viewModel.handle(SpaceDirectoryViewAction.Retry)
}
private val addExistingRoomActivityResult = registerStartForActivityResult { _ ->
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))
// }

View File

@ -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

View File

@ -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() {

View File

@ -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)
}))
}
}
}
}

View 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>

View File

@ -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"

View File

@ -0,0 +1,72 @@
<?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:importantForAccessibility="no"
app:layout_constraintBottom_toTopOf="@id/emptyItemTitleView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?riotx_reaction_background_on"
tools:ignore="MissingPrefix"
tools:src="@drawable/ic_empty_icon_room" />
<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>

View 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>

View File

@ -3359,6 +3359,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>
@ -3380,5 +3381,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 theyre private and you need an invite.\nYou dont have permission to add rooms.</string>
<string name="this_space_has_no_rooms_admin">Some rooms may be hidden because theyre private and you need an invite.</string>
</resources>