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) @JsonClass(generateAdapter = true)
data class ThirdPartyUser( 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, @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, @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 @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() { fun AppCompatActivity.resetBackstack() {
repeat(supportFragmentManager.backStackEntryCount) { repeat(supportFragmentManager.backStackEntryCount) {
supportFragmentManager.popBackStack() 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 @EpoxyAttribute
var continueOnClick: ClickListener? = null var continueOnClick: ClickListener? = null
@EpoxyAttribute
var canContinue: Boolean = true
@EpoxyAttribute @EpoxyAttribute
var cancelOnClick: ClickListener? = null var cancelOnClick: ClickListener? = null
@ -43,6 +46,7 @@ abstract class SettingsContinueCancelItem : EpoxyModelWithHolder<SettingsContinu
continueText?.let { holder.continueButton.text = it } continueText?.let { holder.continueButton.text = it }
holder.continueButton.onClick(continueOnClick) holder.continueButton.onClick(continueOnClick)
holder.continueButton.isEnabled = canContinue
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {

View File

@ -19,6 +19,7 @@ package im.vector.app.features.form
import android.text.Editable import android.text.Editable
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
@ -52,7 +53,7 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
var inputType: Int? = null var inputType: Int? = null
@EpoxyAttribute @EpoxyAttribute
var singleLine: Boolean? = null var singleLine: Boolean = true
@EpoxyAttribute @EpoxyAttribute
var imeOptions: Int? = null var imeOptions: Int? = null
@ -60,9 +61,13 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
@EpoxyAttribute @EpoxyAttribute
var endIconMode: Int? = null var endIconMode: Int? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) // FIXME restore EpoxyAttribute.Option.DoNotHash and fix that properly
@EpoxyAttribute
var onTextChange: ((String) -> Unit)? = null var onTextChange: ((String) -> Unit)? = null
@EpoxyAttribute
var editorActionListener: TextView.OnEditorActionListener? = null
private val onTextChangeListener = object : SimpleTextWatcher() { private val onTextChangeListener = object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) { override fun afterTextChanged(s: Editable) {
onTextChange?.invoke(s.toString()) onTextChange?.invoke(s.toString())
@ -80,10 +85,11 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
holder.textInputEditText.isEnabled = enabled holder.textInputEditText.isEnabled = enabled
inputType?.let { holder.textInputEditText.inputType = it } 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.imeOptions = imeOptions ?: EditorInfo.IME_ACTION_NONE
holder.textInputEditText.addTextChangedListener(onTextChangeListener) holder.textInputEditText.addTextChangedListener(onTextChangeListener)
holder.textInputEditText.setOnEditorActionListener(editorActionListener)
holder.bottomSeparator.isVisible = showBottomSeparator 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.PinArgs
import im.vector.app.features.pin.PinMode import im.vector.app.features.pin.PinMode
import im.vector.app.features.roomdirectory.RoomDirectoryActivity 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.createroom.CreateRoomActivity
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewActivity import im.vector.app.features.roomdirectory.roompreview.RoomPreviewActivity
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData 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 im.vector.app.space
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction 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.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.terms.TermsService
import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.Widget
import org.matrix.android.sdk.api.session.widgets.model.WidgetType 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.login.LoginConfig
import im.vector.app.features.media.AttachmentData import im.vector.app.features.media.AttachmentData
import im.vector.app.features.pin.PinMode 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.roomdirectory.roompreview.RoomPreviewData
import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.share.SharedData 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.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.terms.TermsService
import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.Widget
import org.matrix.android.sdk.api.util.MatrixItem 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 com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState 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.roomdirectory.PublicRoom
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
data class PublicRoomsViewState( data class PublicRoomsViewState(
// The current filter // The current filter

View File

@ -17,7 +17,6 @@
package im.vector.app.features.roomdirectory package im.vector.app.features.roomdirectory
import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
sealed class RoomDirectoryAction : VectorViewModelAction { sealed class RoomDirectoryAction : VectorViewModelAction {
data class SetRoomDirectoryData(val roomDirectoryData: RoomDirectoryData) : RoomDirectoryAction() 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.di.ScreenComponent
import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragment
import im.vector.app.core.extensions.addFragmentToBackstack 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.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment
@ -58,7 +59,7 @@ class RoomDirectoryActivity : VectorBaseActivity<ActivitySimpleBinding>() {
.observe() .observe()
.subscribe { sharedAction -> .subscribe { sharedAction ->
when (sharedAction) { when (sharedAction) {
is RoomDirectorySharedAction.Back -> onBackPressed() is RoomDirectorySharedAction.Back -> popBackstack()
is RoomDirectorySharedAction.CreateRoom -> { is RoomDirectorySharedAction.CreateRoom -> {
// Transmit the filter to the CreateRoomFragment // Transmit the filter to the CreateRoomFragment
withState(roomDirectoryViewModel) { 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,13 +14,12 @@
* limitations under the License. * 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( data class RoomDirectoryData(
/** /**
* The server name (might be null) * The server name (might be null)
* Set null when the server is the current user's home server. * 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) * 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 * The third party server identifier
@ -40,15 +44,10 @@ data class RoomDirectoryData(
/** /**
* Tell if all the federated servers must be included * Tell if all the federated servers must be included
*/ */
val includeAllNetworks: Boolean = false, val includeAllNetworks: Boolean = false
/**
* the avatar url
*/
val avatarUrl: String? = null
) { ) {
companion object { 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.Membership
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter 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.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.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
@ -230,9 +229,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(
Timber.w("Try to join an already joining room. Should not happen") Timber.w("Try to join an already joining room. Should not happen")
return@withState return@withState
} }
val viaServers = state.roomDirectoryData.homeServer val viaServers = listOfNotNull(state.roomDirectoryData.homeServer)
?.let { listOf(it) }
.orEmpty()
viewModelScope.launch { viewModelScope.launch {
try { try {
session.joinRoom(action.roomId, viaServers = viaServers) session.joinRoom(action.roomId, viaServers = viaServers)

View File

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

View File

@ -16,10 +16,12 @@
package im.vector.app.features.roomdirectory.picker package im.vector.app.features.roomdirectory.picker
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R import im.vector.app.R
@ -43,6 +45,9 @@ abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>()
@EpoxyAttribute @EpoxyAttribute
var includeAllNetworks: Boolean = false var includeAllNetworks: Boolean = false
@EpoxyAttribute
var checked: Boolean = false
@EpoxyAttribute @EpoxyAttribute
var globalListener: (() -> Unit)? = null var globalListener: (() -> Unit)? = null
@ -63,6 +68,7 @@ abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>()
holder.nameView.text = directoryName holder.nameView.text = directoryName
holder.descriptionView.setTextOrHide(directoryDescription) holder.descriptionView.setTextOrHide(directoryDescription)
holder.checkedView.isVisible = checked
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {
@ -71,5 +77,6 @@ abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>()
val avatarView by bind<ImageView>(R.id.itemRoomDirectoryAvatar) val avatarView by bind<ImageView>(R.id.itemRoomDirectoryAvatar)
val nameView by bind<TextView>(R.id.itemRoomDirectoryName) val nameView by bind<TextView>(R.id.itemRoomDirectoryName)
val descriptionView by bind<TextView>(R.id.itemRoomDirectoryDescription) 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.R
import im.vector.app.core.resources.StringArrayProvider 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.Session
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import javax.inject.Inject import javax.inject.Inject
class RoomDirectoryListCreator @Inject constructor(private val stringArrayProvider: StringArrayProvider, class RoomDirectoryListCreator @Inject constructor(
private val session: Session) { private val stringArrayProvider: StringArrayProvider,
private val session: Session
) {
fun computeDirectories(thirdPartyProtocolData: Map<String, ThirdPartyProtocol>): List<RoomDirectoryData> { fun computeDirectories(thirdPartyProtocolData: Map<String, ThirdPartyProtocol>,
val result = ArrayList<RoomDirectoryData>() customHomeservers: Set<String>): List<RoomDirectoryServer> {
val result = ArrayList<RoomDirectoryServer>()
val protocols = ArrayList<RoomDirectoryData>()
// Add user homeserver name // Add user homeserver name
val userHsName = session.myUserId.substringAfter(":") val userHsName = session.myUserId.substringAfter(":")
result.add(RoomDirectoryData( // Add default protocol
displayName = userHsName, protocols.add(
includeAllNetworks = true RoomDirectoryData(
)) homeServer = null,
displayName = RoomDirectoryData.MATRIX_PROTOCOL_NAME,
// Add user's HS but for Matrix public rooms only includeAllNetworks = false
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 result of the request // Add result of the request
thirdPartyProtocolData.forEach { thirdPartyProtocolData.forEach {
it.value.instances?.forEach { thirdPartyProtocolInstance -> it.value.instances?.forEach { thirdPartyProtocolInstance ->
result.add(RoomDirectoryData( protocols.add(
RoomDirectoryData(
homeServer = null, homeServer = null,
displayName = thirdPartyProtocolInstance.desc ?: "", displayName = thirdPartyProtocolInstance.desc ?: "",
thirdPartyInstanceId = thirdPartyProtocolInstance.instanceId, thirdPartyInstanceId = thirdPartyProtocolInstance.instanceId,
includeAllNetworks = false, includeAllNetworks = false,
// Default to protocol icon // Default to protocol icon
avatarUrl = thirdPartyProtocolInstance.icon ?: it.value.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 return result
} }
} }

View File

@ -17,7 +17,14 @@
package im.vector.app.features.roomdirectory.picker package im.vector.app.features.roomdirectory.picker
import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.roomdirectory.RoomDirectoryServer
sealed class RoomDirectoryPickerAction : VectorViewModelAction { sealed class RoomDirectoryPickerAction : VectorViewModelAction {
object Retry : RoomDirectoryPickerAction() 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 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.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import im.vector.app.R 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.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.extensions.join
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider 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.inject.Inject
import javax.net.ssl.HttpsURLConnection
class RoomDirectoryPickerController @Inject constructor(private val stringProvider: StringProvider, class RoomDirectoryPickerController @Inject constructor(
private val errorFormatter: ErrorFormatter, private val stringProvider: StringProvider,
private val roomDirectoryListCreator: RoomDirectoryListCreator private val colorProvider: ColorProvider,
private val dimensionConverter: DimensionConverter,
private val errorFormatter: ErrorFormatter
) : TypedEpoxyController<RoomDirectoryPickerViewState>() { ) : TypedEpoxyController<RoomDirectoryPickerViewState>() {
var currentRoomDirectoryData: RoomDirectoryData? = null
var callback: Callback? = 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 host = this
val asyncThirdPartyProtocol = viewState.asyncThirdPartyRequest
when (asyncThirdPartyProtocol) { when (val asyncThirdPartyProtocol = data.asyncThirdPartyRequest) {
is Success -> { is Success -> {
val directories = roomDirectoryListCreator.computeDirectories(asyncThirdPartyProtocol()) data.directories.join(
each = { _, roomDirectoryServer -> buildDirectory(roomDirectoryServer) },
directories.forEach { between = { idx, _ -> buildDivider(idx) }
buildDirectory(it) )
buildForm(data)
verticalMarginItem {
id("space_bottom")
heightInPx(host.dimensionConverter.dpToPx(16))
} }
} }
is Incomplete -> { 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 val host = this
roomDirectoryItem { if (data.inEditMode) {
id(host.index++) verticalMarginItem {
id("form_space")
directoryName(roomDirectoryData.displayName) heightInPx(host.dimensionConverter.dpToPx(16))
}
val description = when { settingsInformationItem {
roomDirectoryData.includeAllNetworks -> id("form_notice")
host.stringProvider.getString(R.string.directory_server_all_rooms_on_server, roomDirectoryData.displayName) message(host.stringProvider.getString(R.string.directory_add_a_new_server_prompt))
"Matrix" == roomDirectoryData.displayName -> colorProvider(host.colorProvider)
host.stringProvider.getString(R.string.directory_server_native_rooms, roomDirectoryData.displayName) }
else -> verticalMarginItem {
null 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) directoryAvatarUrl(roomDirectoryData.avatarUrl)
includeAllNetworks(roomDirectoryData.includeAllNetworks) includeAllNetworks(roomDirectoryData.includeAllNetworks)
checked(roomDirectoryData == host.currentRoomDirectoryData)
globalListener { globalListener {
host.callback?.onRoomDirectoryClicked(roomDirectoryData) host.callback?.onRoomDirectoryClicked(roomDirectoryData)
} }
} }
} }
}
interface Callback { interface Callback {
fun onRoomDirectoryClicked(roomDirectoryData: RoomDirectoryData) fun onRoomDirectoryClicked(roomDirectoryData: RoomDirectoryData)
fun retry() 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.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -28,21 +27,22 @@ 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.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.roomdirectory.RoomDirectoryAction 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.RoomDirectorySharedAction
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
import im.vector.app.features.roomdirectory.RoomDirectoryViewModel import im.vector.app.features.roomdirectory.RoomDirectoryViewModel
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject 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, class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerViewModelFactory: RoomDirectoryPickerViewModel.Factory,
private val roomDirectoryPickerController: RoomDirectoryPickerController private val roomDirectoryPickerController: RoomDirectoryPickerController
) : VectorBaseFragment<FragmentRoomDirectoryPickerBinding>(), ) : VectorBaseFragment<FragmentRoomDirectoryPickerBinding>(),
OnBackPressed,
RoomDirectoryPickerController.Callback { RoomDirectoryPickerController.Callback {
private val viewModel: RoomDirectoryViewModel by activityViewModel() private val viewModel: RoomDirectoryViewModel by activityViewModel()
@ -65,6 +65,11 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java) sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
setupRecyclerView() 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() { override fun onDestroyView() {
@ -73,18 +78,6 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
super.onDestroyView() 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() { private fun setupRecyclerView() {
views.roomDirectoryPickerList.configureWith(roomDirectoryPickerController) views.roomDirectoryPickerList.configureWith(roomDirectoryPickerController)
roomDirectoryPickerController.callback = this roomDirectoryPickerController.callback = this
@ -97,6 +90,26 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
sharedActionViewModel.post(RoomDirectorySharedAction.Back) 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() { override fun onResume() {
super.onResume() super.onResume()
(activity as? AppCompatActivity)?.supportActionBar?.setTitle(R.string.select_room_directory) (activity as? AppCompatActivity)?.supportActionBar?.setTitle(R.string.select_room_directory)
@ -111,4 +124,16 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
// Populate list with Epoxy // Populate list with Epoxy
roomDirectoryPickerController.setData(state) 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.Loading
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory 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.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel 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 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.room.model.roomdirectory.PublicRoomsParams
class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initialState: RoomDirectoryPickerViewState, class RoomDirectoryPickerViewModel @AssistedInject constructor(
private val session: Session) @Assisted initialState: RoomDirectoryPickerViewState,
: VectorViewModel<RoomDirectoryPickerViewState, RoomDirectoryPickerAction, EmptyViewEvents>(initialState) { private val session: Session,
private val uiStateRepository: UiStateRepository,
private val stringProvider: StringProvider,
private val roomDirectoryListCreator: RoomDirectoryListCreator
) : VectorViewModel<RoomDirectoryPickerViewState, RoomDirectoryPickerAction, EmptyViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
@ -50,7 +60,22 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initial
} }
init { init {
observeAndCompute()
load() load()
loadCustomRoomDirectoryHomeservers()
}
private fun observeAndCompute() {
selectSubscribe(
RoomDirectoryPickerViewState::asyncThirdPartyRequest,
RoomDirectoryPickerViewState::customHomeservers
) { async, custom ->
async()?.let {
setState {
copy(directories = roomDirectoryListCreator.computeDirectories(it, custom))
}
}
}
} }
private fun load() { 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) { override fun handle(action: RoomDirectoryPickerAction) {
when (action) { 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 ->
// 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.Async
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import im.vector.app.features.roomdirectory.RoomDirectoryServer
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
data class RoomDirectoryPickerViewState( 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 ) : 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.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.roomdirectory.RoomDirectoryData
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom 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 org.matrix.android.sdk.api.util.MatrixItem
import timber.log.Timber import timber.log.Timber

View File

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

View File

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

View File

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

View File

@ -89,6 +89,18 @@ class SharedPreferencesUiStateRepository @Inject constructor(
return sharedPreferences.getBoolean("$KEY_SELECTED_METHOD@$sessionId", true) 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 { companion object {
private const val KEY_DISPLAY_MODE = "UI_STATE_DISPLAY_MODE" private const val KEY_DISPLAY_MODE = "UI_STATE_DISPLAY_MODE"
private const val VALUE_DISPLAY_MODE_CATCHUP = 0 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_SPACE = "UI_STATE_SELECTED_SPACE"
private const val KEY_SELECTED_GROUP = "UI_STATE_SELECTED_GROUP" private const val KEY_SELECTED_GROUP = "UI_STATE_SELECTED_GROUP"
private const val KEY_SELECTED_METHOD = "UI_STATE_SELECTED_METHOD" 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) 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 storeSelectedSpace(spaceId: String?, sessionId: String)
fun storeSelectedGroup(groupId: String?, sessionId: String) fun storeSelectedGroup(groupId: String?, sessionId: String)
@ -40,4 +41,7 @@ interface UiStateRepository {
fun getSelectedSpace(sessionId: String): String? fun getSelectedSpace(sessionId: String): String?
fun getSelectedGroup(sessionId: String): String? fun getSelectedGroup(sessionId: String): String?
fun isGroupingMethodSpace(sessionId: String): Boolean 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"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
@ -15,23 +14,23 @@
android:id="@+id/itemRoomDirectoryAvatar" android:id="@+id/itemRoomDirectoryAvatar"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginStart="8dp" android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:background="@drawable/circle" android:background="@drawable/circle"
android:contentDescription="@string/avatar" android:contentDescription="@string/avatar"
android:padding="8dp" android:padding="8dp"
app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryBottomSeparator" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" /> tools:src="@drawable/network_matrix" />
<TextView <TextView
android:id="@+id/itemRoomDirectoryName" android:id="@+id/itemRoomDirectoryName"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="8dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="8dp"
android:ellipsize="end" android:ellipsize="end"
android:lines="1" android:lines="1"
android:maxLines="2" android:maxLines="2"
@ -39,36 +38,46 @@
android:textSize="15sp" android:textSize="15sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryDescription" 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_constraintStart_toEndOf="@id/itemRoomDirectoryAvatar"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" app:layout_constraintVertical_chainStyle="packed"
app:layout_goneMarginEnd="@dimen/layout_horizontal_margin"
tools:text="@tools:sample/lorem/random" /> tools:text="@tools:sample/lorem/random" />
<TextView <TextView
android:id="@+id/itemRoomDirectoryDescription" android:id="@+id/itemRoomDirectoryDescription"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="8dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="8dp"
android:ellipsize="end" android:ellipsize="end"
android:lines="1" android:lines="1"
android:maxLines="2" android:maxLines="2"
android:textColor="?riotx_text_primary" android:textColor="?riotx_text_primary"
android:textSize="15sp" android:textSize="14sp"
app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryBottomSeparator" android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/itemRoomDirectoryChecked"
app:layout_constraintStart_toEndOf="@id/itemRoomDirectoryAvatar" app:layout_constraintStart_toEndOf="@id/itemRoomDirectoryAvatar"
app:layout_constraintTop_toBottomOf="@id/itemRoomDirectoryName" 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 <ImageView
android:id="@+id/itemRoomDirectoryBottomSeparator" android:id="@+id/itemRoomDirectoryChecked"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="1dp" android:layout_height="wrap_content"
android:background="?riotx_header_panel_border_mobile" 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_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </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"> <string-array name="room_directory_servers" translatable="false">
<item>matrix.org</item> <item>matrix.org</item>
<item>gitter.im</item>
</string-array> </string-array>
</resources> </resources>

View File

@ -1584,9 +1584,14 @@
<string name="select_room_directory">Select a room directory</string> <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_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_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_all_rooms_on_server">All rooms on %s server</string>
<string name="directory_server_native_rooms">All native %s rooms</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--> <!-- Lock screen-->
<string name="lock_screen_hint">Type here…</string> <string name="lock_screen_hint">Type here…</string>