Add custom server

This commit is contained in:
Benoit Marty 2021-05-27 14:05:13 +02:00
parent f30eb4af8a
commit cbed1afaaa
18 changed files with 387 additions and 46 deletions

View File

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

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_generic_space)
abstract class GenericSpaceItem : VectorEpoxyModel<GenericSpaceItem.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_generic_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

@ -51,6 +51,7 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
@EpoxyAttribute
var inputType: Int? = null
// TODO Should be true by default
@EpoxyAttribute
var singleLine: Boolean? = null

View File

@ -24,6 +24,11 @@ data class RoomDirectoryServer(
*/
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

View File

@ -29,7 +29,8 @@ class RoomDirectoryListCreator @Inject constructor(
private val session: Session
) {
fun computeDirectories(thirdPartyProtocolData: Map<String, ThirdPartyProtocol>): List<RoomDirectoryServer> {
fun computeDirectories(thirdPartyProtocolData: Map<String, ThirdPartyProtocol>,
customHomeservers: Set<String>): List<RoomDirectoryServer> {
val result = ArrayList<RoomDirectoryServer>()
val protocols = ArrayList<RoomDirectoryData>()
@ -75,6 +76,7 @@ class RoomDirectoryListCreator @Inject constructor(
RoomDirectoryServer(
serverName = userHsName,
isUserServer = true,
isManuallyAdded = false,
protocols = protocols
)
)
@ -88,6 +90,7 @@ class RoomDirectoryListCreator @Inject constructor(
RoomDirectoryServer(
serverName = it,
isUserServer = false,
isManuallyAdded = false,
protocols = listOf(
RoomDirectoryData(
homeServer = it,
@ -99,7 +102,25 @@ class RoomDirectoryListCreator @Inject constructor(
)
}
// TODO Add manually added server by the user
// 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,10 +16,14 @@
package im.vector.app.features.roomdirectory.picker
import android.text.InputType
import android.view.View
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
@ -28,13 +32,22 @@ 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 im.vector.app.core.ui.list.genericButtonItem
import im.vector.app.core.ui.list.genericSpaceItem
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,
colorProvider: ColorProvider,
private val colorProvider: ColorProvider,
private val dimensionConverter: DimensionConverter,
private val errorFormatter: ErrorFormatter,
private val roomDirectoryListCreator: RoomDirectoryListCreator
) : TypedEpoxyController<RoomDirectoryPickerViewState>() {
@ -44,16 +57,24 @@ class RoomDirectoryPickerController @Inject constructor(
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
override fun buildModels(viewState: RoomDirectoryPickerViewState) {
override fun buildModels(data: RoomDirectoryPickerViewState) {
val host = this
when (val asyncThirdPartyProtocol = viewState.asyncThirdPartyRequest) {
when (val asyncThirdPartyProtocol = data.asyncThirdPartyRequest) {
is Success -> {
val directories = roomDirectoryListCreator.computeDirectories(asyncThirdPartyProtocol())
val directories = roomDirectoryListCreator.computeDirectories(
asyncThirdPartyProtocol(),
data.customHomeservers
)
directories.join(
each = { _, roomDirectoryServer -> buildDirectory(roomDirectoryServer) },
between = { idx, _ -> buildDivider(idx) }
)
buildForm(data)
genericSpaceItem {
id("space_bottom")
heightInPx(host.dimensionConverter.dpToPx(16))
}
}
is Incomplete -> {
loadingItem {
@ -70,6 +91,77 @@ class RoomDirectoryPickerController @Inject constructor(
}
}
private fun buildForm(data: RoomDirectoryPickerViewState) {
buildDivider(1000)
val host = this
if (data.inEditMode) {
genericSpaceItem {
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)
}
genericSpaceItem {
id("form_space_2")
heightInPx(host.dimensionConverter.dpToPx(8))
}
formEditTextItem {
id("edit")
showBottomSeparator(false)
value(data.enteredServer)
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()
})
}
}
}
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 {
@ -81,8 +173,10 @@ class RoomDirectoryPickerController @Inject constructor(
private fun buildDirectory(roomDirectoryServer: RoomDirectoryServer) {
val host = this
roomDirectoryServerItem {
id("server_" + roomDirectoryServer.serverName)
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))
@ -91,7 +185,7 @@ class RoomDirectoryPickerController @Inject constructor(
roomDirectoryServer.protocols.forEach { roomDirectoryData ->
roomDirectoryItem {
id("server_" + roomDirectoryServer.serverName + "_proto_" + roomDirectoryData.displayName)
id("server_${roomDirectoryServer}_proto_$roomDirectoryData")
directoryName(
if (roomDirectoryData.includeAllNetworks) {
host.stringProvider.getString(R.string.directory_server_all_rooms_on_server, roomDirectoryServer.serverName)
@ -117,5 +211,10 @@ class RoomDirectoryPickerController @Inject constructor(
interface Callback {
fun onRoomDirectoryClicked(roomDirectoryData: RoomDirectoryData)
fun retry()
fun onStartEnterServer()
fun onEnterServerChange(server: String)
fun onSubmitServer()
fun onCancelEnterServer()
fun onRemoveServer(roomDirectoryServer: RoomDirectoryServer)
}
}

View File

@ -28,20 +28,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 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()
@ -77,18 +79,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
@ -101,6 +91,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)
@ -115,4 +125,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,24 @@ 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.core.extensions.exhaustive
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
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
) : VectorViewModel<RoomDirectoryPickerViewState, RoomDirectoryPickerAction, EmptyViewEvents>(initialState) {
@AssistedFactory
interface Factory {
@ -51,6 +57,7 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initial
init {
load()
loadCustomRoomDirectoryHomeservers()
}
private fun load() {
@ -71,9 +78,89 @@ 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.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 ->
viewModelScope.launch {
setState {
copy(addServerAsync = Loading())
}
try {
session.getPublicRooms(
server = state.enteredServer,
publicRoomsParams = PublicRoomsParams(limit = 1)
)
// Success, let add the server to our local repository, and update the state
val newSet = uiStateRepository.getCustomRoomDirectoryHomeservers(session.sessionId) + state.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

@ -22,5 +22,9 @@ import com.airbnb.mvrx.Uninitialized
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
) : MvRxState

View File

@ -16,12 +16,16 @@
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)
@ -33,14 +37,23 @@ abstract class RoomDirectoryServerItem : VectorEpoxyModel<RoomDirectoryServerIte
@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

@ -39,7 +39,7 @@ class SharedPreferencesUiStateRepository @Inject constructor(
override fun getDisplayMode(): RoomListDisplayMode {
return when (sharedPreferences.getInt(KEY_DISPLAY_MODE, VALUE_DISPLAY_MODE_CATCHUP)) {
VALUE_DISPLAY_MODE_PEOPLE -> RoomListDisplayMode.PEOPLE
VALUE_DISPLAY_MODE_ROOMS -> RoomListDisplayMode.ROOMS
VALUE_DISPLAY_MODE_ROOMS -> RoomListDisplayMode.ROOMS
else -> if (vectorPreferences.labAddNotificationTab()) {
RoomListDisplayMode.NOTIFICATIONS
} else {
@ -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

@ -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_generic_space"
android:layout_width="match_parent"
android:layout_height="@dimen/layout_vertical_margin" />

View File

@ -12,7 +12,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:lines="1"
android:maxLines="2"
@ -20,10 +20,11 @@
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryServerDescription"
app:layout_constraintEnd_toEndOf="parent"
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
@ -31,7 +32,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:lines="1"
android:maxLines="2"
@ -39,10 +40,28 @@
android:textSize="15sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="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

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

@ -1584,10 +1584,13 @@
<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>
<!-- Lock screen-->
<string name="lock_screen_hint">Type here…</string>