Create RoomDirectoryAPI, and handle deletion of alias

This commit is contained in:
Benoit Marty 2020-11-23 14:30:48 +01:00 committed by Benoit Marty
parent a6f56ace24
commit 5b618ba1f3
12 changed files with 169 additions and 37 deletions

View File

@ -122,6 +122,11 @@ interface RoomService {
searchOnServer: Boolean, searchOnServer: Boolean,
callback: MatrixCallback<Optional<String>>): Cancelable callback: MatrixCallback<Optional<String>>): Cancelable
/**
* Delete a room alias
*/
suspend fun deleteRoomAlias(roomAlias: String)
/** /**
* Return a live data of all local changes membership that happened since the session has been opened. * Return a live data of all local changes membership that happened since the session has been opened.
* It allows you to track this in your client to known what is currently being processed by the SDK. * It allows you to track this in your client to known what is currently being processed by the SDK.

View File

@ -0,0 +1,53 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.session.directory
import org.matrix.android.sdk.internal.network.NetworkConstants
import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasBody
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.PUT
import retrofit2.http.Path
internal interface DirectoryAPI {
/**
* Get the room ID associated to the room alias.
*
* @param roomAlias the room alias.
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call<RoomAliasDescription>
/**
* Add alias to the room.
* @param roomAlias the room alias.
*/
// TODO Remove (https://github.com/matrix-org/matrix-doc/blob/rav/proposal/alt_canonical_aliases/proposals/2432-revised-alias-publishing.md)
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
fun addRoomAlias(@Path("roomAlias") roomAlias: String,
@Body body: AddRoomAliasBody): Call<Unit>
/**
* Delete a room aliases
* @param roomAlias the room alias.
*/
@DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
fun deleteRoomAlias(@Path("roomAlias") roomAlias: String): Call<Unit>
}

View File

@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
@ -53,6 +54,7 @@ internal class DefaultRoomService @Inject constructor(
private val markAllRoomsReadTask: MarkAllRoomsReadTask, private val markAllRoomsReadTask: MarkAllRoomsReadTask,
private val updateBreadcrumbsTask: UpdateBreadcrumbsTask, private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
private val roomIdByAliasTask: GetRoomIdByAliasTask, private val roomIdByAliasTask: GetRoomIdByAliasTask,
private val deleteRoomAliasTask: DeleteRoomAliasTask,
private val roomGetter: RoomGetter, private val roomGetter: RoomGetter,
private val roomSummaryDataSource: RoomSummaryDataSource, private val roomSummaryDataSource: RoomSummaryDataSource,
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
@ -125,6 +127,10 @@ internal class DefaultRoomService @Inject constructor(
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
override suspend fun deleteRoomAlias(roomAlias: String) {
deleteRoomAliasTask.execute(DeleteRoomAliasTask.Params(roomAlias))
}
override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> { override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> {
return roomChangeMembershipStateDataSource.getLiveStates() return roomChangeMembershipStateDataSource.getLiveStates()
} }

View File

@ -23,9 +23,7 @@ import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsRe
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.network.NetworkConstants
import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasBody
import org.matrix.android.sdk.internal.session.room.alias.GetAliasesResponse import org.matrix.android.sdk.internal.session.room.alias.GetAliasesResponse
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
import org.matrix.android.sdk.internal.session.room.create.CreateRoomBody import org.matrix.android.sdk.internal.session.room.create.CreateRoomBody
import org.matrix.android.sdk.internal.session.room.create.CreateRoomResponse import org.matrix.android.sdk.internal.session.room.create.CreateRoomResponse
import org.matrix.android.sdk.internal.session.room.create.JoinRoomResponse import org.matrix.android.sdk.internal.session.room.create.JoinRoomResponse
@ -321,23 +319,6 @@ internal interface RoomAPI {
@Path("eventId") eventId: String, @Path("eventId") eventId: String,
@Body body: ReportContentBody): Call<Unit> @Body body: ReportContentBody): Call<Unit>
/**
* Get the room ID associated to the room alias.
*
* @param roomAlias the room alias.
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call<RoomAliasDescription>
/**
* Add alias to the room.
* @param roomAlias the room alias.
*/
// TODO Remove (https://github.com/matrix-org/matrix-doc/blob/rav/proposal/alt_canonical_aliases/proposals/2432-revised-alias-publishing.md)
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
fun addRoomAlias(@Path("roomAlias") roomAlias: String,
@Body body: AddRoomAliasBody): Call<Unit>
/** /**
* Get local aliases of this room * Get local aliases of this room
*/ */

View File

@ -26,10 +26,13 @@ import org.matrix.android.sdk.api.session.room.RoomDirectoryService
import org.matrix.android.sdk.api.session.room.RoomService import org.matrix.android.sdk.api.session.room.RoomService
import org.matrix.android.sdk.internal.session.DefaultFileService import org.matrix.android.sdk.internal.session.DefaultFileService
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask
import org.matrix.android.sdk.internal.session.room.alias.DefaultAddRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.DefaultAddRoomAliasTask
import org.matrix.android.sdk.internal.session.room.alias.DefaultDeleteRoomAliasTask
import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomIdByAliasTask
import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomLocalAliasesTask import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomLocalAliasesTask
import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
import org.matrix.android.sdk.internal.session.room.alias.GetRoomLocalAliasesTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomLocalAliasesTask
import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
@ -92,6 +95,13 @@ internal abstract class RoomModule {
return retrofit.create(RoomAPI::class.java) return retrofit.create(RoomAPI::class.java)
} }
@Provides
@JvmStatic
@SessionScope
fun providesDirectoryAPI(retrofit: Retrofit): DirectoryAPI {
return retrofit.create(DirectoryAPI::class.java)
}
@Provides @Provides
@JvmStatic @JvmStatic
fun providesParser(): Parser { fun providesParser(): Parser {
@ -189,6 +199,9 @@ internal abstract class RoomModule {
@Binds @Binds
abstract fun bindAddRoomAliasTask(task: DefaultAddRoomAliasTask): AddRoomAliasTask abstract fun bindAddRoomAliasTask(task: DefaultAddRoomAliasTask): AddRoomAliasTask
@Binds
abstract fun bindDeleteRoomAliasTask(task: DefaultDeleteRoomAliasTask): DeleteRoomAliasTask
@Binds @Binds
abstract fun bindSendTypingTask(task: DefaultSendTypingTask): SendTypingTask abstract fun bindSendTypingTask(task: DefaultSendTypingTask): SendTypingTask

View File

@ -16,10 +16,10 @@
package org.matrix.android.sdk.internal.session.room.alias package org.matrix.android.sdk.internal.session.room.alias
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.task.Task
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
internal interface AddRoomAliasTask : Task<AddRoomAliasTask.Params, Unit> { internal interface AddRoomAliasTask : Task<AddRoomAliasTask.Params, Unit> {
@ -30,13 +30,13 @@ internal interface AddRoomAliasTask : Task<AddRoomAliasTask.Params, Unit> {
} }
internal class DefaultAddRoomAliasTask @Inject constructor( internal class DefaultAddRoomAliasTask @Inject constructor(
private val roomAPI: RoomAPI, private val directoryAPI: DirectoryAPI,
private val eventBus: EventBus private val eventBus: EventBus
) : AddRoomAliasTask { ) : AddRoomAliasTask {
override suspend fun execute(params: AddRoomAliasTask.Params) { override suspend fun execute(params: AddRoomAliasTask.Params) {
executeRequest<Unit>(eventBus) { executeRequest<Unit>(eventBus) {
apiCall = roomAPI.addRoomAlias( apiCall = directoryAPI.addRoomAlias(
roomAlias = params.roomAlias, roomAlias = params.roomAlias,
body = AddRoomAliasBody( body = AddRoomAliasBody(
roomId = params.roomId roomId = params.roomId

View File

@ -0,0 +1,43 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.session.room.alias
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface DeleteRoomAliasTask : Task<DeleteRoomAliasTask.Params, Unit> {
data class Params(
val roomAlias: String
)
}
internal class DefaultDeleteRoomAliasTask @Inject constructor(
private val directoryAPI: DirectoryAPI,
private val eventBus: EventBus
) : DeleteRoomAliasTask {
override suspend fun execute(params: DeleteRoomAliasTask.Params) {
executeRequest<Unit>(eventBus) {
apiCall = directoryAPI.deleteRoomAlias(
roomAlias = params.roomAlias
)
}
}
}

View File

@ -25,7 +25,7 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.query.findByAlias import org.matrix.android.sdk.internal.database.query.findByAlias
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -38,7 +38,7 @@ internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Opti
internal class DefaultGetRoomIdByAliasTask @Inject constructor( internal class DefaultGetRoomIdByAliasTask @Inject constructor(
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
private val roomAPI: RoomAPI, private val directoryAPI: DirectoryAPI,
private val eventBus: EventBus private val eventBus: EventBus
) : GetRoomIdByAliasTask { ) : GetRoomIdByAliasTask {
@ -53,7 +53,7 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
} else { } else {
roomId = tryOrNull("## Failed to get roomId from alias") { roomId = tryOrNull("## Failed to get roomId from alias") {
executeRequest<RoomAliasDescription>(eventBus) { executeRequest<RoomAliasDescription>(eventBus) {
apiCall = roomAPI.getRoomIdByAlias(params.roomAlias) apiCall = directoryAPI.getRoomIdByAlias(params.roomAlias)
} }
}?.roomId }?.roomId
Optional.from(roomId) Optional.from(roomId)

View File

@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
@ -47,6 +48,7 @@ internal interface CreateRoomTask : Task<CreateRoomParams, String>
internal class DefaultCreateRoomTask @Inject constructor( internal class DefaultCreateRoomTask @Inject constructor(
private val roomAPI: RoomAPI, private val roomAPI: RoomAPI,
private val directoryAPI: DirectoryAPI,
@UserId private val userId: String, @UserId private val userId: String,
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
private val directChatsHelper: DirectChatsHelper, private val directChatsHelper: DirectChatsHelper,
@ -72,7 +74,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
val fullAlias = "#" + params.roomAliasName + ":" + userId.substringAfter(":") val fullAlias = "#" + params.roomAliasName + ":" + userId.substringAfter(":")
try { try {
executeRequest<RoomAliasDescription>(eventBus) { executeRequest<RoomAliasDescription>(eventBus) {
apiCall = roomAPI.getRoomIdByAlias(fullAlias) apiCall = directoryAPI.getRoomIdByAlias(fullAlias)
} }
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
if (throwable is Failure.ServerError && throwable.httpCode == 404) { if (throwable is Failure.ServerError && throwable.httpCode == 404) {

View File

@ -16,13 +16,16 @@
package im.vector.app.features.roomprofile.alias package im.vector.app.features.roomprofile.alias
import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.dialogs.withColoredButton
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
@ -59,7 +62,7 @@ class RoomAliasFragment @Inject constructor(
viewModel.observeViewEvents { viewModel.observeViewEvents {
when (it) { when (it) {
is RoomAliasViewEvents.Failure -> showFailure(it.throwable) is RoomAliasViewEvents.Failure -> showFailure(it.throwable)
RoomAliasViewEvents.Success -> showSuccess() RoomAliasViewEvents.Success -> showSuccess()
}.exhaustive }.exhaustive
} }
} }
@ -91,7 +94,15 @@ class RoomAliasFragment @Inject constructor(
} }
override fun removeAlias(altAlias: String) { override fun removeAlias(altAlias: String) {
viewModel.handle(RoomAliasAction.RemoveAlias(altAlias)) AlertDialog.Builder(requireContext())
.setTitle(R.string.dialog_title_confirmation)
.setMessage(getString(R.string.room_alias_delete_confirmation, altAlias))
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.delete) { _, _ ->
viewModel.handle(RoomAliasAction.RemoveAlias(altAlias))
}
.show()
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
} }
override fun setCanonicalAlias(alias: String) { override fun setCanonicalAlias(alias: String) {
@ -107,6 +118,14 @@ class RoomAliasFragment @Inject constructor(
} }
override fun removeLocalAlias(alias: String) { override fun removeLocalAlias(alias: String) {
viewModel.handle(RoomAliasAction.RemoveLocalAlias(alias)) AlertDialog.Builder(requireContext())
.setTitle(R.string.dialog_title_confirmation)
.setMessage(getString(R.string.room_alias_delete_confirmation, alias))
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.delete) { _, _ ->
viewModel.handle(RoomAliasAction.RemoveLocalAlias(alias))
}
.show()
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
} }
} }

View File

@ -140,12 +140,12 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo
override fun handle(action: RoomAliasAction) { override fun handle(action: RoomAliasAction) {
when (action) { when (action) {
is RoomAliasAction.AddAlias -> handleAddAlias(action) is RoomAliasAction.AddAlias -> handleAddAlias(action)
is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action) is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action)
is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action)
RoomAliasAction.UnSetCanonicalAlias -> handleUnsetCanonicalAlias() RoomAliasAction.UnSetCanonicalAlias -> handleUnsetCanonicalAlias()
is RoomAliasAction.AddLocalAlias -> handleAddLocalAlias(action) is RoomAliasAction.AddLocalAlias -> handleAddLocalAlias(action)
is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action)
}.exhaustive }.exhaustive
} }
@ -154,7 +154,16 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo
} }
private fun handleRemoveAlias(action: RoomAliasAction.RemoveAlias) { private fun handleRemoveAlias(action: RoomAliasAction.RemoveAlias) {
TODO("Not yet implemented") setState {
copy(isLoading = true)
}
viewModelScope.launch {
runCatching { session.deleteRoomAlias(action.alias) }
.onFailure { _viewEvents.post(RoomAliasViewEvents.Failure(it)) }
setState {
copy(isLoading = false)
}
}
} }
private fun handleSetCanonicalAlias(action: RoomAliasAction.SetCanonicalAlias) { private fun handleSetCanonicalAlias(action: RoomAliasAction.SetCanonicalAlias) {

View File

@ -1031,6 +1031,7 @@
<string name="room_alias_published_alias_subtitle">Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.</string> <string name="room_alias_published_alias_subtitle">Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.</string>
<string name="room_alias_main_address_hint">Main address</string> <string name="room_alias_main_address_hint">Main address</string>
<string name="room_alias_published_other">Other published addresses:</string> <string name="room_alias_published_other">Other published addresses:</string>
<string name="room_alias_delete_confirmation">Delete the address \"%1$s\"?</string>
<!-- Parameter will be the url of the homeserver, ex: matrix.org --> <!-- Parameter will be the url of the homeserver, ex: matrix.org -->
<string name="room_alias_publish">Publish this room to the public in %1$s\'s room directory?</string> <string name="room_alias_publish">Publish this room to the public in %1$s\'s room directory?</string>
<string name="room_alias_address_empty">No other published addresses yet, add one below</string> <string name="room_alias_address_empty">No other published addresses yet, add one below</string>