Merge pull request #3419 from vector-im/feature/bma/gitter

Feature/bma/gitter
This commit is contained in:
Benoit Marty 2021-05-27 21:23:05 +02:00 committed by GitHub
commit 0db5dd3d50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 752 additions and 159 deletions

View File

@ -22,16 +22,16 @@ import org.matrix.android.sdk.api.util.JsonDict
@JsonClass(generateAdapter = true)
data class ThirdPartyUser(
/*
Required. A Matrix User ID represting a third party user.
/**
* Required. A Matrix User ID representing a third party user.
*/
@Json(name = "userid") val userId: String,
/*
Required. The protocol ID that the third party location is a part of.
/**
* Required. The protocol ID that the third party location is a part of.
*/
@Json(name = "protocol") val protocol: String,
/*
Required. Information used to identify this third party location.
/**
* Required. Information used to identify this third party location.
*/
@Json(name = "fields") val fields: JsonDict
)

View File

@ -0,0 +1 @@
Allow user to add custom "network" in room search

View File

@ -0,0 +1 @@
Add Gitter.im as a default in the Change Network menu

View File

@ -94,6 +94,10 @@ fun <T : Fragment> AppCompatActivity.addFragmentToBackstack(
}
}
fun AppCompatActivity.popBackstack() {
supportFragmentManager.popBackStack()
}
fun AppCompatActivity.resetBackstack() {
repeat(supportFragmentManager.backStackEntryCount) {
supportFragmentManager.popBackStack()

View File

@ -0,0 +1,45 @@
/*
* Copyright 2021 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.view.View
import androidx.core.view.updateLayoutParams
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
/**
* A generic item with empty space.
*/
@EpoxyModelClass(layout = R.layout.item_vertical_margin)
abstract class VerticalMarginItem : VectorEpoxyModel<VerticalMarginItem.Holder>() {
@EpoxyAttribute
var heightInPx: Int = 0
override fun bind(holder: Holder) {
super.bind(holder)
holder.space.updateLayoutParams {
height = heightInPx
}
}
class Holder : VectorEpoxyHolder() {
val space by bind<View>(R.id.item_vertical_margin_space)
}
}

View File

@ -33,6 +33,9 @@ abstract class SettingsContinueCancelItem : EpoxyModelWithHolder<SettingsContinu
@EpoxyAttribute
var continueOnClick: ClickListener? = null
@EpoxyAttribute
var canContinue: Boolean = true
@EpoxyAttribute
var cancelOnClick: ClickListener? = null
@ -43,6 +46,7 @@ abstract class SettingsContinueCancelItem : EpoxyModelWithHolder<SettingsContinu
continueText?.let { holder.continueButton.text = it }
holder.continueButton.onClick(continueOnClick)
holder.continueButton.isEnabled = canContinue
}
class Holder : VectorEpoxyHolder() {

View File

@ -19,6 +19,7 @@ package im.vector.app.features.form
import android.text.Editable
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
@ -52,7 +53,7 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
var inputType: Int? = null
@EpoxyAttribute
var singleLine: Boolean? = null
var singleLine: Boolean = true
@EpoxyAttribute
var imeOptions: Int? = null
@ -60,9 +61,13 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
@EpoxyAttribute
var endIconMode: Int? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
// FIXME restore EpoxyAttribute.Option.DoNotHash and fix that properly
@EpoxyAttribute
var onTextChange: ((String) -> Unit)? = null
@EpoxyAttribute
var editorActionListener: TextView.OnEditorActionListener? = null
private val onTextChangeListener = object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) {
onTextChange?.invoke(s.toString())
@ -80,10 +85,11 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
holder.textInputEditText.isEnabled = enabled
inputType?.let { holder.textInputEditText.inputType = it }
holder.textInputEditText.isSingleLine = singleLine ?: false
holder.textInputEditText.isSingleLine = singleLine
holder.textInputEditText.imeOptions = imeOptions ?: EditorInfo.IME_ACTION_NONE
holder.textInputEditText.addTextChangedListener(onTextChangeListener)
holder.textInputEditText.setOnEditorActionListener(editorActionListener)
holder.bottomSeparator.isVisible = showBottomSeparator
}

View File

@ -65,6 +65,7 @@ import im.vector.app.features.pin.PinActivity
import im.vector.app.features.pin.PinArgs
import im.vector.app.features.pin.PinMode
import im.vector.app.features.roomdirectory.RoomDirectoryActivity
import im.vector.app.features.roomdirectory.RoomDirectoryData
import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewActivity
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
@ -86,7 +87,6 @@ import im.vector.app.features.widgets.WidgetArgsBuilder
import im.vector.app.space
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.api.session.widgets.model.Widget
import org.matrix.android.sdk.api.session.widgets.model.WidgetType

View File

@ -26,11 +26,11 @@ import im.vector.app.features.crypto.recover.SetupMode
import im.vector.app.features.login.LoginConfig
import im.vector.app.features.media.AttachmentData
import im.vector.app.features.pin.PinMode
import im.vector.app.features.roomdirectory.RoomDirectoryData
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.share.SharedData
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.api.session.widgets.model.Widget
import org.matrix.android.sdk.api.util.MatrixItem

View File

@ -21,7 +21,6 @@ import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
data class PublicRoomsViewState(
// The current filter

View File

@ -17,7 +17,6 @@
package im.vector.app.features.roomdirectory
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
sealed class RoomDirectoryAction : VectorViewModelAction {
data class SetRoomDirectoryData(val roomDirectoryData: RoomDirectoryData) : RoomDirectoryAction()

View File

@ -25,6 +25,7 @@ import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.popBackstack
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment
@ -58,7 +59,7 @@ class RoomDirectoryActivity : VectorBaseActivity<ActivitySimpleBinding>() {
.observe()
.subscribe { sharedAction ->
when (sharedAction) {
is RoomDirectorySharedAction.Back -> onBackPressed()
is RoomDirectorySharedAction.Back -> popBackstack()
is RoomDirectorySharedAction.CreateRoom -> {
// Transmit the filter to the CreateRoomFragment
withState(roomDirectoryViewModel) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
* Copyright (c) 2021 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.
@ -14,13 +14,12 @@
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.room.model.thirdparty
package im.vector.app.features.roomdirectory
/**
* This class describes a rooms directory server.
* This class describes a rooms directory server protocol.
*/
data class RoomDirectoryData(
/**
* The server name (might be null)
* Set null when the server is the current user's home server.
@ -30,7 +29,12 @@ data class RoomDirectoryData(
/**
* The display name (the server description)
*/
val displayName: String = DEFAULT_HOME_SERVER_NAME,
val displayName: String = MATRIX_PROTOCOL_NAME,
/**
* the avatar url
*/
val avatarUrl: String? = null,
/**
* The third party server identifier
@ -40,15 +44,10 @@ data class RoomDirectoryData(
/**
* Tell if all the federated servers must be included
*/
val includeAllNetworks: Boolean = false,
/**
* the avatar url
*/
val avatarUrl: String? = null
val includeAllNetworks: Boolean = false
) {
companion object {
const val DEFAULT_HOME_SERVER_NAME = "Matrix"
const val MATRIX_PROTOCOL_NAME = "Matrix"
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.roomdirectory
data class RoomDirectoryServer(
val serverName: String,
/**
* True if this is the current user server
*/
val isUserServer: Boolean,
/**
* True if manually added, so it can be removed by the user
*/
val isManuallyAdded: Boolean,
/**
* Supported protocols
* TODO Rename RoomDirectoryData to RoomDirectoryProtocols
*/
val protocols: List<RoomDirectoryData>
)

View File

@ -37,7 +37,6 @@ import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.rx.rx
import timber.log.Timber
@ -230,9 +229,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(
Timber.w("Try to join an already joining room. Should not happen")
return@withState
}
val viaServers = state.roomDirectoryData.homeServer
?.let { listOf(it) }
.orEmpty()
val viaServers = listOfNotNull(state.roomDirectoryData.homeServer)
viewModelScope.launch {
try {
session.joinRoom(action.roomId, viaServers = viaServers)

View File

@ -75,6 +75,7 @@ class CreateRoomController @Inject constructor(
id("topic")
enabled(enableFormElement)
value(viewState.roomTopic)
singleLine(false)
hint(host.stringProvider.getString(R.string.create_room_topic_hint))
onTextChange { text ->

View File

@ -16,10 +16,12 @@
package im.vector.app.features.roomdirectory.picker
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
@ -43,6 +45,9 @@ abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>()
@EpoxyAttribute
var includeAllNetworks: Boolean = false
@EpoxyAttribute
var checked: Boolean = false
@EpoxyAttribute
var globalListener: (() -> Unit)? = null
@ -63,6 +68,7 @@ abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>()
holder.nameView.text = directoryName
holder.descriptionView.setTextOrHide(directoryDescription)
holder.checkedView.isVisible = checked
}
class Holder : VectorEpoxyHolder() {
@ -71,5 +77,6 @@ abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>()
val avatarView by bind<ImageView>(R.id.itemRoomDirectoryAvatar)
val nameView by bind<TextView>(R.id.itemRoomDirectoryName)
val descriptionView by bind<TextView>(R.id.itemRoomDirectoryDescription)
val checkedView by bind<View>(R.id.itemRoomDirectoryChecked)
}
}

View File

@ -18,55 +18,110 @@ package im.vector.app.features.roomdirectory.picker
import im.vector.app.R
import im.vector.app.core.resources.StringArrayProvider
import im.vector.app.features.roomdirectory.RoomDirectoryData
import im.vector.app.features.roomdirectory.RoomDirectoryServer
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import javax.inject.Inject
class RoomDirectoryListCreator @Inject constructor(private val stringArrayProvider: StringArrayProvider,
private val session: Session) {
class RoomDirectoryListCreator @Inject constructor(
private val stringArrayProvider: StringArrayProvider,
private val session: Session
) {
fun computeDirectories(thirdPartyProtocolData: Map<String, ThirdPartyProtocol>): List<RoomDirectoryData> {
val result = ArrayList<RoomDirectoryData>()
fun computeDirectories(thirdPartyProtocolData: Map<String, ThirdPartyProtocol>,
customHomeservers: Set<String>): List<RoomDirectoryServer> {
val result = ArrayList<RoomDirectoryServer>()
val protocols = ArrayList<RoomDirectoryData>()
// Add user homeserver name
val userHsName = session.myUserId.substringAfter(":")
result.add(RoomDirectoryData(
displayName = userHsName,
includeAllNetworks = true
))
// Add user's HS but for Matrix public rooms only
result.add(RoomDirectoryData())
// Add custom directory servers
val hsNamesList = stringArrayProvider.getStringArray(R.array.room_directory_servers)
hsNamesList.forEach {
if (it != userHsName) {
// Use the server name as a default display name
result.add(RoomDirectoryData(
homeServer = it,
displayName = it,
includeAllNetworks = true
))
}
}
// Add default protocol
protocols.add(
RoomDirectoryData(
homeServer = null,
displayName = RoomDirectoryData.MATRIX_PROTOCOL_NAME,
includeAllNetworks = false
)
)
// Add result of the request
thirdPartyProtocolData.forEach {
it.value.instances?.forEach { thirdPartyProtocolInstance ->
result.add(RoomDirectoryData(
protocols.add(
RoomDirectoryData(
homeServer = null,
displayName = thirdPartyProtocolInstance.desc ?: "",
thirdPartyInstanceId = thirdPartyProtocolInstance.instanceId,
includeAllNetworks = false,
// Default to protocol icon
avatarUrl = thirdPartyProtocolInstance.icon ?: it.value.icon
))
)
)
}
}
// Add all rooms
protocols.add(
RoomDirectoryData(
homeServer = null,
displayName = RoomDirectoryData.MATRIX_PROTOCOL_NAME,
includeAllNetworks = true
)
)
result.add(
RoomDirectoryServer(
serverName = userHsName,
isUserServer = true,
isManuallyAdded = false,
protocols = protocols
)
)
// Add custom directory servers, form the config file, excluding the current user homeserver
stringArrayProvider.getStringArray(R.array.room_directory_servers)
.filter { it != userHsName }
.forEach {
// Use the server name as a default display name
result.add(
RoomDirectoryServer(
serverName = it,
isUserServer = false,
isManuallyAdded = false,
protocols = listOf(
RoomDirectoryData(
homeServer = it,
displayName = RoomDirectoryData.MATRIX_PROTOCOL_NAME,
includeAllNetworks = false
)
)
)
)
}
// Add manually added server by the user
customHomeservers
.forEach {
// Use the server name as a default display name
result.add(
RoomDirectoryServer(
serverName = it,
isUserServer = false,
isManuallyAdded = true,
protocols = listOf(
RoomDirectoryData(
homeServer = it,
displayName = RoomDirectoryData.MATRIX_PROTOCOL_NAME,
includeAllNetworks = false
)
)
)
)
}
return result
}
}

View File

@ -17,7 +17,14 @@
package im.vector.app.features.roomdirectory.picker
import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.roomdirectory.RoomDirectoryServer
sealed class RoomDirectoryPickerAction : VectorViewModelAction {
object Retry : RoomDirectoryPickerAction()
object EnterEditMode : RoomDirectoryPickerAction()
object ExitEditMode : RoomDirectoryPickerAction()
data class SetServerUrl(val url: String) : RoomDirectoryPickerAction()
data class RemoveServer(val roomDirectoryServer: RoomDirectoryServer) : RoomDirectoryPickerAction()
object Submit : RoomDirectoryPickerAction()
}

View File

@ -16,37 +16,62 @@
package im.vector.app.features.roomdirectory.picker
import android.text.InputType
import android.view.KeyEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import im.vector.app.R
import im.vector.app.core.epoxy.dividerItem
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.extensions.join
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
import im.vector.app.core.ui.list.genericButtonItem
import im.vector.app.core.ui.list.verticalMarginItem
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.discovery.settingsContinueCancelItem
import im.vector.app.features.discovery.settingsInformationItem
import im.vector.app.features.form.formEditTextItem
import im.vector.app.features.roomdirectory.RoomDirectoryData
import im.vector.app.features.roomdirectory.RoomDirectoryServer
import org.matrix.android.sdk.api.failure.Failure
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection
class RoomDirectoryPickerController @Inject constructor(private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter,
private val roomDirectoryListCreator: RoomDirectoryListCreator
class RoomDirectoryPickerController @Inject constructor(
private val stringProvider: StringProvider,
private val colorProvider: ColorProvider,
private val dimensionConverter: DimensionConverter,
private val errorFormatter: ErrorFormatter
) : TypedEpoxyController<RoomDirectoryPickerViewState>() {
var currentRoomDirectoryData: RoomDirectoryData? = null
var callback: Callback? = null
var index = 0
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
override fun buildModels(viewState: RoomDirectoryPickerViewState) {
override fun buildModels(data: RoomDirectoryPickerViewState) {
val host = this
val asyncThirdPartyProtocol = viewState.asyncThirdPartyRequest
when (asyncThirdPartyProtocol) {
when (val asyncThirdPartyProtocol = data.asyncThirdPartyRequest) {
is Success -> {
val directories = roomDirectoryListCreator.computeDirectories(asyncThirdPartyProtocol())
directories.forEach {
buildDirectory(it)
data.directories.join(
each = { _, roomDirectoryServer -> buildDirectory(roomDirectoryServer) },
between = { idx, _ -> buildDivider(idx) }
)
buildForm(data)
verticalMarginItem {
id("space_bottom")
heightInPx(host.dimensionConverter.dpToPx(16))
}
}
is Incomplete -> {
@ -64,34 +89,142 @@ class RoomDirectoryPickerController @Inject constructor(private val stringProvid
}
}
private fun buildDirectory(roomDirectoryData: RoomDirectoryData) {
private fun buildForm(data: RoomDirectoryPickerViewState) {
buildDivider(1000)
val host = this
roomDirectoryItem {
id(host.index++)
directoryName(roomDirectoryData.displayName)
val description = when {
roomDirectoryData.includeAllNetworks ->
host.stringProvider.getString(R.string.directory_server_all_rooms_on_server, roomDirectoryData.displayName)
"Matrix" == roomDirectoryData.displayName ->
host.stringProvider.getString(R.string.directory_server_native_rooms, roomDirectoryData.displayName)
else ->
null
if (data.inEditMode) {
verticalMarginItem {
id("form_space")
heightInPx(host.dimensionConverter.dpToPx(16))
}
settingsInformationItem {
id("form_notice")
message(host.stringProvider.getString(R.string.directory_add_a_new_server_prompt))
colorProvider(host.colorProvider)
}
verticalMarginItem {
id("form_space_2")
heightInPx(host.dimensionConverter.dpToPx(8))
}
formEditTextItem {
id("edit")
showBottomSeparator(false)
value(data.enteredServer)
imeOptions(EditorInfo.IME_ACTION_DONE)
editorActionListener(object : TextView.OnEditorActionListener {
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
if (actionId == EditorInfo.IME_ACTION_DONE) {
if (data.enteredServer.isNotEmpty()) {
host.callback?.onSubmitServer()
}
return true
}
return false
}
})
hint(host.stringProvider.getString(R.string.directory_server_placeholder))
inputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI)
onTextChange { text ->
host.callback?.onEnterServerChange(text)
}
when (data.addServerAsync) {
Uninitialized -> enabled(true)
is Loading -> enabled(false)
is Success -> enabled(false)
is Fail -> {
enabled(true)
errorMessage(host.getErrorMessage(data.addServerAsync.error))
}
}
}
when (data.addServerAsync) {
Uninitialized,
is Fail -> settingsContinueCancelItem {
id("continueCancel")
continueText(host.stringProvider.getString(R.string.ok))
canContinue(data.enteredServer.isNotEmpty())
continueOnClick { host.callback?.onSubmitServer() }
cancelOnClick { host.callback?.onCancelEnterServer() }
}
is Loading -> loadingItem {
id("addLoading")
}
is Success -> Unit /* This is a transitive state */
}
} else {
genericButtonItem {
id("add")
text(host.stringProvider.getString(R.string.directory_add_a_new_server))
textColor(host.colorProvider.getColor(R.color.riotx_accent))
buttonClickAction(View.OnClickListener {
host.callback?.onStartEnterServer()
})
}
}
}
directoryDescription(description)
private fun getErrorMessage(error: Throwable): String {
return if (error is Failure.ServerError
&& error.httpCode == HttpsURLConnection.HTTP_INTERNAL_ERROR /* 500 */) {
stringProvider.getString(R.string.directory_add_a_new_server_error)
} else {
errorFormatter.toHumanReadable(error)
}
}
private fun buildDivider(idx: Int) {
val host = this
dividerItem {
id("divider_$idx")
color(host.dividerColor)
}
}
private fun buildDirectory(roomDirectoryServer: RoomDirectoryServer) {
val host = this
roomDirectoryServerItem {
id("server_$roomDirectoryServer")
serverName(roomDirectoryServer.serverName)
canRemove(roomDirectoryServer.isManuallyAdded)
removeListener { host.callback?.onRemoveServer(roomDirectoryServer) }
if (roomDirectoryServer.isUserServer) {
serverDescription(host.stringProvider.getString(R.string.directory_your_server))
}
}
roomDirectoryServer.protocols.forEach { roomDirectoryData ->
roomDirectoryItem {
id("server_${roomDirectoryServer}_proto_$roomDirectoryData")
directoryName(
if (roomDirectoryData.includeAllNetworks) {
host.stringProvider.getString(R.string.directory_server_all_rooms_on_server, roomDirectoryServer.serverName)
} else {
roomDirectoryData.displayName
}
)
if (roomDirectoryData.displayName == RoomDirectoryData.MATRIX_PROTOCOL_NAME && !roomDirectoryData.includeAllNetworks) {
directoryDescription(
host.stringProvider.getString(R.string.directory_server_native_rooms, roomDirectoryServer.serverName)
)
}
directoryAvatarUrl(roomDirectoryData.avatarUrl)
includeAllNetworks(roomDirectoryData.includeAllNetworks)
checked(roomDirectoryData == host.currentRoomDirectoryData)
globalListener {
host.callback?.onRoomDirectoryClicked(roomDirectoryData)
}
}
}
}
interface Callback {
fun onRoomDirectoryClicked(roomDirectoryData: RoomDirectoryData)
fun retry()
fun onStartEnterServer()
fun onEnterServerChange(server: String)
fun onSubmitServer()
fun onCancelEnterServer()
fun onRemoveServer(roomDirectoryServer: RoomDirectoryServer)
}
}

View File

@ -18,7 +18,6 @@ package im.vector.app.features.roomdirectory.picker
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
@ -28,21 +27,22 @@ 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.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding
import im.vector.app.features.roomdirectory.RoomDirectoryAction
import im.vector.app.features.roomdirectory.RoomDirectoryData
import im.vector.app.features.roomdirectory.RoomDirectoryServer
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
import im.vector.app.features.roomdirectory.RoomDirectoryViewModel
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
import timber.log.Timber
import javax.inject.Inject
// TODO Menu to add custom room directory (not done in RiotWeb so far...)
class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerViewModelFactory: RoomDirectoryPickerViewModel.Factory,
private val roomDirectoryPickerController: RoomDirectoryPickerController
) : VectorBaseFragment<FragmentRoomDirectoryPickerBinding>(),
OnBackPressed,
RoomDirectoryPickerController.Callback {
private val viewModel: RoomDirectoryViewModel by activityViewModel()
@ -65,6 +65,11 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
setupRecyclerView()
// Give the current data to our controller. There maybe a better way to do that...
withState(viewModel) {
roomDirectoryPickerController.currentRoomDirectoryData = it.roomDirectoryData
}
}
override fun onDestroyView() {
@ -73,18 +78,6 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
super.onDestroyView()
}
override fun getMenuRes() = R.menu.menu_directory_server_picker
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.action_add_custom_hs) {
// TODO
vectorBaseActivity.notImplemented("Entering custom homeserver")
return true
}
return super.onOptionsItemSelected(item)
}
private fun setupRecyclerView() {
views.roomDirectoryPickerList.configureWith(roomDirectoryPickerController)
roomDirectoryPickerController.callback = this
@ -97,6 +90,26 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
sharedActionViewModel.post(RoomDirectorySharedAction.Back)
}
override fun onStartEnterServer() {
pickerViewModel.handle(RoomDirectoryPickerAction.EnterEditMode)
}
override fun onCancelEnterServer() {
pickerViewModel.handle(RoomDirectoryPickerAction.ExitEditMode)
}
override fun onEnterServerChange(server: String) {
pickerViewModel.handle(RoomDirectoryPickerAction.SetServerUrl(server))
}
override fun onSubmitServer() {
pickerViewModel.handle(RoomDirectoryPickerAction.Submit)
}
override fun onRemoveServer(roomDirectoryServer: RoomDirectoryServer) {
pickerViewModel.handle(RoomDirectoryPickerAction.RemoveServer(roomDirectoryServer))
}
override fun onResume() {
super.onResume()
(activity as? AppCompatActivity)?.supportActionBar?.setTitle(R.string.select_room_directory)
@ -111,4 +124,16 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
// Populate list with Epoxy
roomDirectoryPickerController.setData(state)
}
override fun onBackPressed(toolbarButton: Boolean): Boolean {
// Leave the add server mode if started
return withState(pickerViewModel) {
if (it.inEditMode) {
pickerViewModel.handle(RoomDirectoryPickerAction.ExitEditMode)
true
} else {
false
}
}
}
}

View File

@ -22,18 +22,28 @@ import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.R
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.ui.UiStateRepository
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initialState: RoomDirectoryPickerViewState,
private val session: Session)
: VectorViewModel<RoomDirectoryPickerViewState, RoomDirectoryPickerAction, EmptyViewEvents>(initialState) {
class RoomDirectoryPickerViewModel @AssistedInject constructor(
@Assisted initialState: RoomDirectoryPickerViewState,
private val session: Session,
private val uiStateRepository: UiStateRepository,
private val stringProvider: StringProvider,
private val roomDirectoryListCreator: RoomDirectoryListCreator
) : VectorViewModel<RoomDirectoryPickerViewState, RoomDirectoryPickerAction, EmptyViewEvents>(initialState) {
@AssistedFactory
interface Factory {
@ -50,7 +60,22 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initial
}
init {
observeAndCompute()
load()
loadCustomRoomDirectoryHomeservers()
}
private fun observeAndCompute() {
selectSubscribe(
RoomDirectoryPickerViewState::asyncThirdPartyRequest,
RoomDirectoryPickerViewState::customHomeservers
) { async, custom ->
async()?.let {
setState {
copy(directories = roomDirectoryListCreator.computeDirectories(it, custom))
}
}
}
}
private fun load() {
@ -71,9 +96,101 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initial
}
}
private fun loadCustomRoomDirectoryHomeservers() {
setState {
copy(
customHomeservers = uiStateRepository.getCustomRoomDirectoryHomeservers(session.sessionId)
)
}
}
override fun handle(action: RoomDirectoryPickerAction) {
when (action) {
RoomDirectoryPickerAction.Retry -> load()
RoomDirectoryPickerAction.EnterEditMode -> handleEnterEditMode()
RoomDirectoryPickerAction.ExitEditMode -> handleExitEditMode()
is RoomDirectoryPickerAction.SetServerUrl -> handleSetServerUrl(action)
RoomDirectoryPickerAction.Submit -> handleSubmit()
is RoomDirectoryPickerAction.RemoveServer -> handleRemoveServer(action)
}.exhaustive
}
private fun handleEnterEditMode() {
setState {
copy(
inEditMode = true,
enteredServer = "",
addServerAsync = Uninitialized
)
}
}
private fun handleExitEditMode() {
setState {
copy(
inEditMode = false,
enteredServer = "",
addServerAsync = Uninitialized
)
}
}
private fun handleSetServerUrl(action: RoomDirectoryPickerAction.SetServerUrl) {
setState {
copy(
enteredServer = action.url
)
}
}
private fun handleSubmit() = withState { state ->
// First avoid duplicate
val enteredServer = state.enteredServer
val existingServerList = state.directories.map { it.serverName }
if (enteredServer in existingServerList) {
setState {
copy(addServerAsync = Fail(Throwable(stringProvider.getString(R.string.directory_add_a_new_server_error_already_added))))
}
return@withState
}
viewModelScope.launch {
setState {
copy(addServerAsync = Loading())
}
try {
session.getPublicRooms(
server = enteredServer,
publicRoomsParams = PublicRoomsParams(limit = 1)
)
// Success, let add the server to our local repository, and update the state
val newSet = uiStateRepository.getCustomRoomDirectoryHomeservers(session.sessionId) + enteredServer
uiStateRepository.setCustomRoomDirectoryHomeservers(session.sessionId, newSet)
setState {
copy(
inEditMode = false,
enteredServer = "",
addServerAsync = Uninitialized,
customHomeservers = newSet
)
}
} catch (failure: Throwable) {
setState {
copy(addServerAsync = Fail(failure))
}
}
}
}
private fun handleRemoveServer(action: RoomDirectoryPickerAction.RemoveServer) {
val newSet = uiStateRepository.getCustomRoomDirectoryHomeservers(session.sessionId) - action.roomDirectoryServer.serverName
uiStateRepository.setCustomRoomDirectoryHomeservers(session.sessionId, newSet)
setState {
copy(
customHomeservers = newSet
)
}
}
}

View File

@ -19,8 +19,15 @@ package im.vector.app.features.roomdirectory.picker
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.app.features.roomdirectory.RoomDirectoryServer
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
data class RoomDirectoryPickerViewState(
val asyncThirdPartyRequest: Async<Map<String, ThirdPartyProtocol>> = Uninitialized
val asyncThirdPartyRequest: Async<Map<String, ThirdPartyProtocol>> = Uninitialized,
val customHomeservers: Set<String> = emptySet(),
val inEditMode: Boolean = false,
val enteredServer: String = "",
val addServerAsync: Async<Unit> = Uninitialized,
// computed
val directories: List<RoomDirectoryServer> = emptyList()
) : MvRxState

View File

@ -0,0 +1,59 @@
/*
* Copyright 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.roomdirectory.picker
import android.view.View
import android.widget.TextView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.extensions.setTextOrHide
@EpoxyModelClass(layout = R.layout.item_room_directory_server)
abstract class RoomDirectoryServerItem : VectorEpoxyModel<RoomDirectoryServerItem.Holder>() {
@EpoxyAttribute
var serverName: String? = null
@EpoxyAttribute
var serverDescription: String? = null
@EpoxyAttribute
var canRemove: Boolean = false
@EpoxyAttribute
var removeListener: ClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.nameView.text = serverName
holder.descriptionView.setTextOrHide(serverDescription)
holder.deleteView.isVisible = canRemove
holder.deleteView.onClick(removeListener)
}
class Holder : VectorEpoxyHolder() {
val nameView by bind<TextView>(R.id.itemRoomDirectoryServerName)
val descriptionView by bind<TextView>(R.id.itemRoomDirectoryServerDescription)
val deleteView by bind<View>(R.id.itemRoomDirectoryServerRemove)
}
}

View File

@ -25,9 +25,9 @@ import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.roomdirectory.RoomDirectoryData
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
import org.matrix.android.sdk.api.util.MatrixItem
import timber.log.Timber

View File

@ -102,6 +102,7 @@ class RoomSettingsController @Inject constructor(
id("topic")
enabled(data.actionPermissions.canChangeTopic)
value(data.newTopic ?: roomSummary.topic)
singleLine(false)
hint(host.stringProvider.getString(R.string.room_settings_topic_hint))
onTextChange { text ->

View File

@ -69,7 +69,6 @@ class SpaceDefaultRoomEpoxyController @Inject constructor(
id("roomName1")
enabled(true)
value(firstRoomName)
singleLine(true)
hint(host.stringProvider.getString(R.string.create_room_name_section))
endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT)
showBottomSeparator(false)
@ -83,7 +82,6 @@ class SpaceDefaultRoomEpoxyController @Inject constructor(
id("roomName2")
enabled(true)
value(secondRoomName)
singleLine(true)
hint(host.stringProvider.getString(R.string.create_room_name_section))
endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT)
showBottomSeparator(false)
@ -97,7 +95,6 @@ class SpaceDefaultRoomEpoxyController @Inject constructor(
id("roomName3")
enabled(true)
value(thirdRoomName)
singleLine(true)
hint(host.stringProvider.getString(R.string.create_room_name_section))
endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT)
showBottomSeparator(false)

View File

@ -64,7 +64,6 @@ class SpaceDetailEpoxyController @Inject constructor(
enabled(true)
value(data?.name)
hint(host.stringProvider.getString(R.string.create_room_name_hint))
singleLine(true)
showBottomSeparator(false)
errorMessage(data?.nameInlineError)
// onBind { _, view, _ ->

View File

@ -89,6 +89,18 @@ class SharedPreferencesUiStateRepository @Inject constructor(
return sharedPreferences.getBoolean("$KEY_SELECTED_METHOD@$sessionId", true)
}
override fun setCustomRoomDirectoryHomeservers(sessionId: String, servers: Set<String>) {
sharedPreferences.edit {
putStringSet("$KEY_CUSTOM_DIRECTORY_HOMESERVER@$sessionId", servers)
}
}
override fun getCustomRoomDirectoryHomeservers(sessionId: String): Set<String> {
return sharedPreferences.getStringSet("$KEY_CUSTOM_DIRECTORY_HOMESERVER@$sessionId", null)
.orEmpty()
.toSet()
}
companion object {
private const val KEY_DISPLAY_MODE = "UI_STATE_DISPLAY_MODE"
private const val VALUE_DISPLAY_MODE_CATCHUP = 0
@ -98,5 +110,7 @@ class SharedPreferencesUiStateRepository @Inject constructor(
private const val KEY_SELECTED_SPACE = "UI_STATE_SELECTED_SPACE"
private const val KEY_SELECTED_GROUP = "UI_STATE_SELECTED_GROUP"
private const val KEY_SELECTED_METHOD = "UI_STATE_SELECTED_METHOD"
private const val KEY_CUSTOM_DIRECTORY_HOMESERVER = "KEY_CUSTOM_DIRECTORY_HOMESERVER"
}
}

View File

@ -32,6 +32,7 @@ interface UiStateRepository {
fun storeDisplayMode(displayMode: RoomListDisplayMode)
// TODO Handle SharedPreference per session in a better way, also to cleanup when login out
fun storeSelectedSpace(spaceId: String?, sessionId: String)
fun storeSelectedGroup(groupId: String?, sessionId: String)
@ -40,4 +41,7 @@ interface UiStateRepository {
fun getSelectedSpace(sessionId: String): String?
fun getSelectedGroup(sessionId: String): String?
fun isGroupingMethodSpace(sessionId: String): Boolean
fun setCustomRoomDirectoryHomeservers(sessionId: String, servers: Set<String>)
fun getCustomRoomDirectoryHomeservers(sessionId: String): Set<String>
}

View File

@ -1,5 +1,4 @@
<?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"
@ -15,23 +14,23 @@
android:id="@+id/itemRoomDirectoryAvatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:background="@drawable/circle"
android:contentDescription="@string/avatar"
android:padding="8dp"
app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryBottomSeparator"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
tools:src="@drawable/network_matrix" />
<TextView
android:id="@+id/itemRoomDirectoryName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:lines="1"
android:maxLines="2"
@ -39,36 +38,46 @@
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryDescription"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/itemRoomDirectoryChecked"
app:layout_constraintStart_toEndOf="@id/itemRoomDirectoryAvatar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:layout_goneMarginEnd="@dimen/layout_horizontal_margin"
tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/itemRoomDirectoryDescription"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:lines="1"
android:maxLines="2"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryBottomSeparator"
app:layout_constraintEnd_toEndOf="parent"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/itemRoomDirectoryChecked"
app:layout_constraintStart_toEndOf="@id/itemRoomDirectoryAvatar"
app:layout_constraintTop_toBottomOf="@id/itemRoomDirectoryName"
tools:text="@string/directory_server_all_rooms_on_server" />
app:layout_goneMarginEnd="@dimen/layout_horizontal_margin"
tools:text="@string/directory_server_native_rooms"
tools:visibility="visible" />
<View
android:id="@+id/itemRoomDirectoryBottomSeparator"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="?riotx_header_panel_border_mobile"
<ImageView
android:id="@+id/itemRoomDirectoryChecked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:contentDescription="@string/avatar"
android:padding="8dp"
android:src="@drawable/ic_check_on"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,67 @@
<?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:background="?vctr_list_header_background_color"
android:minHeight="?listPreferredItemHeight">
<TextView
android:id="@+id/itemRoomDirectoryServerName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:lines="1"
android:maxLines="2"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryServerDescription"
app:layout_constraintEnd_toStartOf="@id/itemRoomDirectoryServerRemove"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:layout_goneMarginEnd="@dimen/layout_horizontal_margin"
tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/itemRoomDirectoryServerDescription"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:lines="1"
android:maxLines="2"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/itemRoomDirectoryServerRemove"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/itemRoomDirectoryServerName"
app:layout_goneMarginEnd="@dimen/layout_horizontal_margin"
tools:text="@string/directory_your_server"
tools:visibility="visible" />
<ImageView
android:id="@+id/itemRoomDirectoryServerRemove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:contentDescription="@string/avatar"
android:padding="8dp"
android:src="@drawable/ic_delete"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/vector_error_color"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Space xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_vertical_margin_space"
android:layout_width="match_parent"
android:layout_height="@dimen/layout_vertical_margin" />

View File

@ -1,13 +0,0 @@
<?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/action_add_custom_hs"
android:icon="@drawable/ic_add_black"
android:title="@string/action_open"
android:visible="@bool/false_not_implemented"
app:iconTint="?colorAccent"
app:showAsAction="always" />
</menu>

View File

@ -23,6 +23,7 @@
<string-array name="room_directory_servers" translatable="false">
<item>matrix.org</item>
<item>gitter.im</item>
</string-array>
</resources>

View File

@ -1584,9 +1584,14 @@
<string name="select_room_directory">Select a room directory</string>
<string name="directory_server_fail_to_retrieve_server">The server may be unavailable or overloaded</string>
<string name="directory_server_type_homeserver">Type a homeserver to list public rooms from</string>
<string name="directory_server_placeholder">Homeserver URL</string>
<string name="directory_server_placeholder">Server name</string>
<string name="directory_server_all_rooms_on_server">All rooms on %s server</string>
<string name="directory_server_native_rooms">All native %s rooms</string>
<string name="directory_your_server">Your server</string>
<string name="directory_add_a_new_server">Add a new server</string>
<string name="directory_add_a_new_server_prompt">Enter the name of a new server you want to explore.</string>
<string name="directory_add_a_new_server_error">"Can't find this server or its room list"</string>
<string name="directory_add_a_new_server_error_already_added">This server is already present in the list</string>
<!-- Lock screen-->
<string name="lock_screen_hint">Type here…</string>