Merge pull request #2439 from vector-im/feature/bma/alias

Add room alias management
This commit is contained in:
Benoit Marty 2020-11-30 14:59:21 +01:00 committed by GitHub
commit 93ffb116b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 2244 additions and 220 deletions

View file

@ -31,6 +31,7 @@
<w>ssss</w>
<w>sygnal</w>
<w>threepid</w>
<w>unpublish</w>
<w>unwedging</w>
</words>
</dictionary>

View file

@ -2,7 +2,7 @@ Changes in Element 1.0.12 (2020-XX-XX)
===================================================
Features ✨:
-
- Add room aliases management, and room directory visibility management in a dedicated screen (#1579, #2428)
Improvements 🙌:
- Add Setting Item to Change PIN (#2462)

View file

@ -127,14 +127,6 @@ class RxRoom(private val room: Room) {
room.updateName(name, it)
}
fun addRoomAlias(alias: String): Completable = completableBuilder<Unit> {
room.addRoomAlias(alias, it)
}
fun updateCanonicalAlias(alias: String): Completable = completableBuilder<Unit> {
room.updateCanonicalAlias(alias, it)
}
fun updateHistoryReadability(readability: RoomHistoryVisibility): Completable = completableBuilder<Unit> {
room.updateHistoryReadability(readability, it)
}

View file

@ -49,6 +49,12 @@ object EventType {
const val STATE_ROOM_JOIN_RULES = "m.room.join_rules"
const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
/**
* Note that this Event has been deprecated, see
* - https://matrix.org/docs/spec/client_server/r0.6.1#historical-events
* - https://github.com/matrix-org/matrix-doc/pull/2432
*/
const val STATE_ROOM_ALIASES = "m.room.aliases"
const val STATE_ROOM_TOMBSTONE = "m.room.tombstone"
const val STATE_ROOM_CANONICAL_ALIAS = "m.room.canonical_alias"

View file

@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room
import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.room.alias.AliasService
import org.matrix.android.sdk.api.session.room.call.RoomCallService
import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
import org.matrix.android.sdk.api.session.room.members.MembershipService
@ -46,6 +47,7 @@ interface Room :
DraftService,
ReadService,
TypingService,
AliasService,
TagsService,
MembershipService,
StateService,

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.api.session.room
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
@ -39,4 +40,14 @@ interface RoomDirectoryService {
* Includes both the available protocols and all fields required for queries against each protocol.
*/
fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>): Cancelable
/**
* Get the visibility of a room in the directory
*/
suspend fun getRoomDirectoryVisibility(roomId: String): RoomDirectoryVisibility
/**
* Set the visibility of a room in the directory
*/
suspend fun setRoomDirectoryVisibility(roomId: String, roomDirectoryVisibility: RoomDirectoryVisibility)
}

View file

@ -122,6 +122,11 @@ interface RoomService {
searchOnServer: Boolean,
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.
* It allows you to track this in your client to known what is currently being processed by the SDK.

View file

@ -0,0 +1,32 @@
/*
* 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.api.session.room.alias
interface AliasService {
/**
* Get list of local alias of the room
* @return the list of the aliases (full aliases, not only the local part)
*/
suspend fun getRoomAliases(): List<String>
/**
* Add local alias to the room
* @param aliasLocalPart the local part of the alias.
* Ex: for the alias "#my_alias:example.org", the local part is "my_alias"
*/
suspend fun addAlias(aliasLocalPart: String)
}

View file

@ -0,0 +1,23 @@
/*
* 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.api.session.room.alias
sealed class RoomAliasError : Throwable() {
object AliasEmpty : RoomAliasError()
object AliasNotAvailable : RoomAliasError()
object AliasInvalid : RoomAliasError()
}

View file

@ -18,13 +18,10 @@ package org.matrix.android.sdk.api.session.room.failure
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
sealed class CreateRoomFailure : Failure.FeatureFailure() {
object CreatedWithTimeout : CreateRoomFailure()
data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure()
sealed class RoomAliasError : CreateRoomFailure() {
object AliasEmpty : RoomAliasError()
object AliasNotAvailable : RoomAliasError()
object AliasInvalid : RoomAliasError()
}
data class AliasError(val aliasError: RoomAliasError) : CreateRoomFailure()
}

View file

@ -21,6 +21,9 @@ import com.squareup.moshi.JsonClass
/**
* Class representing the EventType.STATE_ROOM_ALIASES state event content
* Note that this Event has been deprecated, see
* - https://matrix.org/docs/spec/client_server/r0.6.1#historical-events
* - https://github.com/matrix-org/matrix-doc/pull/2432
*/
@JsonClass(generateAdapter = true)
data class RoomAliasesContent(

View file

@ -24,5 +24,14 @@ import com.squareup.moshi.JsonClass
*/
@JsonClass(generateAdapter = true)
data class RoomCanonicalAliasContent(
@Json(name = "alias") val canonicalAlias: String? = null
/**
* The canonical alias for the room. If not present, null, or empty the room should be considered to have no canonical alias.
*/
@Json(name = "alias") val canonicalAlias: String? = null,
/**
* Alternative aliases the room advertises.
* This list can have aliases despite the alias field being null, empty, or otherwise not present.
*/
@Json(name = "alt_aliases") val alternativeAliases: List<String>? = null
)

View file

@ -38,15 +38,12 @@ interface StateService {
*/
fun updateName(name: String, callback: MatrixCallback<Unit>): Cancelable
/**
* Add new alias to the room.
*/
fun addRoomAlias(roomAlias: String, callback: MatrixCallback<Unit>): Cancelable
/**
* Update the canonical alias of the room
* @param alias the canonical alias, or null to reset the canonical alias of this room
* @param altAliases the alternative aliases for this room. It should include the canonical alias if any.
*/
fun updateCanonicalAlias(alias: String, callback: MatrixCallback<Unit>): Cancelable
fun updateCanonicalAlias(alias: String?, altAliases: List<String>, callback: MatrixCallback<Unit>): Cancelable
/**
* Update the history readability of the room

View file

@ -0,0 +1,70 @@
/*
* 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>
/**
* Get the room directory visibility.
*
* @param roomId the room id.
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}")
fun getRoomDirectoryVisibility(@Path("roomId") roomId: String): Call<RoomDirectoryVisibilityJson>
/**
* Set the room directory visibility.
*
* @param roomId the room id.
* @param body the body containing the new directory visibility
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}")
fun setRoomDirectoryVisibility(@Path("roomId") roomId: String,
@Body body: RoomDirectoryVisibilityJson): Call<Unit>
/**
* Add alias to the room.
* @param roomAlias the room alias.
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
fun addRoomAlias(@Path("roomAlias") roomAlias: String,
@Body body: AddRoomAliasBody): Call<Unit>
/**
* Delete a room alias
* @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

@ -0,0 +1,29 @@
/*
* 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 com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
@JsonClass(generateAdapter = true)
internal data class RoomDirectoryVisibilityJson(
/**
* The visibility of the room in the directory. One of: ["private", "public"]
*/
@Json(name = "visibility") val visibility: RoomDirectoryVisibility
)

View file

@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.alias.AliasService
import org.matrix.android.sdk.api.session.room.call.RoomCallService
import org.matrix.android.sdk.api.session.room.members.MembershipService
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@ -58,6 +59,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
private val roomCallService: RoomCallService,
private val readService: ReadService,
private val typingService: TypingService,
private val aliasService: AliasService,
private val tagsService: TagsService,
private val cryptoService: CryptoService,
private val relationService: RelationService,
@ -76,6 +78,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
RoomCallService by roomCallService,
ReadService by readService,
TypingService by typingService,
AliasService by aliasService,
TagsService by tagsService,
RelationService by relationService,
MembershipService by roomMembersService,

View file

@ -18,19 +18,25 @@ package org.matrix.android.sdk.internal.session.room
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.room.RoomDirectoryService
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.directory.GetThirdPartyProtocolsTask
import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import javax.inject.Inject
internal class DefaultRoomDirectoryService @Inject constructor(private val getPublicRoomTask: GetPublicRoomTask,
private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask,
private val taskExecutor: TaskExecutor) : RoomDirectoryService {
internal class DefaultRoomDirectoryService @Inject constructor(
private val getPublicRoomTask: GetPublicRoomTask,
private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask,
private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask,
private val setRoomDirectoryVisibilityTask: SetRoomDirectoryVisibilityTask,
private val taskExecutor: TaskExecutor) : RoomDirectoryService {
override fun getPublicRooms(server: String?,
publicRoomsParams: PublicRoomsParams,
@ -49,4 +55,12 @@ internal class DefaultRoomDirectoryService @Inject constructor(private val getPu
}
.executeBy(taskExecutor)
}
override suspend fun getRoomDirectoryVisibility(roomId: String): RoomDirectoryVisibility {
return getRoomDirectoryVisibilityTask.execute(GetRoomDirectoryVisibilityTask.Params(roomId))
}
override suspend fun setRoomDirectoryVisibility(roomId: String, roomDirectoryVisibility: RoomDirectoryVisibility) {
setRoomDirectoryVisibilityTask.execute(SetRoomDirectoryVisibilityTask.Params(roomId, roomDirectoryVisibility))
}
}

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.model.RoomMemberSummaryEntityFields
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.create.CreateRoomTask
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 updateBreadcrumbsTask: UpdateBreadcrumbsTask,
private val roomIdByAliasTask: GetRoomIdByAliasTask,
private val deleteRoomAliasTask: DeleteRoomAliasTask,
private val roomGetter: RoomGetter,
private val roomSummaryDataSource: RoomSummaryDataSource,
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
@ -125,6 +127,10 @@ internal class DefaultRoomService @Inject constructor(
.executeBy(taskExecutor)
}
override suspend fun deleteRoomAlias(roomAlias: String) {
deleteRoomAliasTask.execute(DeleteRoomAliasTask.Params(roomAlias))
}
override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> {
return roomChangeMembershipStateDataSource.getLiveStates()
}

View file

@ -23,8 +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.util.JsonDict
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 org.matrix.android.sdk.internal.session.room.alias.GetAliasesResponse
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.JoinRoomResponse
@ -321,20 +320,11 @@ internal interface RoomAPI {
@Body body: ReportContentBody): Call<Unit>
/**
* Get the room ID associated to the room alias.
*
* @param roomAlias the room alias.
* Get a list of aliases maintained by the local server for the given room.
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-aliases
*/
@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.
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
fun addRoomAlias(@Path("roomAlias") roomAlias: String,
@Body body: AddRoomAliasBody): Call<Unit>
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2432/rooms/{roomId}/aliases")
fun getAliases(@Path("roomId") roomId: String): Call<GetAliasesResponse>
/**
* Inform that the user is starting to type or has stopped typing

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService
import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService
import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService
import org.matrix.android.sdk.internal.session.room.membership.DefaultMembershipService
@ -54,6 +55,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
private val roomCallServiceFactory: DefaultRoomCallService.Factory,
private val readServiceFactory: DefaultReadService.Factory,
private val typingServiceFactory: DefaultTypingService.Factory,
private val aliasServiceFactory: DefaultAliasService.Factory,
private val tagsServiceFactory: DefaultTagsService.Factory,
private val relationServiceFactory: DefaultRelationService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory,
@ -76,6 +78,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
roomCallService = roomCallServiceFactory.create(roomId),
readService = readServiceFactory.create(roomId),
typingService = typingServiceFactory.create(roomId),
aliasService = aliasServiceFactory.create(roomId),
tagsService = tagsServiceFactory.create(roomId),
cryptoService = cryptoService,
relationService = relationServiceFactory.create(roomId),

View file

@ -26,16 +26,25 @@ import org.matrix.android.sdk.api.session.room.RoomDirectoryService
import org.matrix.android.sdk.api.session.room.RoomService
import org.matrix.android.sdk.internal.session.DefaultFileService
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.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.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.GetRoomLocalAliasesTask
import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomTask
import org.matrix.android.sdk.internal.session.room.directory.DefaultGetPublicRoomTask
import org.matrix.android.sdk.internal.session.room.directory.DefaultGetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.directory.DefaultGetThirdPartyProtocolsTask
import org.matrix.android.sdk.internal.session.room.directory.DefaultSetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.directory.GetThirdPartyProtocolsTask
import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.membership.DefaultLoadRoomMembersTask
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.session.room.membership.admin.DefaultMembershipAdminTask
@ -90,6 +99,13 @@ internal abstract class RoomModule {
return retrofit.create(RoomAPI::class.java)
}
@Provides
@JvmStatic
@SessionScope
fun providesDirectoryAPI(retrofit: Retrofit): DirectoryAPI {
return retrofit.create(DirectoryAPI::class.java)
}
@Provides
@JvmStatic
fun providesParser(): Parser {
@ -127,6 +143,12 @@ internal abstract class RoomModule {
@Binds
abstract fun bindGetPublicRoomTask(task: DefaultGetPublicRoomTask): GetPublicRoomTask
@Binds
abstract fun bindGetRoomDirectoryVisibilityTask(task: DefaultGetRoomDirectoryVisibilityTask): GetRoomDirectoryVisibilityTask
@Binds
abstract fun bindSetRoomDirectoryVisibilityTask(task: DefaultSetRoomDirectoryVisibilityTask): SetRoomDirectoryVisibilityTask
@Binds
abstract fun bindGetThirdPartyProtocolsTask(task: DefaultGetThirdPartyProtocolsTask): GetThirdPartyProtocolsTask
@ -181,9 +203,15 @@ internal abstract class RoomModule {
@Binds
abstract fun bindGetRoomIdByAliasTask(task: DefaultGetRoomIdByAliasTask): GetRoomIdByAliasTask
@Binds
abstract fun bindGetRoomLocalAliasesTask(task: DefaultGetRoomLocalAliasesTask): GetRoomLocalAliasesTask
@Binds
abstract fun bindAddRoomAliasTask(task: DefaultAddRoomAliasTask): AddRoomAliasTask
@Binds
abstract fun bindDeleteRoomAliasTask(task: DefaultDeleteRoomAliasTask): DeleteRoomAliasTask
@Binds
abstract fun bindSendTypingTask(task: DefaultSendTypingTask): SendTypingTask

View file

@ -16,28 +16,38 @@
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.matrix.android.sdk.internal.di.UserId
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.alias.RoomAliasAvailabilityChecker.Companion.toFullLocalAlias
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface AddRoomAliasTask : Task<AddRoomAliasTask.Params, Unit> {
data class Params(
val roomId: String,
val roomAlias: String
/**
* the local part of the alias.
* Ex: for the alias "#my_alias:example.org", the local part is "my_alias"
*/
val aliasLocalPart: String
)
}
internal class DefaultAddRoomAliasTask @Inject constructor(
private val roomAPI: RoomAPI,
@UserId private val userId: String,
private val directoryAPI: DirectoryAPI,
private val aliasAvailabilityChecker: RoomAliasAvailabilityChecker,
private val eventBus: EventBus
) : AddRoomAliasTask {
override suspend fun execute(params: AddRoomAliasTask.Params) {
aliasAvailabilityChecker.check(params.aliasLocalPart)
executeRequest<Unit>(eventBus) {
apiCall = roomAPI.addRoomAlias(
roomAlias = params.roomAlias,
apiCall = directoryAPI.addRoomAlias(
roomAlias = params.aliasLocalPart.toFullLocalAlias(userId),
body = AddRoomAliasBody(
roomId = params.roomId
)

View file

@ -0,0 +1,41 @@
/*
* 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 com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import org.matrix.android.sdk.api.session.room.alias.AliasService
internal class DefaultAliasService @AssistedInject constructor(
@Assisted private val roomId: String,
private val getRoomLocalAliasesTask: GetRoomLocalAliasesTask,
private val addRoomAliasTask: AddRoomAliasTask
) : AliasService {
@AssistedInject.Factory
interface Factory {
fun create(roomId: String): AliasService
}
override suspend fun getRoomAliases(): List<String> {
return getRoomLocalAliasesTask.execute(GetRoomLocalAliasesTask.Params(roomId))
}
override suspend fun addAlias(aliasLocalPart: String) {
addRoomAliasTask.execute(AddRoomAliasTask.Params(roomId, aliasLocalPart))
}
}

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

@ -0,0 +1,28 @@
/*
* 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 com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class GetAliasesResponse(
/**
* Required. The server's local aliases on the room. Can be empty.
*/
@Json(name = "aliases") val aliases: List<String> = emptyList()
)

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.di.SessionDatabase
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 javax.inject.Inject
@ -38,7 +38,7 @@ internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Opti
internal class DefaultGetRoomIdByAliasTask @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
private val roomAPI: RoomAPI,
private val directoryAPI: DirectoryAPI,
private val eventBus: EventBus
) : GetRoomIdByAliasTask {
@ -53,7 +53,7 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
} else {
roomId = tryOrNull("## Failed to get roomId from alias") {
executeRequest<RoomAliasDescription>(eventBus) {
apiCall = roomAPI.getRoomIdByAlias(params.roomAlias)
apiCall = directoryAPI.getRoomIdByAlias(params.roomAlias)
}
}?.roomId
Optional.from(roomId)

View file

@ -0,0 +1,44 @@
/*
* 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.room.RoomAPI
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface GetRoomLocalAliasesTask : Task<GetRoomLocalAliasesTask.Params, List<String>> {
data class Params(
val roomId: String
)
}
internal class DefaultGetRoomLocalAliasesTask @Inject constructor(
private val roomAPI: RoomAPI,
private val eventBus: EventBus
) : GetRoomLocalAliasesTask {
override suspend fun execute(params: GetRoomLocalAliasesTask.Params): List<String> {
// We do not check for "org.matrix.msc2432", so the API may be missing
val response = executeRequest<GetAliasesResponse>(eventBus) {
apiCall = roomAPI.getAliases(roomId = params.roomId)
}
return response.aliases
}
}

View file

@ -0,0 +1,65 @@
/*
* 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.api.failure.Failure
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import javax.inject.Inject
internal class RoomAliasAvailabilityChecker @Inject constructor(
@UserId private val userId: String,
private val directoryAPI: DirectoryAPI,
private val eventBus: EventBus
) {
/**
* @param aliasLocalPart the local part of the alias.
* Ex: for the alias "#my_alias:example.org", the local part is "my_alias"
*/
@Throws(RoomAliasError::class)
suspend fun check(aliasLocalPart: String?) {
if (aliasLocalPart.isNullOrEmpty()) {
throw RoomAliasError.AliasEmpty
}
// Check alias availability
val fullAlias = aliasLocalPart.toFullLocalAlias(userId)
try {
executeRequest<RoomAliasDescription>(eventBus) {
apiCall = directoryAPI.getRoomIdByAlias(fullAlias)
}
} catch (throwable: Throwable) {
if (throwable is Failure.ServerError && throwable.httpCode == 404) {
// This is a 404, so the alias is available: nominal case
null
} else {
// Other error, propagate it
throw throwable
}
}
?.let {
// Alias already exists: error case
throw RoomAliasError.AliasNotAvailable
}
}
companion object {
internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.substringAfter(":")
}
}

View file

@ -22,6 +22,7 @@ import kotlinx.coroutines.TimeoutCancellationException
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
@ -31,10 +32,9 @@ import org.matrix.android.sdk.internal.database.model.RoomEntityFields
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.executeRequest
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.RoomAliasAvailabilityChecker
import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
@ -47,8 +47,8 @@ internal interface CreateRoomTask : Task<CreateRoomParams, String>
internal class DefaultCreateRoomTask @Inject constructor(
private val roomAPI: RoomAPI,
@UserId private val userId: String,
@SessionDatabase private val monarchy: Monarchy,
private val aliasAvailabilityChecker: RoomAliasAvailabilityChecker,
private val directChatsHelper: DirectChatsHelper,
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val readMarkersTask: SetReadMarkersTask,
@ -65,28 +65,11 @@ internal class DefaultCreateRoomTask @Inject constructor(
} else null
if (params.preset == CreateRoomPreset.PRESET_PUBLIC_CHAT) {
if (params.roomAliasName.isNullOrEmpty()) {
throw CreateRoomFailure.RoomAliasError.AliasEmpty
}
// Check alias availability
val fullAlias = "#" + params.roomAliasName + ":" + userId.substringAfter(":")
try {
executeRequest<RoomAliasDescription>(eventBus) {
apiCall = roomAPI.getRoomIdByAlias(fullAlias)
}
} catch (throwable: Throwable) {
if (throwable is Failure.ServerError && throwable.httpCode == 404) {
// This is a 404, so the alias is available: nominal case
null
} else {
// Other error, propagate it
throw throwable
}
aliasAvailabilityChecker.check(params.roomAliasName)
} catch (aliasError: RoomAliasError) {
throw CreateRoomFailure.AliasError(aliasError)
}
?.let {
// Alias already exists: error case
throw CreateRoomFailure.RoomAliasError.AliasNotAvailable
}
}
val createRoomBody = createRoomBodyBuilder.build(params)
@ -104,7 +87,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
} else if (throwable.httpCode == 400
&& throwable.error.code == MatrixError.M_UNKNOWN
&& throwable.error.message == "Invalid characters in room alias") {
throw CreateRoomFailure.RoomAliasError.AliasInvalid
throw CreateRoomFailure.AliasError(RoomAliasError.AliasInvalid)
}
}
throw throwable

View file

@ -0,0 +1,44 @@
/*
* 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.directory
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import org.matrix.android.sdk.internal.session.directory.RoomDirectoryVisibilityJson
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface GetRoomDirectoryVisibilityTask : Task<GetRoomDirectoryVisibilityTask.Params, RoomDirectoryVisibility> {
data class Params(
val roomId: String
)
}
internal class DefaultGetRoomDirectoryVisibilityTask @Inject constructor(
private val directoryAPI: DirectoryAPI,
private val eventBus: EventBus
) : GetRoomDirectoryVisibilityTask {
override suspend fun execute(params: GetRoomDirectoryVisibilityTask.Params): RoomDirectoryVisibility {
return executeRequest<RoomDirectoryVisibilityJson>(eventBus) {
apiCall = directoryAPI.getRoomDirectoryVisibility(params.roomId)
}
.visibility
}
}

View file

@ -0,0 +1,47 @@
/*
* 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.directory
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import org.matrix.android.sdk.internal.session.directory.RoomDirectoryVisibilityJson
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface SetRoomDirectoryVisibilityTask : Task<SetRoomDirectoryVisibilityTask.Params, Unit> {
data class Params(
val roomId: String,
val roomDirectoryVisibility: RoomDirectoryVisibility
)
}
internal class DefaultSetRoomDirectoryVisibilityTask @Inject constructor(
private val directoryAPI: DirectoryAPI,
private val eventBus: EventBus
) : SetRoomDirectoryVisibilityTask {
override suspend fun execute(params: SetRoomDirectoryVisibilityTask.Params) {
executeRequest<Unit>(eventBus) {
apiCall = directoryAPI.setRoomDirectoryVisibility(
params.roomId,
RoomDirectoryVisibilityJson(visibility = params.roomDirectoryVisibility)
)
}
}
}

View file

@ -21,7 +21,6 @@ import org.matrix.android.sdk.R
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
@ -71,12 +70,6 @@ internal class RoomDisplayNameResolver @Inject constructor(
return name
}
val aliases = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root
name = ContentMapper.map(aliases?.content).toModel<RoomAliasesContent>()?.aliases?.firstOrNull()
if (!name.isNullOrEmpty()) {
return name
}
val roomMembers = RoomMemberHelper(realm, roomId)
val activeMembers = roomMembers.queryActiveRoomMembersEvent().findAll()

View file

@ -24,6 +24,8 @@ import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.state.StateService
import org.matrix.android.sdk.api.util.Cancelable
@ -104,18 +106,19 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
)
}
override fun addRoomAlias(roomAlias: String, callback: MatrixCallback<Unit>): Cancelable {
return addRoomAliasTask
.configureWith(AddRoomAliasTask.Params(roomId, roomAlias)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun updateCanonicalAlias(alias: String, callback: MatrixCallback<Unit>): Cancelable {
override fun updateCanonicalAlias(alias: String?, altAliases: List<String>, callback: MatrixCallback<Unit>): Cancelable {
return sendStateEvent(
eventType = EventType.STATE_ROOM_CANONICAL_ALIAS,
body = mapOf("alias" to alias),
body = RoomCanonicalAliasContent(
canonicalAlias = alias,
alternativeAliases = altAliases
// Ensure there is no duplicate
.distinct()
// Ensure the canonical alias is not also included in the alt alias
.minus(listOfNotNull(alias))
// Sort for the cleanup
.sorted()
).toContent(),
callback = callback,
stateKey = null
)

View file

@ -246,7 +246,7 @@
<plurals name="notice_room_aliases_removed">
<item quantity="one">%1$s removed %2$s as an address for this room.</item>
<item quantity="other">%1$s removed %3$s as addresses for this room.</item>
<item quantity="other">%1$s removed %2$s as addresses for this room.</item>
</plurals>
<plurals name="notice_room_aliases_removed_by_you">
@ -262,6 +262,33 @@
<string name="notice_room_canonical_alias_unset">"%1$s removed the main address for this room."</string>
<string name="notice_room_canonical_alias_unset_by_you">"You removed the main address for this room."</string>
<plurals name="notice_room_canonical_alias_alternative_added">
<item quantity="one">%1$s added the alternative address %2$s for this room.</item>
<item quantity="other">%1$s added the alternative addresses %2$s for this room.</item>
</plurals>
<plurals name="notice_room_canonical_alias_alternative_added_by_you">
<item quantity="one">You added the alternative address %1$s for this room.</item>
<item quantity="other">You added the alternative addresses %1$s for this room.</item>
</plurals>
<plurals name="notice_room_canonical_alias_alternative_removed">
<item quantity="one">%1$s removed the alternative address %2$s for this room.</item>
<item quantity="other">%1$s removed the alternative addresses %2$s for this room.</item>
</plurals>
<plurals name="notice_room_canonical_alias_alternative_removed_by_you">
<item quantity="one">You removed the alternative address %1$s for this room.</item>
<item quantity="other">You removed the alternative addresses %1$s for this room.</item>
</plurals>
<string name="notice_room_canonical_alias_alternative_changed">%1$s changed the alternative addresses for this room.</string>
<string name="notice_room_canonical_alias_alternative_changed_by_you">You changed the alternative addresses for this room.</string>
<string name="notice_room_canonical_alias_main_and_alternative_changed">%1$s changed the main and alternative addresses for this room.</string>
<string name="notice_room_canonical_alias_main_and_alternative_changed_by_you">You changed the main and alternative addresses for this room.</string>
<string name="notice_room_canonical_alias_no_change">%1$s changed the addresses for this room.</string>
<string name="notice_room_canonical_alias_no_change_by_you">You changed the addresses for this room.</string>
<string name="notice_room_guest_access_can_join">"%1$s has allowed guests to join the room."</string>
<string name="notice_room_guest_access_can_join_by_you">"You have allowed guests to join the room."</string>
<string name="notice_direct_room_guest_access_can_join">"%1$s has allowed guests to join here."</string>

View file

@ -84,6 +84,7 @@ import im.vector.app.features.roomprofile.RoomProfileFragment
import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment
import im.vector.app.features.roomprofile.members.RoomMemberListFragment
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
import im.vector.app.features.roomprofile.alias.RoomAliasFragment
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment
import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment
@ -364,6 +365,11 @@ interface FragmentModule {
@FragmentKey(RoomSettingsFragment::class)
fun bindRoomSettingsFragment(fragment: RoomSettingsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomAliasFragment::class)
fun bindRoomAliasFragment(fragment: RoomAliasFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomMemberProfileFragment::class)

View file

@ -67,6 +67,7 @@ import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
import im.vector.app.features.roommemberprofile.RoomMemberProfileActivity
import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet
import im.vector.app.features.roomprofile.RoomProfileActivity
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet
import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet
import im.vector.app.features.share.IncomingShareActivity
@ -153,6 +154,7 @@ interface ScreenComponent {
fun inject(bottomSheet: ViewEditHistoryBottomSheet)
fun inject(bottomSheet: DisplayReadReceiptsBottomSheet)
fun inject(bottomSheet: RoomListQuickActionsBottomSheet)
fun inject(bottomSheet: RoomAliasBottomSheet)
fun inject(bottomSheet: VerificationBottomSheet)
fun inject(bottomSheet: DeviceVerificationInfoBottomSheet)
fun inject(bottomSheet: DeviceListBottomSheet)

View file

@ -35,6 +35,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
import im.vector.app.features.reactions.EmojiChooserViewModel
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
@Module
@ -105,6 +106,11 @@ interface ViewModelModule {
@ViewModelKey(RoomListQuickActionsSharedActionViewModel::class)
fun bindRoomListQuickActionsSharedActionViewModel(viewModel: RoomListQuickActionsSharedActionViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(RoomAliasBottomSheetSharedActionViewModel::class)
fun bindRoomAliasBottomSheetSharedActionViewModel(viewModel: RoomAliasBottomSheetSharedActionViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(RoomDirectorySharedActionViewModel::class)

View file

@ -17,7 +17,6 @@
package im.vector.app.core.epoxy.profiles
import android.content.res.ColorStateList
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
@ -26,8 +25,10 @@ import androidx.core.widget.ImageViewCompat
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.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
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.themes.ThemeUtils
@ -67,11 +68,11 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
var destructive: Boolean = false
@EpoxyAttribute
var listener: View.OnClickListener? = null
var listener: ClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.view.setOnClickListener(listener)
holder.view.onClick(listener)
if (listener == null) {
holder.view.isClickable = false
}

View file

@ -59,9 +59,7 @@ fun EpoxyController.buildProfileAction(
accessoryRes(accessory)
accessoryMatrixItem(accessoryMatrixItem)
avatarRenderer(avatarRenderer)
listener { _ ->
action?.invoke()
}
listener(action)
}
if (divider) {

View file

@ -27,6 +27,9 @@ import im.vector.app.core.epoxy.onClick
@EpoxyModelClass(layout = R.layout.item_settings_continue_cancel)
abstract class SettingsContinueCancelItem : EpoxyModelWithHolder<SettingsContinueCancelItem.Holder>() {
@EpoxyAttribute
var continueText: String? = null
@EpoxyAttribute
var continueOnClick: ClickListener? = null
@ -37,6 +40,8 @@ abstract class SettingsContinueCancelItem : EpoxyModelWithHolder<SettingsContinu
super.bind(holder)
holder.cancelButton.onClick(cancelOnClick)
continueText?.let { holder.continueButton.text = it }
holder.continueButton.onClick(continueOnClick)
}

View file

@ -61,8 +61,8 @@ abstract class FormSwitchItem : VectorEpoxyModel<FormSwitchItem.Holder>() {
holder.switchView.isEnabled = enabled
holder.switchView.setOnCheckedChangeListener(null)
holder.switchView.isChecked = switchChecked
holder.switchView.setOnCheckedChangeListener { _, isChecked ->
listener?.invoke(isChecked)
}

View file

@ -53,7 +53,6 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.STATE_ROOM_AVATAR,
EventType.STATE_ROOM_MEMBER,
EventType.STATE_ROOM_THIRD_PARTY_INVITE,
EventType.STATE_ROOM_ALIASES,
EventType.STATE_ROOM_CANONICAL_ALIAS,
EventType.STATE_ROOM_JOIN_RULES,
EventType.STATE_ROOM_HISTORY_VISIBILITY,
@ -79,6 +78,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
encryptedItemFactory.create(event, nextEvent, highlight, callback)
}
}
EventType.STATE_ROOM_ALIASES,
EventType.KEY_VERIFICATION_ACCEPT,
EventType.KEY_VERIFICATION_START,
EventType.KEY_VERIFICATION_KEY,

View file

@ -465,21 +465,72 @@ class NoticeEventFormatter @Inject constructor(
private fun formatRoomCanonicalAliasEvent(event: Event, senderName: String?): String? {
val eventContent: RoomCanonicalAliasContent? = event.getClearContent().toModel()
val canonicalAlias = eventContent?.canonicalAlias
return canonicalAlias
?.takeIf { it.isNotBlank() }
?.let {
val prevContent: RoomCanonicalAliasContent? = event.resolvedPrevContent().toModel()
val canonicalAlias = eventContent?.canonicalAlias?.takeIf { it.isNotEmpty() }
val prevCanonicalAlias = prevContent?.canonicalAlias?.takeIf { it.isNotEmpty() }
val altAliases = eventContent?.alternativeAliases.orEmpty()
val prevAltAliases = prevContent?.alternativeAliases.orEmpty()
val added = altAliases - prevAltAliases
val removed = prevAltAliases - altAliases
return when {
added.isEmpty() && removed.isEmpty() && canonicalAlias == prevCanonicalAlias -> {
// No difference between the two events say something as we can't simply hide the event from here
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_canonical_alias_no_change_by_you)
} else {
sp.getString(R.string.notice_room_canonical_alias_no_change, senderName)
}
}
added.isEmpty() && removed.isEmpty() -> {
// Canonical has changed
if (canonicalAlias != null) {
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_canonical_alias_set_by_you, it)
sp.getString(R.string.notice_room_canonical_alias_set_by_you, canonicalAlias)
} else {
sp.getString(R.string.notice_room_canonical_alias_set, senderName, it)
sp.getString(R.string.notice_room_canonical_alias_set, senderName, canonicalAlias)
}
} else {
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_canonical_alias_unset_by_you)
} else {
sp.getString(R.string.notice_room_canonical_alias_unset, senderName)
}
}
?: if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_canonical_alias_unset_by_you)
}
added.isEmpty() && canonicalAlias == prevCanonicalAlias -> {
// Some alternative has been removed
if (event.isSentByCurrentUser()) {
sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_removed_by_you, removed.size, removed.joinToString())
} else {
sp.getString(R.string.notice_room_canonical_alias_unset, senderName)
sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_removed, removed.size, senderName, removed.joinToString())
}
}
removed.isEmpty() && canonicalAlias == prevCanonicalAlias -> {
// Some alternative has been added
if (event.isSentByCurrentUser()) {
sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_added_by_you, added.size, added.joinToString())
} else {
sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_added, added.size, senderName, added.joinToString())
}
}
canonicalAlias == prevCanonicalAlias -> {
// Alternative added and removed
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_canonical_alias_alternative_changed_by_you)
} else {
sp.getString(R.string.notice_room_canonical_alias_alternative_changed, senderName)
}
}
else -> {
// Main and removed, or main and added, or main and added and removed
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_canonical_alias_main_and_alternative_changed_by_you)
} else {
sp.getString(R.string.notice_room_canonical_alias_main_and_alternative_changed, senderName)
}
}
}
}
private fun formatRoomGuestAccessEvent(event: Event, senderName: String?, rs: RoomSummary?): String? {

View file

@ -77,6 +77,7 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R
override fun onDestroyView() {
recyclerView.cleanup()
roomListActionsEpoxyController.listener = null
super.onDestroyView()
}

View file

@ -20,7 +20,6 @@ import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import im.vector.app.R
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.discovery.settingsSectionTitleItem
import im.vector.app.features.form.formAdvancedToggleItem
@ -31,8 +30,9 @@ import im.vector.app.features.form.formSwitchItem
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import javax.inject.Inject
class CreateRoomController @Inject constructor(private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter
class CreateRoomController @Inject constructor(
private val stringProvider: StringProvider,
private val roomAliasErrorFormatter: RoomAliasErrorFormatter
) : TypedEpoxyController<CreateRoomViewState>() {
var listener: Listener? = null
@ -104,13 +104,8 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin
value(viewState.roomType.aliasLocalPart)
homeServer(":" + viewState.homeServerName)
errorMessage(
when ((viewState.asyncCreateRoomRequest as? Fail)?.error) {
is CreateRoomFailure.RoomAliasError.AliasEmpty -> R.string.create_room_alias_empty
is CreateRoomFailure.RoomAliasError.AliasNotAvailable -> R.string.create_room_alias_already_in_use
is CreateRoomFailure.RoomAliasError.AliasInvalid -> R.string.create_room_alias_invalid
else -> null
}
?.let { stringProvider.getString(it) }
roomAliasErrorFormatter.format(
(((viewState.asyncCreateRoomRequest as? Fail)?.error) as? CreateRoomFailure.AliasError)?.aliasError)
)
onTextChange { value ->
listener?.setAliasLocalPart(value)

View file

@ -84,7 +84,7 @@ class CreateRoomFragment @Inject constructor(
override fun showFailure(throwable: Throwable) {
// Note: RoomAliasError are displayed directly in the form
if (throwable !is CreateRoomFailure.RoomAliasError) {
if (throwable !is CreateRoomFailure.AliasError) {
super.showFailure(throwable)
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2020 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.createroom
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import javax.inject.Inject
class RoomAliasErrorFormatter @Inject constructor(
private val stringProvider: StringProvider
) {
fun format(roomAliasError: RoomAliasError?): String? {
return when (roomAliasError) {
is RoomAliasError.AliasEmpty -> R.string.create_room_alias_empty
is RoomAliasError.AliasNotAvailable -> R.string.create_room_alias_already_in_use
is RoomAliasError.AliasInvalid -> R.string.create_room_alias_invalid
else -> null
}
?.let { stringProvider.getString(it) }
}
}

View file

@ -36,6 +36,7 @@ import im.vector.app.features.room.RequireActiveMembershipViewState
import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment
import im.vector.app.features.roomprofile.members.RoomMemberListFragment
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
import im.vector.app.features.roomprofile.alias.RoomAliasFragment
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
import javax.inject.Inject
@ -98,10 +99,11 @@ class RoomProfileActivity :
.observe()
.subscribe { sharedAction ->
when (sharedAction) {
is RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers()
is RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings()
is RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads()
is RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers()
is RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers()
is RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings()
is RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias()
is RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads()
is RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers()
}
}
.disposeOnDestroy()
@ -135,6 +137,10 @@ class RoomProfileActivity :
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomSettingsFragment::class.java, roomProfileArgs)
}
private fun openRoomAlias() {
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomAliasFragment::class.java, roomProfileArgs)
}
private fun openRoomMembers() {
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomMemberListFragment::class.java, roomProfileArgs)
}

View file

@ -23,6 +23,7 @@ import im.vector.app.core.platform.VectorSharedAction
*/
sealed class RoomProfileSharedAction : VectorSharedAction {
object OpenRoomSettings : RoomProfileSharedAction()
object OpenRoomAliasesSettings : RoomProfileSharedAction()
object OpenRoomUploads : RoomProfileSharedAction()
object OpenRoomMembers : RoomProfileSharedAction()
object OpenBannedRoomMembers : RoomProfileSharedAction()

View file

@ -0,0 +1,39 @@
/*
* Copyright 2020 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.roomprofile.alias
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
sealed class RoomAliasAction : VectorViewModelAction {
// Canonical
object ToggleManualPublishForm : RoomAliasAction()
data class SetNewAlias(val alias: String) : RoomAliasAction()
object ManualPublishAlias : RoomAliasAction()
data class PublishAlias(val alias: String) : RoomAliasAction()
data class UnpublishAlias(val alias: String) : RoomAliasAction()
data class SetCanonicalAlias(val canonicalAlias: String?) : RoomAliasAction()
// Room directory
data class SetRoomDirectoryVisibility(val roomDirectoryVisibility: RoomDirectoryVisibility) : RoomAliasAction()
// Local
data class RemoveLocalAlias(val alias: String) : RoomAliasAction()
object ToggleAddLocalAliasForm : RoomAliasAction()
data class SetNewLocalAliasLocalPart(val aliasLocalPart: String) : RoomAliasAction()
object AddLocalAlias : RoomAliasAction()
}

View file

@ -0,0 +1,262 @@
/*
* Copyright 2020 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.roomprofile.alias
import android.text.InputType
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail
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.errorWithRetryItem
import im.vector.app.core.epoxy.loadingItem
import im.vector.app.core.epoxy.profiles.buildProfileSection
import im.vector.app.core.epoxy.profiles.profileActionItem
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.discovery.settingsButtonItem
import im.vector.app.features.discovery.settingsContinueCancelItem
import im.vector.app.features.discovery.settingsInfoItem
import im.vector.app.features.form.formEditTextItem
import im.vector.app.features.form.formSwitchItem
import im.vector.app.features.roomdirectory.createroom.RoomAliasErrorFormatter
import im.vector.app.features.roomdirectory.createroom.roomAliasEditItem
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import javax.inject.Inject
class RoomAliasController @Inject constructor(
private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter,
private val colorProvider: ColorProvider,
private val roomAliasErrorFormatter: RoomAliasErrorFormatter
) : TypedEpoxyController<RoomAliasViewState>() {
interface Callback {
fun toggleManualPublishForm()
fun setNewAlias(alias: String)
fun addAlias()
fun setRoomDirectoryVisibility(roomDirectoryVisibility: RoomDirectoryVisibility)
fun toggleLocalAliasForm()
fun setNewLocalAliasLocalPart(aliasLocalPart: String)
fun addLocalAlias()
fun openAliasDetail(alias: String)
}
var callback: Callback? = null
init {
setData(null)
}
override fun buildModels(data: RoomAliasViewState?) {
data ?: return
// Published alias
buildPublishInfo(data)
// Room directory visibility
buildRoomDirectoryVisibility(data)
// Local alias
buildLocalInfo(data)
}
private fun buildRoomDirectoryVisibility(data: RoomAliasViewState) {
when (data.roomDirectoryVisibility) {
Uninitialized -> Unit
is Loading -> Unit
is Success -> {
formSwitchItem {
id("roomVisibility")
title(stringProvider.getString(R.string.room_alias_publish_to_directory, data.homeServerName))
showDivider(false)
switchChecked(data.roomDirectoryVisibility() == RoomDirectoryVisibility.PUBLIC)
listener {
if (it) {
callback?.setRoomDirectoryVisibility(RoomDirectoryVisibility.PUBLIC)
} else {
callback?.setRoomDirectoryVisibility(RoomDirectoryVisibility.PRIVATE)
}
}
}
}
is Fail -> {
errorWithRetryItem {
text(stringProvider.getString(R.string.room_alias_publish_to_directory_error,
errorFormatter.toHumanReadable(data.roomDirectoryVisibility.error)))
}
}
}
}
private fun buildPublishInfo(data: RoomAliasViewState) {
buildProfileSection(
stringProvider.getString(R.string.room_alias_published_alias_title)
)
settingsInfoItem {
id("publishedInfo")
helperTextResId(R.string.room_alias_published_alias_subtitle)
}
data.canonicalAlias
?.takeIf { it.isNotEmpty() }
?.let { canonicalAlias ->
profileActionItem {
id("canonical")
title(data.canonicalAlias)
subtitle(stringProvider.getString(R.string.room_alias_published_alias_main))
listener { callback?.openAliasDetail(canonicalAlias) }
}
}
if (data.alternativeAliases.isEmpty()) {
settingsInfoItem {
id("otherPublishedEmpty")
if (data.actionPermissions.canChangeCanonicalAlias) {
helperTextResId(R.string.room_alias_address_empty_can_add)
} else {
helperTextResId(R.string.room_alias_address_empty)
}
}
} else {
settingsInfoItem {
id("otherPublished")
helperTextResId(R.string.room_alias_published_other)
}
data.alternativeAliases.forEachIndexed { idx, altAlias ->
profileActionItem {
id("alt_$idx")
title(altAlias)
listener { callback?.openAliasDetail(altAlias) }
}
}
}
if (data.actionPermissions.canChangeCanonicalAlias) {
buildPublishManuallyForm(data)
}
}
private fun buildPublishManuallyForm(data: RoomAliasViewState) {
when (data.publishManuallyState) {
RoomAliasViewState.AddAliasState.Hidden -> Unit
RoomAliasViewState.AddAliasState.Closed -> {
settingsButtonItem {
id("publishManually")
colorProvider(colorProvider)
buttonTitleId(R.string.room_alias_published_alias_add_manually)
buttonClickListener { callback?.toggleManualPublishForm() }
}
}
is RoomAliasViewState.AddAliasState.Editing -> {
formEditTextItem {
id("publishManuallyEdit")
value(data.publishManuallyState.value)
showBottomSeparator(false)
hint(stringProvider.getString(R.string.room_alias_address_hint))
inputType(InputType.TYPE_CLASS_TEXT)
onTextChange { text ->
callback?.setNewAlias(text)
}
}
settingsContinueCancelItem {
id("publishManuallySubmit")
continueText(stringProvider.getString(R.string.room_alias_published_alias_add_manually_submit))
continueOnClick { callback?.addAlias() }
cancelOnClick { callback?.toggleManualPublishForm() }
}
}
}
}
private fun buildLocalInfo(data: RoomAliasViewState) {
buildProfileSection(
stringProvider.getString(R.string.room_alias_local_address_title)
)
settingsInfoItem {
id("localInfo")
helperText(stringProvider.getString(R.string.room_alias_local_address_subtitle, data.homeServerName))
}
when (val localAliases = data.localAliases) {
is Uninitialized -> {
loadingItem {
id("loadingAliases")
}
}
is Success -> {
if (localAliases().isEmpty()) {
settingsInfoItem {
id("locEmpty")
helperTextResId(R.string.room_alias_local_address_empty)
}
} else {
localAliases().forEachIndexed { idx, localAlias ->
profileActionItem {
id("loc_$idx")
title(localAlias)
listener { callback?.openAliasDetail(localAlias) }
}
}
}
}
is Fail -> {
errorWithRetryItem {
id("alt_error")
text(errorFormatter.toHumanReadable(localAliases.error))
}
}
}
// Add local
buildAddLocalAlias(data)
}
private fun buildAddLocalAlias(data: RoomAliasViewState) {
when (data.newLocalAliasState) {
RoomAliasViewState.AddAliasState.Hidden -> Unit
RoomAliasViewState.AddAliasState.Closed -> {
settingsButtonItem {
id("newLocalAliasButton")
colorProvider(colorProvider)
buttonTitleId(R.string.room_alias_local_address_add)
buttonClickListener { callback?.toggleLocalAliasForm() }
}
}
is RoomAliasViewState.AddAliasState.Editing -> {
roomAliasEditItem {
id("newLocalAlias")
value(data.newLocalAliasState.value)
homeServer(":" + data.homeServerName)
showBottomSeparator(false)
errorMessage(roomAliasErrorFormatter.format((data.newLocalAliasState.asyncRequest as? Fail)?.error as? RoomAliasError))
onTextChange { value ->
callback?.setNewLocalAliasLocalPart(value)
}
}
settingsContinueCancelItem {
id("newLocalAliasSubmit")
continueText(stringProvider.getString(R.string.action_add))
continueOnClick { callback?.addLocalAlias() }
cancelOnClick { callback?.toggleLocalAliasForm() }
}
}
}
}
}

View file

@ -0,0 +1,193 @@
/*
* Copyright 2020 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.roomprofile.alias
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
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.configureWith
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.shareText
import im.vector.app.core.utils.toast
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roomprofile.RoomProfileArgs
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedAction
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel
import kotlinx.android.synthetic.main.fragment_room_setting_generic.*
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
class RoomAliasFragment @Inject constructor(
val viewModelFactory: RoomAliasViewModel.Factory,
private val controller: RoomAliasController,
private val avatarRenderer: AvatarRenderer
) :
VectorBaseFragment(),
RoomAliasController.Callback {
private val viewModel: RoomAliasViewModel by fragmentViewModel()
private lateinit var sharedActionViewModel: RoomAliasBottomSheetSharedActionViewModel
private val roomProfileArgs: RoomProfileArgs by args()
override fun getLayoutResId() = R.layout.fragment_room_setting_generic
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(RoomAliasBottomSheetSharedActionViewModel::class.java)
controller.callback = this
setupToolbar(roomSettingsToolbar)
roomSettingsRecyclerView.configureWith(controller, hasFixedSize = true)
waiting_view_status_text.setText(R.string.please_wait)
waiting_view_status_text.isVisible = true
viewModel.observeViewEvents {
when (it) {
is RoomAliasViewEvents.Failure -> showFailure(it.throwable)
RoomAliasViewEvents.Success -> showSuccess()
}.exhaustive
}
sharedActionViewModel
.observe()
.subscribe { handleAliasAction(it) }
.disposeOnDestroyView()
}
private fun handleAliasAction(action: RoomAliasBottomSheetSharedAction?) {
when (action) {
is RoomAliasBottomSheetSharedAction.ShareAlias -> shareAlias(action.matrixTo)
is RoomAliasBottomSheetSharedAction.PublishAlias -> viewModel.handle(RoomAliasAction.PublishAlias(action.alias))
is RoomAliasBottomSheetSharedAction.UnPublishAlias -> unpublishAlias(action.alias)
is RoomAliasBottomSheetSharedAction.DeleteAlias -> removeLocalAlias(action.alias)
is RoomAliasBottomSheetSharedAction.SetMainAlias -> viewModel.handle(RoomAliasAction.SetCanonicalAlias(action.alias))
RoomAliasBottomSheetSharedAction.UnsetMainAlias -> viewModel.handle(RoomAliasAction.SetCanonicalAlias(canonicalAlias = null))
null -> Unit
}
}
private fun shareAlias(matrixTo: String) {
shareText(requireContext(), matrixTo)
}
override fun showFailure(throwable: Throwable) {
if (throwable !is RoomAliasError) {
super.showFailure(throwable)
}
}
private fun showSuccess() {
activity?.toast(R.string.room_settings_save_success)
}
override fun onDestroyView() {
controller.callback = null
roomSettingsRecyclerView.cleanup()
super.onDestroyView()
}
override fun invalidate() = withState(viewModel) { state ->
waiting_view.isVisible = state.isLoading
controller.setData(state)
renderRoomSummary(state)
}
private fun renderRoomSummary(state: RoomAliasViewState) {
state.roomSummary()?.let {
roomSettingsToolbarTitleView.text = it.displayName
avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView)
}
}
private fun unpublishAlias(alias: String) {
AlertDialog.Builder(requireContext())
.setTitle(R.string.dialog_title_confirmation)
.setMessage(getString(R.string.room_alias_unpublish_confirmation, alias))
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.action_unpublish) { _, _ ->
viewModel.handle(RoomAliasAction.UnpublishAlias(alias))
}
.show()
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
}
override fun toggleManualPublishForm() {
viewModel.handle(RoomAliasAction.ToggleManualPublishForm)
}
override fun setNewAlias(alias: String) {
viewModel.handle(RoomAliasAction.SetNewAlias(alias))
}
override fun addAlias() {
viewModel.handle(RoomAliasAction.ManualPublishAlias)
}
override fun setRoomDirectoryVisibility(roomDirectoryVisibility: RoomDirectoryVisibility) {
viewModel.handle(RoomAliasAction.SetRoomDirectoryVisibility(roomDirectoryVisibility))
}
override fun toggleLocalAliasForm() {
viewModel.handle(RoomAliasAction.ToggleAddLocalAliasForm)
}
override fun setNewLocalAliasLocalPart(aliasLocalPart: String) {
viewModel.handle(RoomAliasAction.SetNewLocalAliasLocalPart(aliasLocalPart))
}
override fun addLocalAlias() {
viewModel.handle(RoomAliasAction.AddLocalAlias)
}
override fun openAliasDetail(alias: String) = withState(viewModel) { state ->
RoomAliasBottomSheet
.newInstance(
alias = alias,
isPublished = alias in state.allPublishedAliases,
isMainAlias = alias == state.canonicalAlias,
isLocal = alias in state.localAliases().orEmpty(),
canEditCanonicalAlias = state.actionPermissions.canChangeCanonicalAlias
)
.show(childFragmentManager, "ROOM_ALIAS_ACTIONS")
}
private fun removeLocalAlias(alias: String) {
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

@ -0,0 +1,28 @@
/*
* Copyright 2020 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.roomprofile.alias
import im.vector.app.core.platform.VectorViewEvents
/**
* Transient events for room settings screen
*/
sealed class RoomAliasViewEvents : VectorViewEvents {
data class Failure(val throwable: Throwable) : RoomAliasViewEvents()
object Success : RoomAliasViewEvents()
}

View file

@ -0,0 +1,384 @@
/*
* Copyright 2020 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.roomprofile.alias
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Fail
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 com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.rx.mapOptional
import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.rx.unwrap
class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: RoomAliasViewState,
private val session: Session)
: VectorViewModel<RoomAliasViewState, RoomAliasAction, RoomAliasViewEvents>(initialState) {
@AssistedInject.Factory
interface Factory {
fun create(initialState: RoomAliasViewState): RoomAliasViewModel
}
companion object : MvRxViewModelFactory<RoomAliasViewModel, RoomAliasViewState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomAliasViewState): RoomAliasViewModel? {
val fragment: RoomAliasFragment = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.viewModelFactory.create(state)
}
}
private val room = session.getRoom(initialState.roomId)!!
init {
initHomeServerName()
observeRoomSummary()
observePowerLevel()
observeRoomCanonicalAlias()
fetchRoomAlias()
fetchRoomDirectoryVisibility()
}
private fun fetchRoomDirectoryVisibility() {
setState {
copy(
roomDirectoryVisibility = Loading()
)
}
viewModelScope.launch {
runCatching {
session.getRoomDirectoryVisibility(room.roomId)
}.fold(
{
setState {
copy(
roomDirectoryVisibility = Success(it)
)
}
},
{
setState {
copy(
roomDirectoryVisibility = Fail(it)
)
}
}
)
}
}
private fun initHomeServerName() {
setState {
copy(
homeServerName = session.myUserId.substringAfter(":")
)
}
}
private fun fetchRoomAlias() {
setState {
copy(
localAliases = Loading()
)
}
viewModelScope.launch {
runCatching { room.getRoomAliases() }
.fold(
{
setState { copy(localAliases = Success(it.sorted())) }
},
{
setState { copy(localAliases = Fail(it)) }
}
)
}
}
private fun observeRoomSummary() {
room.rx().liveRoomSummary()
.unwrap()
.execute { async ->
copy(
roomSummary = async
)
}
}
private fun observePowerLevel() {
PowerLevelsObservableFactory(room)
.createObservable()
.subscribe {
val powerLevelsHelper = PowerLevelsHelper(it)
val permissions = RoomAliasViewState.ActionPermissions(
canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend(
userId = session.myUserId,
isState = true,
eventType = EventType.STATE_ROOM_CANONICAL_ALIAS
)
)
setState {
val newPublishManuallyState = if (permissions.canChangeCanonicalAlias) {
when (publishManuallyState) {
RoomAliasViewState.AddAliasState.Hidden -> RoomAliasViewState.AddAliasState.Closed
else -> publishManuallyState
}
} else {
RoomAliasViewState.AddAliasState.Hidden
}
copy(
actionPermissions = permissions,
publishManuallyState = newPublishManuallyState
)
}
}
.disposeOnClear()
}
/**
* We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar.
*/
private fun observeRoomCanonicalAlias() {
room.rx()
.liveStateEvent(EventType.STATE_ROOM_CANONICAL_ALIAS, QueryStringValue.NoCondition)
.mapOptional { it.content.toModel<RoomCanonicalAliasContent>() }
.unwrap()
.subscribe {
setState {
copy(
canonicalAlias = it.canonicalAlias,
alternativeAliases = it.alternativeAliases.orEmpty().sorted()
)
}
}
.disposeOnClear()
}
override fun handle(action: RoomAliasAction) {
when (action) {
RoomAliasAction.ToggleManualPublishForm -> handleToggleManualPublishForm()
is RoomAliasAction.SetNewAlias -> handleSetNewAlias(action)
is RoomAliasAction.ManualPublishAlias -> handleManualPublishAlias()
is RoomAliasAction.UnpublishAlias -> handleUnpublishAlias(action)
is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action)
is RoomAliasAction.SetRoomDirectoryVisibility -> handleSetRoomDirectoryVisibility(action)
RoomAliasAction.ToggleAddLocalAliasForm -> handleToggleAddLocalAliasForm()
is RoomAliasAction.SetNewLocalAliasLocalPart -> handleSetNewLocalAliasLocalPart(action)
RoomAliasAction.AddLocalAlias -> handleAddLocalAlias()
is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action)
is RoomAliasAction.PublishAlias -> handlePublishAlias(action)
}.exhaustive
}
private fun handleSetRoomDirectoryVisibility(action: RoomAliasAction.SetRoomDirectoryVisibility) {
postLoading(true)
viewModelScope.launch {
runCatching {
session.setRoomDirectoryVisibility(room.roomId, action.roomDirectoryVisibility)
}.fold(
{
setState {
copy(
isLoading = false,
// Local echo, no need to fetch the data from the server again
roomDirectoryVisibility = Success(action.roomDirectoryVisibility)
)
}
},
{
postLoading(false)
_viewEvents.post(RoomAliasViewEvents.Failure(it))
}
)
}
}
private fun handleToggleAddLocalAliasForm() {
setState {
copy(
newLocalAliasState = when (newLocalAliasState) {
RoomAliasViewState.AddAliasState.Hidden -> RoomAliasViewState.AddAliasState.Hidden
RoomAliasViewState.AddAliasState.Closed -> RoomAliasViewState.AddAliasState.Editing("", Uninitialized)
is RoomAliasViewState.AddAliasState.Editing -> RoomAliasViewState.AddAliasState.Closed
}
)
}
}
private fun handleToggleManualPublishForm() {
setState {
copy(
publishManuallyState = when (publishManuallyState) {
RoomAliasViewState.AddAliasState.Hidden -> RoomAliasViewState.AddAliasState.Hidden
RoomAliasViewState.AddAliasState.Closed -> RoomAliasViewState.AddAliasState.Editing("", Uninitialized)
is RoomAliasViewState.AddAliasState.Editing -> RoomAliasViewState.AddAliasState.Closed
}
)
}
}
private fun handleSetNewAlias(action: RoomAliasAction.SetNewAlias) {
setState {
copy(
publishManuallyState = RoomAliasViewState.AddAliasState.Editing(action.alias, Uninitialized)
)
}
}
private fun handleSetNewLocalAliasLocalPart(action: RoomAliasAction.SetNewLocalAliasLocalPart) {
setState {
copy(
newLocalAliasState = RoomAliasViewState.AddAliasState.Editing(action.aliasLocalPart, Uninitialized)
)
}
}
private fun handleManualPublishAlias() = withState { state ->
val newAlias = (state.publishManuallyState as? RoomAliasViewState.AddAliasState.Editing)?.value ?: return@withState
updateCanonicalAlias(
canonicalAlias = state.canonicalAlias,
alternativeAliases = state.alternativeAliases + newAlias,
closeForm = true
)
}
private fun handlePublishAlias(action: RoomAliasAction.PublishAlias) = withState { state ->
updateCanonicalAlias(
canonicalAlias = state.canonicalAlias,
alternativeAliases = state.alternativeAliases + action.alias,
closeForm = false
)
}
private fun handleUnpublishAlias(action: RoomAliasAction.UnpublishAlias) = withState { state ->
updateCanonicalAlias(
// We can also unpublish the canonical alias
canonicalAlias = state.canonicalAlias.takeIf { it != action.alias },
alternativeAliases = state.alternativeAliases - action.alias,
closeForm = false
)
}
private fun handleSetCanonicalAlias(action: RoomAliasAction.SetCanonicalAlias) = withState { state ->
updateCanonicalAlias(
canonicalAlias = action.canonicalAlias,
// Ensure the previous canonical alias is moved to the alt aliases
alternativeAliases = state.allPublishedAliases,
closeForm = false
)
}
private fun updateCanonicalAlias(canonicalAlias: String?, alternativeAliases: List<String>, closeForm: Boolean) {
postLoading(true)
room.updateCanonicalAlias(canonicalAlias, alternativeAliases, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
setState {
copy(
isLoading = false,
publishManuallyState = if (closeForm) RoomAliasViewState.AddAliasState.Closed else publishManuallyState
)
}
}
override fun onFailure(failure: Throwable) {
postLoading(false)
_viewEvents.post(RoomAliasViewEvents.Failure(failure))
}
})
}
private fun handleAddLocalAlias() = withState { state ->
val previousState = (state.newLocalAliasState as? RoomAliasViewState.AddAliasState.Editing) ?: return@withState
setState {
copy(
isLoading = true,
newLocalAliasState = previousState.copy(asyncRequest = Loading())
)
}
viewModelScope.launch {
runCatching { room.addAlias(previousState.value) }
.onFailure {
setState {
copy(
isLoading = false,
newLocalAliasState = previousState.copy(asyncRequest = Fail(it))
)
}
_viewEvents.post(RoomAliasViewEvents.Failure(it))
}
.onSuccess {
setState {
copy(
isLoading = false,
newLocalAliasState = RoomAliasViewState.AddAliasState.Closed,
// Local echo
localAliases = Success((localAliases().orEmpty() + previousState.value).sorted())
)
}
fetchRoomAlias()
}
}
}
private fun handleRemoveLocalAlias(action: RoomAliasAction.RemoveLocalAlias) {
postLoading(true)
viewModelScope.launch {
runCatching { session.deleteRoomAlias(action.alias) }
.onFailure {
setState {
copy(isLoading = false)
}
_viewEvents.post(RoomAliasViewEvents.Failure(it))
}
.onSuccess {
// Local echo
setState {
copy(
isLoading = false,
// Local echo
localAliases = Success(localAliases().orEmpty() - action.alias)
)
}
fetchRoomAlias()
}
}
}
private fun postLoading(isLoading: Boolean) {
setState {
copy(isLoading = isLoading)
}
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 2020 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.roomprofile.alias
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.app.features.roomprofile.RoomProfileArgs
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomSummary
data class RoomAliasViewState(
val roomId: String,
val homeServerName: String = "",
val roomSummary: Async<RoomSummary> = Uninitialized,
val actionPermissions: ActionPermissions = ActionPermissions(),
val roomDirectoryVisibility: Async<RoomDirectoryVisibility> = Uninitialized,
val isLoading: Boolean = false,
val canonicalAlias: String? = null,
val alternativeAliases: List<String> = emptyList(),
val publishManuallyState: AddAliasState = AddAliasState.Hidden,
val localAliases: Async<List<String>> = Uninitialized,
val newLocalAliasState: AddAliasState = AddAliasState.Closed
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
val allPublishedAliases: List<String>
get() = (alternativeAliases + listOfNotNull(canonicalAlias)).distinct()
data class ActionPermissions(
val canChangeCanonicalAlias: Boolean = false
)
sealed class AddAliasState {
object Hidden : AddAliasState()
object Closed : AddAliasState()
data class Editing(val value: String, val asyncRequest: Async<Unit> = Uninitialized) : AddAliasState()
}
}

View file

@ -0,0 +1,107 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.roomprofile.alias.detail
import android.os.Bundle
import android.os.Parcelable
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import kotlinx.android.parcel.Parcelize
import javax.inject.Inject
@Parcelize
data class RoomAliasBottomSheetArgs(
val alias: String,
val isPublished: Boolean,
val isMainAlias: Boolean,
val isLocal: Boolean,
val canEditCanonicalAlias: Boolean
) : Parcelable
/**
* Bottom sheet fragment that shows room alias information with list of contextual actions
*/
class RoomAliasBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomAliasBottomSheetController.Listener {
private lateinit var sharedActionViewModel: RoomAliasBottomSheetSharedActionViewModel
@Inject lateinit var sharedViewPool: RecyclerView.RecycledViewPool
@Inject lateinit var roomAliasBottomSheetViewModelFactory: RoomAliasBottomSheetViewModel.Factory
@Inject lateinit var controller: RoomAliasBottomSheetController
private val viewModel: RoomAliasBottomSheetViewModel by fragmentViewModel(RoomAliasBottomSheetViewModel::class)
@BindView(R.id.bottomSheetRecyclerView)
lateinit var recyclerView: RecyclerView
override val showExpanded = true
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(RoomAliasBottomSheetSharedActionViewModel::class.java)
recyclerView.configureWith(controller, viewPool = sharedViewPool, hasFixedSize = false, disableItemAnimation = true)
controller.listener = this
}
override fun onDestroyView() {
recyclerView.cleanup()
controller.listener = null
super.onDestroyView()
}
override fun invalidate() = withState(viewModel) {
controller.setData(it)
super.invalidate()
}
override fun didSelectMenuAction(quickAction: RoomAliasBottomSheetSharedAction) {
sharedActionViewModel.post(quickAction)
dismiss()
}
companion object {
fun newInstance(alias: String,
isPublished: Boolean,
isMainAlias: Boolean,
isLocal: Boolean,
canEditCanonicalAlias: Boolean): RoomAliasBottomSheet {
return RoomAliasBottomSheet().apply {
setArguments(RoomAliasBottomSheetArgs(
alias = alias,
isPublished = isPublished,
isMainAlias = isMainAlias,
isLocal = isLocal,
canEditCanonicalAlias = canEditCanonicalAlias
))
}
}
}
}

View file

@ -0,0 +1,88 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.roomprofile.alias.detail
import android.view.View
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.core.epoxy.bottomsheet.bottomSheetActionItem
import im.vector.app.core.epoxy.dividerItem
import im.vector.app.core.epoxy.profiles.profileActionItem
import javax.inject.Inject
/**
* Epoxy controller for room alias actions
*/
class RoomAliasBottomSheetController @Inject constructor() : TypedEpoxyController<RoomAliasBottomSheetState>() {
var listener: Listener? = null
override fun buildModels(state: RoomAliasBottomSheetState) {
profileActionItem {
id("alias")
title(state.alias)
subtitle(state.matrixToLink)
editable(false)
}
// Notifications
dividerItem {
id("aliasSeparator")
}
var idx = 0
// Share
state.matrixToLink?.let {
RoomAliasBottomSheetSharedAction.ShareAlias(it).toBottomSheetItem(++idx)
}
// Action on published alias
if (state.isPublished) {
// Published address
if (state.canEditCanonicalAlias) {
if (state.isMainAlias) {
RoomAliasBottomSheetSharedAction.UnsetMainAlias.toBottomSheetItem(++idx)
} else {
RoomAliasBottomSheetSharedAction.SetMainAlias(state.alias).toBottomSheetItem(++idx)
}
RoomAliasBottomSheetSharedAction.UnPublishAlias(state.alias).toBottomSheetItem(++idx)
}
}
if (state.isLocal) {
// Local address
if (state.canEditCanonicalAlias && state.isPublished.not()) {
// Publish
RoomAliasBottomSheetSharedAction.PublishAlias(state.alias).toBottomSheetItem(++idx)
}
// Delete
RoomAliasBottomSheetSharedAction.DeleteAlias(state.alias).toBottomSheetItem(++idx)
}
}
private fun RoomAliasBottomSheetSharedAction.toBottomSheetItem(index: Int) {
return bottomSheetActionItem {
id("action_$index")
iconRes(iconResId)
textRes(titleRes)
destructive(this@toBottomSheetItem.destructive)
listener(View.OnClickListener { listener?.didSelectMenuAction(this@toBottomSheetItem) })
}
}
interface Listener {
fun didSelectMenuAction(quickAction: RoomAliasBottomSheetSharedAction)
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.roomprofile.alias.detail
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import im.vector.app.R
import im.vector.app.core.platform.VectorSharedAction
sealed class RoomAliasBottomSheetSharedAction(
@StringRes val titleRes: Int,
@DrawableRes val iconResId: Int = 0,
val destructive: Boolean = false)
: VectorSharedAction {
data class ShareAlias(val matrixTo: String) : RoomAliasBottomSheetSharedAction(
R.string.share,
R.drawable.ic_material_share
)
data class PublishAlias(val alias: String) : RoomAliasBottomSheetSharedAction(
R.string.room_alias_action_publish
)
data class UnPublishAlias(val alias: String) : RoomAliasBottomSheetSharedAction(
R.string.room_alias_action_unpublish
)
data class DeleteAlias(val alias: String) : RoomAliasBottomSheetSharedAction(
R.string.delete,
R.drawable.ic_trash_24,
true
)
data class SetMainAlias(val alias: String) : RoomAliasBottomSheetSharedAction(
R.string.room_settings_set_main_address
)
object UnsetMainAlias : RoomAliasBottomSheetSharedAction(
R.string.room_settings_unset_main_address
)
}

View file

@ -0,0 +1,25 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.app.features.roomprofile.alias.detail
import im.vector.app.core.platform.VectorSharedActionViewModel
import javax.inject.Inject
/**
* Activity shared view model to handle room alias quick actions
*/
class RoomAliasBottomSheetSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<RoomAliasBottomSheetSharedAction>()

View file

@ -0,0 +1,37 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.roomprofile.alias.detail
import com.airbnb.mvrx.MvRxState
data class RoomAliasBottomSheetState(
val alias: String,
val matrixToLink: String? = null,
val isPublished: Boolean,
val isMainAlias: Boolean,
val isLocal: Boolean,
val canEditCanonicalAlias: Boolean
) : MvRxState {
constructor(args: RoomAliasBottomSheetArgs) : this(
alias = args.alias,
isPublished = args.isPublished,
isMainAlias = args.isMainAlias,
isLocal = args.isLocal,
canEditCanonicalAlias = args.canEditCanonicalAlias
)
}

View file

@ -0,0 +1,58 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.roomprofile.alias.detail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import org.matrix.android.sdk.api.session.Session
class RoomAliasBottomSheetViewModel @AssistedInject constructor(
@Assisted initialState: RoomAliasBottomSheetState,
session: Session
) : VectorViewModel<RoomAliasBottomSheetState, EmptyAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory
interface Factory {
fun create(initialState: RoomAliasBottomSheetState): RoomAliasBottomSheetViewModel
}
companion object : MvRxViewModelFactory<RoomAliasBottomSheetViewModel, RoomAliasBottomSheetState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomAliasBottomSheetState): RoomAliasBottomSheetViewModel? {
val fragment: RoomAliasBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.roomAliasBottomSheetViewModelFactory.create(state)
}
}
init {
setState {
copy(
matrixToLink = session.permalinkService().createPermalink(alias)
)
}
}
override fun handle(action: EmptyAction) {
// No op
}
}

View file

@ -19,8 +19,8 @@ package im.vector.app.features.roomprofile.banned
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
sealed class RoomBannedListMemberAction : VectorViewModelAction {
data class QueryInfo(val roomMemberSummary: RoomMemberSummary) : RoomBannedListMemberAction()
data class UnBanUser(val roomMemberSummary: RoomMemberSummary) : RoomBannedListMemberAction()
data class Filter(val filter: String) : RoomBannedListMemberAction()
sealed class RoomBannedMemberListAction : VectorViewModelAction {
data class QueryInfo(val roomMemberSummary: RoomMemberSummary) : RoomBannedMemberListAction()
data class UnBanUser(val roomMemberSummary: RoomMemberSummary) : RoomBannedMemberListAction()
data class Filter(val filter: String) : RoomBannedMemberListAction()
}

View file

@ -37,18 +37,18 @@ import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
class RoomBannedMemberListFragment @Inject constructor(
val viewModelFactory: RoomBannedListMemberViewModel.Factory,
val viewModelFactory: RoomBannedMemberListViewModel.Factory,
private val roomMemberListController: RoomBannedMemberListController,
private val avatarRenderer: AvatarRenderer
) : VectorBaseFragment(), RoomBannedMemberListController.Callback {
private val viewModel: RoomBannedListMemberViewModel by fragmentViewModel()
private val viewModel: RoomBannedMemberListViewModel by fragmentViewModel()
private val roomProfileArgs: RoomProfileArgs by args()
override fun getLayoutResId() = R.layout.fragment_room_setting_generic
override fun onUnbanClicked(roomMember: RoomMemberSummary) {
viewModel.handle(RoomBannedListMemberAction.QueryInfo(roomMember))
viewModel.handle(RoomBannedMemberListAction.QueryInfo(roomMember))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -60,7 +60,7 @@ class RoomBannedMemberListFragment @Inject constructor(
viewModel.observeViewEvents {
when (it) {
is RoomBannedViewEvents.ShowBannedInfo -> {
is RoomBannedMemberListViewEvents.ShowBannedInfo -> {
val canBan = withState(viewModel) { state -> state.canUserBan }
AlertDialog.Builder(requireActivity())
.setTitle(getString(R.string.member_banned_by, it.bannedByUserId))
@ -69,13 +69,13 @@ class RoomBannedMemberListFragment @Inject constructor(
.apply {
if (canBan) {
setNegativeButton(R.string.room_participants_action_unban) { _, _ ->
viewModel.handle(RoomBannedListMemberAction.UnBanUser(it.roomMemberSummary))
viewModel.handle(RoomBannedMemberListAction.UnBanUser(it.roomMemberSummary))
}
}
}
.show()
}
is RoomBannedViewEvents.ToastError -> {
is RoomBannedMemberListViewEvents.ToastError -> {
requireActivity().toast(it.info)
}
}
@ -96,7 +96,7 @@ class RoomBannedMemberListFragment @Inject constructor(
}
override fun onQueryTextChange(newText: String): Boolean {
viewModel.handle(RoomBannedListMemberAction.Filter(newText))
viewModel.handle(RoomBannedMemberListAction.Filter(newText))
return true
}
})

View file

@ -19,7 +19,7 @@ package im.vector.app.features.roomprofile.banned
import im.vector.app.core.platform.VectorViewEvents
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
sealed class RoomBannedViewEvents : VectorViewEvents {
data class ShowBannedInfo(val bannedByUserId: String, val banReason: String, val roomMemberSummary: RoomMemberSummary) : RoomBannedViewEvents()
data class ToastError(val info: String) : RoomBannedViewEvents()
sealed class RoomBannedMemberListViewEvents : VectorViewEvents {
data class ShowBannedInfo(val bannedByUserId: String, val banReason: String, val roomMemberSummary: RoomMemberSummary) : RoomBannedMemberListViewEvents()
data class ToastError(val info: String) : RoomBannedMemberListViewEvents()
}

View file

@ -42,14 +42,14 @@ import org.matrix.android.sdk.internal.util.awaitCallback
import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.rx.unwrap
class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initialState: RoomBannedMemberListViewState,
class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomBannedMemberListViewState,
private val stringProvider: StringProvider,
private val session: Session)
: VectorViewModel<RoomBannedMemberListViewState, RoomBannedListMemberAction, RoomBannedViewEvents>(initialState) {
: VectorViewModel<RoomBannedMemberListViewState, RoomBannedMemberListAction, RoomBannedMemberListViewEvents>(initialState) {
@AssistedInject.Factory
interface Factory {
fun create(initialState: RoomBannedMemberListViewState): RoomBannedListMemberViewModel
fun create(initialState: RoomBannedMemberListViewState): RoomBannedMemberListViewModel
}
private val room = session.getRoom(initialState.roomId)!!
@ -78,24 +78,24 @@ class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initia
}.disposeOnClear()
}
companion object : MvRxViewModelFactory<RoomBannedListMemberViewModel, RoomBannedMemberListViewState> {
companion object : MvRxViewModelFactory<RoomBannedMemberListViewModel, RoomBannedMemberListViewState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomBannedMemberListViewState): RoomBannedListMemberViewModel? {
override fun create(viewModelContext: ViewModelContext, state: RoomBannedMemberListViewState): RoomBannedMemberListViewModel? {
val fragment: RoomBannedMemberListFragment = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.viewModelFactory.create(state)
}
}
override fun handle(action: RoomBannedListMemberAction) {
override fun handle(action: RoomBannedMemberListAction) {
when (action) {
is RoomBannedListMemberAction.QueryInfo -> onQueryBanInfo(action.roomMemberSummary)
is RoomBannedListMemberAction.UnBanUser -> unBanUser(action.roomMemberSummary)
is RoomBannedListMemberAction.Filter -> handleFilter(action)
is RoomBannedMemberListAction.QueryInfo -> onQueryBanInfo(action.roomMemberSummary)
is RoomBannedMemberListAction.UnBanUser -> unBanUser(action.roomMemberSummary)
is RoomBannedMemberListAction.Filter -> handleFilter(action)
}.exhaustive
}
private fun handleFilter(action: RoomBannedListMemberAction.Filter) {
private fun handleFilter(action: RoomBannedMemberListAction.Filter) {
setState {
copy(
filter = action.filter
@ -114,7 +114,7 @@ class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initia
val reason = content.reason
val bannedBy = bannedEvent?.senderId ?: return
_viewEvents.post(RoomBannedViewEvents.ShowBannedInfo(bannedBy, reason ?: "", roomMemberSummary))
_viewEvents.post(RoomBannedMemberListViewEvents.ShowBannedInfo(bannedBy, reason ?: "", roomMemberSummary))
}
private fun unBanUser(roomMemberSummary: RoomMemberSummary) {
@ -127,7 +127,7 @@ class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initia
room.unban(roomMemberSummary.userId, null, it)
}
} catch (failure: Throwable) {
_viewEvents.post(RoomBannedViewEvents.ToastError(stringProvider.getString(R.string.failed_to_unban)))
_viewEvents.post(RoomBannedMemberListViewEvents.ToastError(stringProvider.getString(R.string.failed_to_unban)))
} finally {
setState {
copy(

View file

@ -24,7 +24,6 @@ sealed class RoomSettingsAction : VectorViewModelAction {
data class SetRoomName(val newName: String) : RoomSettingsAction()
data class SetRoomTopic(val newTopic: String) : RoomSettingsAction()
data class SetRoomHistoryVisibility(val visibility: RoomHistoryVisibility) : RoomSettingsAction()
data class SetRoomCanonicalAlias(val newCanonicalAlias: String) : RoomSettingsAction()
object Save : RoomSettingsAction()
object Cancel : RoomSettingsAction()
}

View file

@ -46,7 +46,7 @@ class RoomSettingsController @Inject constructor(
fun onNameChanged(name: String)
fun onTopicChanged(topic: String)
fun onHistoryVisibilityClicked()
fun onAliasChanged(alias: String)
fun onRoomAliasesClicked()
}
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
@ -67,13 +67,13 @@ class RoomSettingsController @Inject constructor(
id("avatar")
enabled(data.actionPermissions.canChangeAvatar)
when (val avatarAction = data.avatarAction) {
RoomSettingsViewState.AvatarAction.None -> {
RoomSettingsViewState.AvatarAction.None -> {
// Use the current value
avatarRenderer(avatarRenderer)
// We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar.
matrixItem(roomSummary.toMatrixItem().copy(avatarUrl = data.currentRoomAvatarUrl))
}
RoomSettingsViewState.AvatarAction.DeleteAvatar ->
RoomSettingsViewState.AvatarAction.DeleteAvatar ->
imageUri(null)
is RoomSettingsViewState.AvatarAction.UpdateAvatar ->
imageUri(avatarAction.newAvatarUri)
@ -108,16 +108,15 @@ class RoomSettingsController @Inject constructor(
}
}
formEditTextItem {
id("alias")
enabled(data.actionPermissions.canChangeCanonicalAlias)
value(data.newCanonicalAlias ?: roomSummary.canonicalAlias)
hint(stringProvider.getString(R.string.room_settings_addresses_add_new_address))
onTextChange { text ->
callback?.onAliasChanged(text)
}
}
buildProfileAction(
id = "alias",
title = stringProvider.getString(R.string.room_settings_alias_title),
subtitle = stringProvider.getString(R.string.room_settings_alias_subtitle),
dividerColor = dividerColor,
divider = true,
editable = true,
action = { callback?.onRoomAliasesClicked() }
)
buildProfileAction(
id = "historyReadability",

View file

@ -39,6 +39,8 @@ import im.vector.app.core.utils.toast
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter
import im.vector.app.features.roomprofile.RoomProfileArgs
import im.vector.app.features.roomprofile.RoomProfileSharedAction
import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel
import kotlinx.android.synthetic.main.fragment_room_setting_generic.*
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
import org.matrix.android.sdk.api.session.events.model.toModel
@ -61,6 +63,7 @@ class RoomSettingsFragment @Inject constructor(
GalleryOrCameraDialogHelper.Listener {
private val viewModel: RoomSettingsViewModel by fragmentViewModel()
private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel
private val roomProfileArgs: RoomProfileArgs by args()
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider)
@ -70,6 +73,7 @@ class RoomSettingsFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::class.java)
controller.callback = this
setupToolbar(roomSettingsToolbar)
roomSettingsRecyclerView.configureWith(controller, hasFixedSize = true)
@ -164,8 +168,8 @@ class RoomSettingsFragment @Inject constructor(
return@withState
}
override fun onAliasChanged(alias: String) {
viewModel.handle(RoomSettingsAction.SetRoomCanonicalAlias(alias))
override fun onRoomAliasesClicked() {
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomAliasesSettings)
}
override fun onImageReady(uri: Uri?) {

View file

@ -68,12 +68,10 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
selectSubscribe(
RoomSettingsViewState::avatarAction,
RoomSettingsViewState::newName,
RoomSettingsViewState::newCanonicalAlias,
RoomSettingsViewState::newTopic,
RoomSettingsViewState::newHistoryVisibility,
RoomSettingsViewState::roomSummary) { avatarAction,
newName,
newCanonicalAlias,
newTopic,
newHistoryVisibility,
asyncSummary ->
@ -83,7 +81,6 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
showSaveAction = avatarAction !is RoomSettingsViewState.AvatarAction.None
|| summary?.name != newName
|| summary?.topic != newTopic
|| summary?.canonicalAlias != newCanonicalAlias?.takeIf { it.isNotEmpty() }
|| newHistoryVisibility != null
)
}
@ -99,8 +96,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
historyVisibilityEvent = room.getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY),
roomSummary = async,
newName = roomSummary?.name,
newTopic = roomSummary?.topic,
newCanonicalAlias = roomSummary?.canonicalAlias
newTopic = roomSummary?.topic
)
}
@ -113,8 +109,6 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
canChangeAvatar = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR),
canChangeName = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME),
canChangeTopic = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC),
canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true,
EventType.STATE_ROOM_CANONICAL_ALIAS),
canChangeHistoryReadability = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true,
EventType.STATE_ROOM_HISTORY_VISIBILITY)
)
@ -143,7 +137,6 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
is RoomSettingsAction.SetRoomName -> setState { copy(newName = action.newName) }
is RoomSettingsAction.SetRoomTopic -> setState { copy(newTopic = action.newTopic) }
is RoomSettingsAction.SetRoomHistoryVisibility -> setState { copy(newHistoryVisibility = action.visibility) }
is RoomSettingsAction.SetRoomCanonicalAlias -> setState { copy(newCanonicalAlias = action.newCanonicalAlias) }
is RoomSettingsAction.Save -> saveSettings()
is RoomSettingsAction.Cancel -> cancel()
}.exhaustive
@ -191,11 +184,6 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
operationList.add(room.rx().updateTopic(state.newTopic ?: ""))
}
if (state.newCanonicalAlias != null && summary?.canonicalAlias != state.newCanonicalAlias.takeIf { it.isNotEmpty() }) {
operationList.add(room.rx().addRoomAlias(state.newCanonicalAlias))
operationList.add(room.rx().updateCanonicalAlias(state.newCanonicalAlias))
}
if (state.newHistoryVisibility != null) {
operationList.add(room.rx().updateHistoryReadability(state.newHistoryVisibility))
}

View file

@ -35,7 +35,6 @@ data class RoomSettingsViewState(
val newName: String? = null,
val newTopic: String? = null,
val newHistoryVisibility: RoomHistoryVisibility? = null,
val newCanonicalAlias: String? = null,
val showSaveAction: Boolean = false,
val actionPermissions: ActionPermissions = ActionPermissions()
) : MvRxState {
@ -46,7 +45,6 @@ data class RoomSettingsViewState(
val canChangeAvatar: Boolean = false,
val canChangeName: Boolean = false,
val canChangeTopic: Boolean = false,
val canChangeCanonicalAlias: Boolean = false,
val canChangeHistoryReadability: Boolean = false
)

View file

@ -1,41 +1,47 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M2.25,5.5H5.1667H21.75"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"/>
<path
android:pathData="M16.5,5.5L15,1H9L7.5,5.5"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"/>
<path
android:pathData="M5.25,9.25V20.75C5.25,21.8546 6.1454,22.75 7.25,22.75H16.75C17.8546,22.75 18.75,21.8546 18.75,20.75V9.25"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"/>
<path
android:pathData="M9.75,9.25V18.25"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"/>
<path
android:pathData="M14.25,9.25V18.25"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"/>
<path
android:fillColor="#00000000"
android:pathData="M2.25,5.5H5.1667H21.75"
android:strokeWidth="2"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"
android:strokeLineJoin="round"
tools:strokeColor="@color/riotx_destructive_accent" />
<path
android:fillColor="#00000000"
android:pathData="M16.5,5.5L15,1H9L7.5,5.5"
android:strokeWidth="2"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"
android:strokeLineJoin="round"
tools:strokeColor="@color/riotx_destructive_accent" />
<path
android:fillColor="#00000000"
android:pathData="M5.25,9.25V20.75C5.25,21.8546 6.1454,22.75 7.25,22.75H16.75C17.8546,22.75 18.75,21.8546 18.75,20.75V9.25"
android:strokeWidth="2"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"
android:strokeLineJoin="round"
tools:strokeColor="@color/riotx_destructive_accent" />
<path
android:fillColor="#00000000"
android:pathData="M9.75,9.25V18.25"
android:strokeWidth="2"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"
android:strokeLineJoin="round"
tools:strokeColor="@color/riotx_destructive_accent" />
<path
android:fillColor="#00000000"
android:pathData="M14.25,9.25V18.25"
android:strokeWidth="2"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"
android:strokeLineJoin="round"
tools:strokeColor="@color/riotx_destructive_accent" />
</vector>

View file

@ -20,10 +20,12 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!-- android:imeOptions="actionDone" to fix a crash -->
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/formTextInputTextInputEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
tools:hint="@string/create_room_name_hint" />
</com.google.android.material.textfield.TextInputLayout>

View file

@ -30,11 +30,13 @@
app:layout_constraintStart_toEndOf="@+id/itemRoomAliasHash"
app:layout_constraintTop_toTopOf="parent">
<!-- android:imeOptions="actionDone" to fix a crash -->
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/itemRoomAliasTextInputEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/create_room_alias_hint"
android:imeOptions="actionDone"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>

View file

@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?riotx_background"
android:minHeight="64dp"
android:paddingStart="@dimen/layout_horizontal_margin"
android:paddingEnd="@dimen/layout_horizontal_margin">
@ -12,19 +13,20 @@
android:id="@+id/item_settings_three_pid_icon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginEnd="8dp"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/item_settings_three_pid_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_phone"
app:tint="?riotx_text_secondary"
tools:ignore="MissingPrefix" />
tools:ignore="MissingPrefix"
tools:src="@drawable/ic_phone" />
<TextView
android:id="@+id/item_settings_three_pid_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="4dp"
android:textColor="?riotx_text_primary"
android:textSize="16sp"

View file

@ -137,6 +137,8 @@
<string name="action_open">Open</string>
<string name="action_close">Close</string>
<string name="action_copy">Copy</string>
<string name="action_add">Add</string>
<string name="action_unpublish">Unpublish</string>
<string name="copied_to_clipboard">Copied to clipboard</string>
<string name="disable">Disable</string>
@ -1022,6 +1024,40 @@
<string name="room_settings_room_read_history_rules_pref_dialog_title">Who can read history?</string>
<string name="room_settings_room_access_rules_pref_dialog_title">Who can access this room?</string>
<!-- room settings : alias -->
<string name="room_settings_alias_title">Room addresses</string>
<string name="room_settings_alias_subtitle">See and managed addresses of this room, and its visibility in the room directory.</string>
<string name="room_alias_title">Room Addresses</string>
<string name="room_alias_published_alias_title">Published Addresses</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_published_alias_main">This is the 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_alias_add_manually">Publish a new address manually</string>
<string name="room_alias_published_alias_add_manually_submit">Publish</string>
<string name="room_alias_unpublish_confirmation">Unpublish the address \"%1$s\"?</string>
<string name="room_alias_delete_confirmation">Delete the address \"%1$s\"?</string>
<!-- 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_address_empty_can_add">No other published addresses yet, add one below.</string>
<string name="room_alias_address_empty">No other published addresses yet.</string>
<string name="room_alias_address_hint">New published address (e.g. #alias:server)</string>
<string name="room_alias_local_address_title">Local Addresses</string>
<!-- Parameter will be the url of the homeserver, ex: matrix.org -->
<string name="room_alias_local_address_subtitle">Set addresses for this room so users can find this room through your homeserver (%1$s)</string>
<string name="room_alias_local_address_empty">This room has no local addresses</string>
<string name="room_alias_local_address_add">Add a local address</string>
<string name="room_alias_action_publish">Publish this address</string>
<string name="room_alias_action_unpublish">Unpublish this address</string>
<!-- Parameter will be the url of the homeserver, ex: matrix.org -->
<string name="room_alias_publish_to_directory">Publish this room to the public in %1$s\'s room directory?</string>
<!-- Parameter will be a technical error message -->
<string name="room_alias_publish_to_directory_error">Unable to retrieve the current room directory visibility (%1$s).</string>
<!-- Room settings, access and visibility : WHO CAN READ HISTORY? (read rule) -->
<string name="room_settings_read_history_entry_anyone">Anyone</string>
<string name="room_settings_read_history_entry_members_only_option_time_shared">Members only (since the point in time of selecting this option)</string>
@ -1068,8 +1104,8 @@
<string name="room_settings_addresses_disable_main_address_prompt_msg">You will have no main address specified for this room."</string>
<string name="room_settings_addresses_disable_main_address_prompt_title">Main address warnings</string>
<string name="room_settings_set_main_address">Set as Main Address</string>
<string name="room_settings_unset_main_address">Unset as Main Address</string>
<string name="room_settings_set_main_address">Set as main address</string>
<string name="room_settings_unset_main_address">Unset as main address</string>
<string name="room_settings_copy_room_id">Copy Room ID</string>
<string name="room_settings_copy_room_address">Copy Room Address</string>