mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-16 20:10:04 +03:00
Merge branch 'develop' into feature/bca/fix_3371
This commit is contained in:
commit
40bb58c9cb
27 changed files with 540 additions and 96 deletions
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -5,6 +5,6 @@
|
||||||
- [ ] Changes has been tested on an Android device or Android emulator with API 21
|
- [ ] 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
|
- [ ] UI change has been tested on both light and dark themes
|
||||||
- [ ] Pull request is based on the develop branch
|
- [ ] 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 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)
|
- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md#sign-off)
|
||||||
|
|
37
CHANGES.md
37
CHANGES.md
|
@ -14,6 +14,10 @@ Bugfix 🐛:
|
||||||
- Implement a workaround to render <del> and <u> in the timeline (#1817)
|
- Implement a workaround to render <del> and <u> in the timeline (#1817)
|
||||||
- Make sure the SDK can retrieve the secret storage if the system is upgraded (#3304)
|
- Make sure the SDK can retrieve the secret storage if the system is upgraded (#3304)
|
||||||
- Spaces | Explore room list: the RoomId is displayed instead of name (#3371)
|
- Spaces | Explore room list: the RoomId is displayed instead of name (#3371)
|
||||||
|
- 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 🗣:
|
Translations 🗣:
|
||||||
-
|
-
|
||||||
|
@ -1380,36 +1384,3 @@ Changes in RiotX 0.1.0 (2019-07-11)
|
||||||
First release!
|
First release!
|
||||||
|
|
||||||
Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-android-b17952e8f771
|
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:
|
|
||||||
-
|
|
||||||
|
|
|
@ -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.
|
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
|
### Code quality
|
||||||
|
|
||||||
|
|
|
@ -47,14 +47,15 @@ import java.io.FileNotFoundException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class FileUploader @Inject constructor(@Authenticated
|
internal class FileUploader @Inject constructor(
|
||||||
private val okHttpClient: OkHttpClient,
|
@Authenticated private val okHttpClient: OkHttpClient,
|
||||||
private val globalErrorReceiver: GlobalErrorReceiver,
|
private val globalErrorReceiver: GlobalErrorReceiver,
|
||||||
private val homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService,
|
private val homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val temporaryFileCreator: TemporaryFileCreator,
|
private val temporaryFileCreator: TemporaryFileCreator,
|
||||||
contentUrlResolver: ContentUrlResolver,
|
contentUrlResolver: ContentUrlResolver,
|
||||||
moshi: Moshi) {
|
moshi: Moshi
|
||||||
|
) {
|
||||||
|
|
||||||
private val uploadUrl = contentUrlResolver.uploadUrl
|
private val uploadUrl = contentUrlResolver.uploadUrl
|
||||||
private val responseAdapter = moshi.adapter(ContentUploadResponse::class.java)
|
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 urlBuilder = uploadUrl.toHttpUrlOrNull()?.newBuilder() ?: throw RuntimeException()
|
||||||
|
|
||||||
val httpUrl = urlBuilder
|
val httpUrl = urlBuilder
|
||||||
.addQueryParameter("filename", filename)
|
.apply {
|
||||||
|
if (filename != null) {
|
||||||
|
addQueryParameter("filename", filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val requestBody = if (progressListener != null) ProgressRequestBody(uploadBody, progressListener) else uploadBody
|
val requestBody = if (progressListener != null) ProgressRequestBody(uploadBody, progressListener) else uploadBody
|
||||||
|
|
|
@ -229,7 +229,6 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
val encryptedFile: File?
|
val encryptedFile: File?
|
||||||
val contentUploadResponse = if (params.isEncrypted) {
|
val contentUploadResponse = if (params.isEncrypted) {
|
||||||
Timber.v("## Encrypt file")
|
Timber.v("## Encrypt file")
|
||||||
|
|
||||||
encryptedFile = temporaryFileCreator.create()
|
encryptedFile = temporaryFileCreator.create()
|
||||||
.also { filesToDelete.add(it) }
|
.also { filesToDelete.add(it) }
|
||||||
|
|
||||||
|
@ -239,16 +238,22 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong())
|
contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.v("## Uploading file")
|
Timber.v("## Uploading file")
|
||||||
|
fileUploader.uploadFile(
|
||||||
fileUploader
|
file = encryptedFile,
|
||||||
.uploadFile(encryptedFile, attachment.name, MimeTypes.OctetStream, progressListener)
|
filename = null,
|
||||||
|
mimeType = MimeTypes.OctetStream,
|
||||||
|
progressListener = progressListener
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Timber.v("## Clear file")
|
Timber.v("## Uploading clear file")
|
||||||
encryptedFile = null
|
encryptedFile = null
|
||||||
fileUploader
|
fileUploader.uploadFile(
|
||||||
.uploadFile(fileToUpload, attachment.name, attachment.getSafeMimeType(), progressListener)
|
file = fileToUpload,
|
||||||
|
filename = attachment.name,
|
||||||
|
mimeType = attachment.getSafeMimeType(),
|
||||||
|
progressListener = progressListener
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.v("## Update cache storage for ${contentUploadResponse.contentUri}")
|
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 encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType)
|
||||||
val contentUploadResponse = fileUploader.uploadByteArray(
|
val contentUploadResponse = fileUploader.uploadByteArray(
|
||||||
byteArray = encryptionResult.encryptedByteArray,
|
byteArray = encryptionResult.encryptedByteArray,
|
||||||
filename = "thumb_${params.attachment.name}",
|
filename = null,
|
||||||
mimeType = MimeTypes.OctetStream,
|
mimeType = MimeTypes.OctetStream,
|
||||||
progressListener = thumbnailProgressListener
|
progressListener = thumbnailProgressListener
|
||||||
)
|
)
|
||||||
|
|
|
@ -344,10 +344,9 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
if (it != null) addAll(it)
|
if (it != null) addAll(it)
|
||||||
}
|
}
|
||||||
}.distinct()
|
}.distinct()
|
||||||
if (flattenRelated.isEmpty()) {
|
if (flattenRelated.isNotEmpty()) {
|
||||||
dmRoom.flattenParentIds = null
|
// we keep real m.child/m.parent relations and add the one for common memberships
|
||||||
} else {
|
dmRoom.flattenParentIds += "|${flattenRelated.joinToString("|")}|"
|
||||||
dmRoom.flattenParentIds = "|${flattenRelated.joinToString("|")}|"
|
|
||||||
}
|
}
|
||||||
// Timber.v("## SPACES: flatten of ${dmRoom.otherMemberIds.joinToString(",")} is ${dmRoom.flattenParentIds}")
|
// Timber.v("## SPACES: flatten of ${dmRoom.otherMemberIds.joinToString(",")} is ${dmRoom.flattenParentIds}")
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,6 @@ internal class DefaultSpaceService @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
this.preset = CreateRoomPreset.PRESET_PRIVATE_CHAT
|
this.preset = CreateRoomPreset.PRESET_PRIVATE_CHAT
|
||||||
visibility = RoomDirectoryVisibility.PRIVATE
|
visibility = RoomDirectoryVisibility.PRIVATE
|
||||||
enableEncryption()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
1
newsfragment/3293.misc
Normal file
1
newsfragment/3293.misc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Setup towncrier tool
|
47
tools/towncrier/template.md
Normal file
47
tools/towncrier/template.md
Normal 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 %}
|
|
@ -23,7 +23,7 @@ branch=${TRAVIS_BRANCH}
|
||||||
# If not on develop, exit, else we cannot get the list of modified files
|
# If not on develop, exit, else we cannot get the list of modified files
|
||||||
# It is ok to check only when on develop branch
|
# It is ok to check only when on develop branch
|
||||||
if [[ "${branch}" -eq 'develop' ]]; then
|
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
|
else
|
||||||
echo "Not on develop branch"
|
echo "Not on develop branch"
|
||||||
exit 0
|
exit 0
|
||||||
|
@ -37,9 +37,9 @@ listOfModifiedFiles=`git diff --name-only HEAD ${branch}`
|
||||||
# echo ${listOfModifiedFiles}
|
# echo ${listOfModifiedFiles}
|
||||||
|
|
||||||
|
|
||||||
if [[ ${listOfModifiedFiles} = *"CHANGES.md"* ]]; then
|
if [[ ${listOfModifiedFiles} = *"newsfragment"* ]]; then
|
||||||
echo "CHANGES.md has been modified!"
|
echo "A file has been added to /newsfragment!"
|
||||||
else
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
7
towncrier.toml
Normal file
7
towncrier.toml
Normal 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"
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.spaces
|
package im.vector.app.features.spaces
|
||||||
|
|
||||||
|
import android.content.DialogInterface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -27,8 +28,10 @@ import com.airbnb.mvrx.args
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.di.ScreenComponent
|
import im.vector.app.core.di.ScreenComponent
|
||||||
|
import im.vector.app.core.dialogs.withColoredButton
|
||||||
import im.vector.app.core.extensions.setTextOrHide
|
import im.vector.app.core.extensions.setTextOrHide
|
||||||
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
|
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.databinding.BottomSheetSpaceSettingsBinding
|
import im.vector.app.databinding.BottomSheetSpaceSettingsBinding
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.navigation.Navigator
|
import im.vector.app.features.navigation.Navigator
|
||||||
|
@ -43,9 +46,11 @@ import im.vector.app.features.spaces.manage.SpaceManageActivity
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import me.gujun.android.span.span
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
|
import org.matrix.android.sdk.api.session.room.powerlevels.Role
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -55,6 +60,7 @@ data class SpaceBottomSheetSettingsArgs(
|
||||||
val spaceId: String
|
val spaceId: String
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
// XXX make proper view model before leaving beta
|
||||||
class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetSpaceSettingsBinding>() {
|
class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetSpaceSettingsBinding>() {
|
||||||
|
|
||||||
@Inject lateinit var navigator: Navigator
|
@Inject lateinit var navigator: Navigator
|
||||||
|
@ -62,6 +68,7 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
|
||||||
@Inject lateinit var avatarRenderer: AvatarRenderer
|
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||||
@Inject lateinit var vectorPreferences: VectorPreferences
|
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||||
@Inject lateinit var bugReporter: BugReporter
|
@Inject lateinit var bugReporter: BugReporter
|
||||||
|
@Inject lateinit var colorProvider: ColorProvider
|
||||||
|
|
||||||
private val spaceArgs: SpaceBottomSheetSettingsArgs by args()
|
private val spaceArgs: SpaceBottomSheetSettingsArgs by args()
|
||||||
|
|
||||||
|
@ -71,10 +78,14 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
|
||||||
|
|
||||||
var interactionListener: InteractionListener? = null
|
var interactionListener: InteractionListener? = null
|
||||||
|
|
||||||
|
override val showExpanded = true
|
||||||
|
|
||||||
override fun injectWith(injector: ScreenComponent) {
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
injector.inject(this)
|
injector.inject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isLastAdmin: Boolean = false
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetSpaceSettingsBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetSpaceSettingsBinding {
|
||||||
return BottomSheetSpaceSettingsBinding.inflate(inflater, container, false)
|
return BottomSheetSpaceSettingsBinding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
|
@ -108,6 +119,13 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
|
||||||
|
|
||||||
views.invitePeople.isVisible = canInvite || roomSummary?.isPublic.orFalse()
|
views.invitePeople.isVisible = canInvite || roomSummary?.isPublic.orFalse()
|
||||||
views.addRooms.isVisible = canAddChild
|
views.addRooms.isVisible = canAddChild
|
||||||
|
|
||||||
|
val isAdmin = powerLevelsHelper.getUserRole(session.myUserId) is Role.Admin
|
||||||
|
val otherAdminCount = roomSummary?.otherMemberIds
|
||||||
|
?.map { powerLevelsHelper.getUserRole(it) }
|
||||||
|
?.count { it is Role.Admin }
|
||||||
|
?: 0
|
||||||
|
isLastAdmin = isAdmin && otherAdminCount == 0
|
||||||
}.disposeOnDestroyView()
|
}.disposeOnDestroyView()
|
||||||
|
|
||||||
views.spaceBetaTag.setOnClickListener {
|
views.spaceBetaTag.setOnClickListener {
|
||||||
|
@ -138,8 +156,27 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
|
||||||
}
|
}
|
||||||
|
|
||||||
views.leaveSpace.views.bottomSheetActionClickableZone.debouncedClicks {
|
views.leaveSpace.views.bottomSheetActionClickableZone.debouncedClicks {
|
||||||
|
val spaceSummary = activeSessionHolder.getSafeActiveSession()?.getRoomSummary(spaceArgs.spaceId)
|
||||||
|
?: return@debouncedClicks
|
||||||
|
val warningMessage: CharSequence? = if (spaceSummary.otherMemberIds.isEmpty()) {
|
||||||
|
span(getString(R.string.space_leave_prompt_msg_only_you)) {
|
||||||
|
textColor = colorProvider.getColor(R.color.riotx_destructive_accent)
|
||||||
|
}
|
||||||
|
} else if (isLastAdmin) {
|
||||||
|
span(getString(R.string.space_leave_prompt_msg_as_admin)) {
|
||||||
|
textColor = colorProvider.getColor(R.color.riotx_destructive_accent)
|
||||||
|
}
|
||||||
|
} else if (!spaceSummary.isPublic) {
|
||||||
|
span(getString(R.string.space_leave_prompt_msg_private)) {
|
||||||
|
textColor = colorProvider.getColor(R.color.riotx_destructive_accent)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
AlertDialog.Builder(requireContext())
|
AlertDialog.Builder(requireContext())
|
||||||
.setMessage(getString(R.string.space_leave_prompt_msg))
|
.setMessage(warningMessage)
|
||||||
|
.setTitle(getString(R.string.space_leave_prompt_msg))
|
||||||
.setPositiveButton(R.string.leave) { _, _ ->
|
.setPositiveButton(R.string.leave) { _, _ ->
|
||||||
session.coroutineScope.launch {
|
session.coroutineScope.launch {
|
||||||
try {
|
try {
|
||||||
|
@ -152,6 +189,7 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
|
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,8 @@ import im.vector.app.core.epoxy.loadingItem
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
import im.vector.app.core.error.ErrorFormatter
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.resources.StringProvider
|
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.core.ui.list.genericPillItem
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.home.room.list.spaceChildInfoItem
|
import im.vector.app.features.home.room.list.spaceChildInfoItem
|
||||||
|
@ -50,6 +51,7 @@ class SpaceDirectoryController @Inject constructor(
|
||||||
fun onSpaceChildClick(spaceChildInfo: SpaceChildInfo)
|
fun onSpaceChildClick(spaceChildInfo: SpaceChildInfo)
|
||||||
fun onRoomClick(spaceChildInfo: SpaceChildInfo)
|
fun onRoomClick(spaceChildInfo: SpaceChildInfo)
|
||||||
fun retry()
|
fun retry()
|
||||||
|
fun addExistingRooms(spaceId: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
var listener: InteractionListener? = null
|
var listener: InteractionListener? = null
|
||||||
|
@ -97,9 +99,23 @@ class SpaceDirectoryController @Inject constructor(
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
|
|
||||||
if (flattenChildInfo.isEmpty()) {
|
if (flattenChildInfo.isEmpty()) {
|
||||||
genericFooterItem {
|
genericEmptyWithActionItem {
|
||||||
id("empty_footer")
|
id("empty_res")
|
||||||
host.stringProvider.getString(R.string.no_result_placeholder)
|
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 {
|
} else {
|
||||||
flattenChildInfo.forEach { info ->
|
flattenChildInfo.forEach { info ->
|
||||||
|
|
|
@ -19,6 +19,8 @@ package im.vector.app.features.spaces.explore
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.activityViewModel
|
||||||
|
@ -26,9 +28,12 @@ import com.airbnb.mvrx.withState
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
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.OnBackPressed
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding
|
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 kotlinx.parcelize.Parcelize
|
||||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -44,6 +49,8 @@ class SpaceDirectoryFragment @Inject constructor(
|
||||||
SpaceDirectoryController.InteractionListener,
|
SpaceDirectoryController.InteractionListener,
|
||||||
OnBackPressed {
|
OnBackPressed {
|
||||||
|
|
||||||
|
override fun getMenuRes() = R.menu.menu_space_directory
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
|
||||||
FragmentRoomDirectoryPickerBinding.inflate(layoutInflater, container, false)
|
FragmentRoomDirectoryPickerBinding.inflate(layoutInflater, container, false)
|
||||||
|
|
||||||
|
@ -60,6 +67,10 @@ class SpaceDirectoryFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
epoxyController.listener = this
|
epoxyController.listener = this
|
||||||
views.roomDirectoryPickerList.configureWith(epoxyController)
|
views.roomDirectoryPickerList.configureWith(epoxyController)
|
||||||
|
|
||||||
|
viewModel.selectSubscribe(this, SpaceDirectoryState::canAddRooms) {
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
@ -77,6 +88,28 @@ class SpaceDirectoryFragment @Inject constructor(
|
||||||
views.toolbar.title = title
|
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) {
|
override fun onButtonClick(spaceChildInfo: SpaceChildInfo) {
|
||||||
viewModel.handle(SpaceDirectoryViewAction.JoinOrOpen(spaceChildInfo))
|
viewModel.handle(SpaceDirectoryViewAction.JoinOrOpen(spaceChildInfo))
|
||||||
}
|
}
|
||||||
|
@ -97,6 +130,14 @@ class SpaceDirectoryFragment @Inject constructor(
|
||||||
override fun retry() {
|
override fun retry() {
|
||||||
viewModel.handle(SpaceDirectoryViewAction.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) {
|
// override fun navigateToRoom(roomId: String) {
|
||||||
// viewModel.handle(SpaceDirectoryViewAction.NavigateToRoom(roomId))
|
// viewModel.handle(SpaceDirectoryViewAction.NavigateToRoom(roomId))
|
||||||
// }
|
// }
|
||||||
|
|
|
@ -36,7 +36,8 @@ data class SpaceDirectoryState(
|
||||||
// Set of joined roomId / spaces,
|
// Set of joined roomId / spaces,
|
||||||
val joinedRoomsIds: Set<String> = emptySet(),
|
val joinedRoomsIds: Set<String> = emptySet(),
|
||||||
// keys are room alias or roomId
|
// keys are room alias or roomId
|
||||||
val changeMembershipStates: Map<String, ChangeMembershipState> = emptyMap()
|
val changeMembershipStates: Map<String, ChangeMembershipState> = emptyMap(),
|
||||||
|
val canAddRooms: Boolean = false
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
constructor(args: SpaceDirectoryArgs) : this(
|
constructor(args: SpaceDirectoryArgs) : this(
|
||||||
spaceId = args.spaceId
|
spaceId = args.spaceId
|
||||||
|
|
|
@ -28,12 +28,15 @@ import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.Session
|
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.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
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.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.api.session.room.roomSummaryQueryParams
|
||||||
import org.matrix.android.sdk.rx.rx
|
import org.matrix.android.sdk.rx.rx
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -70,6 +73,23 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
|
||||||
refreshFromApi()
|
refreshFromApi()
|
||||||
observeJoinedRooms()
|
observeJoinedRooms()
|
||||||
observeMembershipChanges()
|
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() {
|
private fun refreshFromApi() {
|
||||||
|
|
|
@ -62,6 +62,22 @@ class AddRoomListController @Inject constructor(
|
||||||
|
|
||||||
var initialLoadOccurred = false
|
var initialLoadOccurred = false
|
||||||
|
|
||||||
|
var expanded: Boolean = true
|
||||||
|
set(value) {
|
||||||
|
if (value != field) {
|
||||||
|
field = value
|
||||||
|
requestForcedModelBuild()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var disabled: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
if (value != field) {
|
||||||
|
field = value
|
||||||
|
requestForcedModelBuild()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun boundaryChange(boundary: ResultBoundaries) {
|
fun boundaryChange(boundary: ResultBoundaries) {
|
||||||
val boundaryHasLoadedSomething = boundary.frontLoaded || boundary.zeroItemLoaded
|
val boundaryHasLoadedSomething = boundary.frontLoaded || boundary.zeroItemLoaded
|
||||||
if (initialLoadOccurred != boundaryHasLoadedSomething) {
|
if (initialLoadOccurred != boundaryHasLoadedSomething) {
|
||||||
|
@ -88,6 +104,10 @@ class AddRoomListController @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addModels(models: List<EpoxyModel<*>>) {
|
override fun addModels(models: List<EpoxyModel<*>>) {
|
||||||
|
if (disabled) {
|
||||||
|
super.addModels(emptyList())
|
||||||
|
return
|
||||||
|
}
|
||||||
val host = this
|
val host = this
|
||||||
val filteredModel = if (ignoreRooms == null) {
|
val filteredModel = if (ignoreRooms == null) {
|
||||||
models
|
models
|
||||||
|
@ -102,10 +122,13 @@ class AddRoomListController @Inject constructor(
|
||||||
RoomCategoryItem_().apply {
|
RoomCategoryItem_().apply {
|
||||||
id("header")
|
id("header")
|
||||||
title(host.sectionName ?: "")
|
title(host.sectionName ?: "")
|
||||||
expanded(true)
|
expanded(host.expanded)
|
||||||
|
listener {
|
||||||
|
host.expanded = !host.expanded
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (subHeaderText != null) {
|
if (expanded && subHeaderText != null) {
|
||||||
add(
|
add(
|
||||||
GenericPillItem_().apply {
|
GenericPillItem_().apply {
|
||||||
id("sub_header")
|
id("sub_header")
|
||||||
|
@ -115,11 +138,13 @@ class AddRoomListController @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.addModels(filteredModel)
|
if (expanded) {
|
||||||
if (!initialLoadOccurred) {
|
super.addModels(filteredModel)
|
||||||
add(
|
if (!initialLoadOccurred) {
|
||||||
RoomSelectionPlaceHolderItem_().apply { id("loading") }
|
add(
|
||||||
)
|
RoomSelectionPlaceHolderItem_().apply { id("loading") }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +154,7 @@ class AddRoomListController @Inject constructor(
|
||||||
return RoomSelectionItem_().apply {
|
return RoomSelectionItem_().apply {
|
||||||
id(item.roomId)
|
id(item.roomId)
|
||||||
matrixItem(item.toMatrixItem())
|
matrixItem(item.toMatrixItem())
|
||||||
avatarRenderer(this@AddRoomListController.avatarRenderer)
|
avatarRenderer(host.avatarRenderer)
|
||||||
space(item.roomType == RoomType.SPACE)
|
space(item.roomType == RoomType.SPACE)
|
||||||
selected(host.selectedItems[item.roomId] ?: false)
|
selected(host.selectedItems[item.roomId] ?: false)
|
||||||
itemClickListener(DebouncedClickListener({
|
itemClickListener(DebouncedClickListener({
|
||||||
|
|
|
@ -42,6 +42,7 @@ import javax.inject.Inject
|
||||||
class SpaceAddRoomFragment @Inject constructor(
|
class SpaceAddRoomFragment @Inject constructor(
|
||||||
private val spaceEpoxyController: AddRoomListController,
|
private val spaceEpoxyController: AddRoomListController,
|
||||||
private val roomEpoxyController: AddRoomListController,
|
private val roomEpoxyController: AddRoomListController,
|
||||||
|
private val dmEpoxyController: AddRoomListController,
|
||||||
private val viewModelFactory: SpaceAddRoomsViewModel.Factory
|
private val viewModelFactory: SpaceAddRoomsViewModel.Factory
|
||||||
) : VectorBaseFragment<FragmentSpaceAddRoomsBinding>(),
|
) : VectorBaseFragment<FragmentSpaceAddRoomsBinding>(),
|
||||||
OnBackPressed, AddRoomListController.Listener, SpaceAddRoomsViewModel.Factory {
|
OnBackPressed, AddRoomListController.Listener, SpaceAddRoomsViewModel.Factory {
|
||||||
|
@ -84,6 +85,7 @@ class SpaceAddRoomFragment @Inject constructor(
|
||||||
viewModel.selectionListLiveData.observe(viewLifecycleOwner) {
|
viewModel.selectionListLiveData.observe(viewLifecycleOwner) {
|
||||||
spaceEpoxyController.selectedItems = it
|
spaceEpoxyController.selectedItems = it
|
||||||
roomEpoxyController.selectedItems = it
|
roomEpoxyController.selectedItems = it
|
||||||
|
dmEpoxyController.selectedItems = it
|
||||||
saveNeeded = it.values.any { it }
|
saveNeeded = it.values.any { it }
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
@ -95,6 +97,7 @@ class SpaceAddRoomFragment @Inject constructor(
|
||||||
viewModel.selectSubscribe(this, SpaceAddRoomsState::ignoreRooms) {
|
viewModel.selectSubscribe(this, SpaceAddRoomsState::ignoreRooms) {
|
||||||
spaceEpoxyController.ignoreRooms = it
|
spaceEpoxyController.ignoreRooms = it
|
||||||
roomEpoxyController.ignoreRooms = it
|
roomEpoxyController.ignoreRooms = it
|
||||||
|
dmEpoxyController.ignoreRooms = it
|
||||||
}.disposeOnDestroyView()
|
}.disposeOnDestroyView()
|
||||||
|
|
||||||
viewModel.selectSubscribe(this, SpaceAddRoomsState::isSaving) {
|
viewModel.selectSubscribe(this, SpaceAddRoomsState::isSaving) {
|
||||||
|
@ -105,6 +108,10 @@ class SpaceAddRoomFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}.disposeOnDestroyView()
|
}.disposeOnDestroyView()
|
||||||
|
|
||||||
|
viewModel.selectSubscribe(this, SpaceAddRoomsState::shouldShowDMs) {
|
||||||
|
dmEpoxyController.disabled = !it
|
||||||
|
}.disposeOnDestroyView()
|
||||||
|
|
||||||
views.createNewRoom.debouncedClicks {
|
views.createNewRoom.debouncedClicks {
|
||||||
sharedViewModel.handle(SpaceManagedSharedAction.CreateRoom)
|
sharedViewModel.handle(SpaceManagedSharedAction.CreateRoom)
|
||||||
}
|
}
|
||||||
|
@ -121,11 +128,11 @@ class SpaceAddRoomFragment @Inject constructor(
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
is SpaceAddRoomsViewEvents.SaveFailed -> {
|
is SpaceAddRoomsViewEvents.SaveFailed -> {
|
||||||
showErrorInSnackbar(it.reason)
|
showErrorInSnackbar(it.reason)
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
SpaceAddRoomsViewEvents.SavedDone -> {
|
SpaceAddRoomsViewEvents.SavedDone -> {
|
||||||
sharedViewModel.handle(SpaceManagedSharedAction.HandleBack)
|
sharedViewModel.handle(SpaceManagedSharedAction.HandleBack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,6 +156,7 @@ class SpaceAddRoomFragment @Inject constructor(
|
||||||
views.roomList.cleanup()
|
views.roomList.cleanup()
|
||||||
spaceEpoxyController.listener = null
|
spaceEpoxyController.listener = null
|
||||||
roomEpoxyController.listener = null
|
roomEpoxyController.listener = null
|
||||||
|
dmEpoxyController.listener = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +189,19 @@ class SpaceAddRoomFragment @Inject constructor(
|
||||||
concatAdapter.addAdapter(roomEpoxyController.adapter)
|
concatAdapter.addAdapter(roomEpoxyController.adapter)
|
||||||
concatAdapter.addAdapter(spaceEpoxyController.adapter)
|
concatAdapter.addAdapter(spaceEpoxyController.adapter)
|
||||||
|
|
||||||
|
// This controller can be disabled depending on the space type (public or not)
|
||||||
|
viewModel.updatableDMLivePageResult.liveBoundaries.observe(viewLifecycleOwner) {
|
||||||
|
dmEpoxyController.boundaryChange(it)
|
||||||
|
}
|
||||||
|
viewModel.updatableDMLivePageResult.livePagedList.observe(viewLifecycleOwner) {
|
||||||
|
dmEpoxyController.totalSize = it.size
|
||||||
|
dmEpoxyController.submitList(it)
|
||||||
|
}
|
||||||
|
dmEpoxyController.sectionName = getString(R.string.direct_chats_header)
|
||||||
|
dmEpoxyController.listener = this
|
||||||
|
|
||||||
|
concatAdapter.addAdapter(dmEpoxyController.adapter)
|
||||||
|
|
||||||
views.roomList.adapter = concatAdapter
|
views.roomList.adapter = concatAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,8 @@ data class SpaceAddRoomsState(
|
||||||
val currentFilter: String = "",
|
val currentFilter: String = "",
|
||||||
val spaceName: String = "",
|
val spaceName: String = "",
|
||||||
val ignoreRooms: List<String> = emptyList(),
|
val ignoreRooms: List<String> = emptyList(),
|
||||||
val isSaving: Async<List<String>> = Uninitialized
|
val isSaving: Async<List<String>> = Uninitialized,
|
||||||
|
val shouldShowDMs : Boolean = false
|
||||||
// val selectionList: Map<String, Boolean> = emptyMap()
|
// val selectionList: Map<String, Boolean> = emptyMap()
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
constructor(args: SpaceManageArgs) : this(
|
constructor(args: SpaceManageArgs) : this(
|
||||||
|
|
|
@ -98,6 +98,26 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val updatableDMLivePageResult: UpdatableLivePageResult by lazy {
|
||||||
|
session.getFilteredPagedRoomSummariesLive(
|
||||||
|
roomSummaryQueryParams {
|
||||||
|
this.memberships = listOf(Membership.JOIN)
|
||||||
|
this.excludeType = listOf(RoomType.SPACE)
|
||||||
|
this.includeType = null
|
||||||
|
this.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||||
|
this.activeSpaceFilter = ActiveSpaceFilter.ExcludeSpace(initialState.spaceId)
|
||||||
|
this.displayName = QueryStringValue.Contains(initialState.currentFilter, QueryStringValue.Case.INSENSITIVE)
|
||||||
|
},
|
||||||
|
pagedListConfig = PagedList.Config.Builder()
|
||||||
|
.setPageSize(10)
|
||||||
|
.setInitialLoadSizeHint(20)
|
||||||
|
.setEnablePlaceholders(true)
|
||||||
|
.setPrefetchDistance(10)
|
||||||
|
.build(),
|
||||||
|
sortOrder = RoomSortOrder.NAME
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private val selectionList = mutableMapOf<String, Boolean>()
|
private val selectionList = mutableMapOf<String, Boolean>()
|
||||||
val selectionListLiveData = MutableLiveData<Map<String, Boolean>>()
|
val selectionListLiveData = MutableLiveData<Map<String, Boolean>>()
|
||||||
|
|
||||||
|
@ -106,7 +126,8 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
spaceName = spaceSummary?.displayName ?: "",
|
spaceName = spaceSummary?.displayName ?: "",
|
||||||
ignoreRooms = (spaceSummary?.flattenParentIds ?: emptyList()) + listOf(initialState.spaceId)
|
ignoreRooms = (spaceSummary?.flattenParentIds ?: emptyList()) + listOf(initialState.spaceId),
|
||||||
|
shouldShowDMs = spaceSummary?.isPublic == false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,12 @@ package im.vector.app.features.spaces.manage
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.Incomplete
|
import com.airbnb.mvrx.Incomplete
|
||||||
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.errorWithRetryItem
|
import im.vector.app.core.epoxy.errorWithRetryItem
|
||||||
import im.vector.app.core.epoxy.loadingItem
|
import im.vector.app.core.epoxy.loadingItem
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
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.core.utils.DebouncedClickListener
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
|
@ -31,7 +34,8 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class SpaceManageRoomsController @Inject constructor(
|
class SpaceManageRoomsController @Inject constructor(
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val errorFormatter: ErrorFormatter
|
private val errorFormatter: ErrorFormatter,
|
||||||
|
private val stringProvider: StringProvider
|
||||||
) : TypedEpoxyController<SpaceManageRoomViewState>() {
|
) : TypedEpoxyController<SpaceManageRoomViewState>() {
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
|
@ -67,17 +71,24 @@ class SpaceManageRoomsController @Inject constructor(
|
||||||
matchFilter.filter = data.currentFilter
|
matchFilter.filter = data.currentFilter
|
||||||
val filteredResult = directChildren.filter { matchFilter.test(it) }
|
val filteredResult = directChildren.filter { matchFilter.test(it) }
|
||||||
|
|
||||||
filteredResult.forEach { childInfo ->
|
if (filteredResult.isEmpty()) {
|
||||||
roomManageSelectionItem {
|
genericFooterItem {
|
||||||
id(childInfo.childRoomId)
|
id("empty_result")
|
||||||
matrixItem(childInfo.toMatrixItem())
|
text(host.stringProvider.getString(R.string.no_result_placeholder))
|
||||||
avatarRenderer(host.avatarRenderer)
|
}
|
||||||
suggested(childInfo.suggested ?: false)
|
} else {
|
||||||
space(childInfo.roomType == RoomType.SPACE)
|
filteredResult.forEach { childInfo ->
|
||||||
selected(data.selectedRooms.contains(childInfo.childRoomId))
|
roomManageSelectionItem {
|
||||||
itemClickListener(DebouncedClickListener({
|
id(childInfo.childRoomId)
|
||||||
host.listener?.toggleSelection(childInfo)
|
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:id="@+id/coordinatorLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?riotx_header_panel_background">
|
android:background="?riotx_background">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
72
vector/src/main/res/layout/item_generic_empty_state.xml
Normal file
72
vector/src/main/res/layout/item_generic_empty_state.xml
Normal 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>
|
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>
|
|
@ -3353,9 +3353,13 @@
|
||||||
<string name="space_add_child_title">Add rooms</string>
|
<string name="space_add_child_title">Add rooms</string>
|
||||||
<string name="leave_space">Leave Space</string>
|
<string name="leave_space">Leave Space</string>
|
||||||
<string name="space_leave_prompt_msg">Are you sure you want to leave the space?</string>
|
<string name="space_leave_prompt_msg">Are you sure you want to leave the space?</string>
|
||||||
|
<string name="space_leave_prompt_msg_only_you">You are the only person here. If you leave, no one will be able to join in the future, including you.</string>
|
||||||
|
<string name="space_leave_prompt_msg_private">This space is not public. You will not be able to rejoin without an invite.</string>
|
||||||
|
<string name="space_leave_prompt_msg_as_admin">You are admin of this space, ensure that you have transferred admin right to another member before leaving.</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="space_add_existing_rooms">Add existing rooms and space</string>
|
<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">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="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>
|
<string name="you_are_invited">You are invited</string>
|
||||||
|
@ -3377,5 +3381,10 @@
|
||||||
<string name="labs_space_show_orphan_in_home">Experimental Space - Only show orphans in Home</string>
|
<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_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_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>
|
</resources>
|
||||||
|
|
Loading…
Add table
Reference in a new issue