Create a dedicated screen to manage room alias (#2428) - WIP

This commit is contained in:
Benoit Marty 2020-11-23 13:05:23 +01:00 committed by Benoit Marty
parent 0d93105bcd
commit a6f56ace24
25 changed files with 718 additions and 39 deletions

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

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

@ -0,0 +1,24 @@
/*
* 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
*/
suspend fun getRoomAliases(): List<String>
}

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

@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtoc
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.GetAliasesResponse
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
import org.matrix.android.sdk.internal.session.room.create.CreateRoomBody
import org.matrix.android.sdk.internal.session.room.create.CreateRoomResponse
@ -332,10 +333,17 @@ internal interface RoomAPI {
* Add alias to the room.
* @param roomAlias the room alias.
*/
// TODO Remove (https://github.com/matrix-org/matrix-doc/blob/rav/proposal/alt_canonical_aliases/proposals/2432-revised-alias-publishing.md)
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
fun addRoomAlias(@Path("roomAlias") roomAlias: String,
@Body body: AddRoomAliasBody): Call<Unit>
/**
* Get local aliases of this room
*/
@GET(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

@ -29,7 +29,9 @@ import org.matrix.android.sdk.internal.session.SessionScope
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.DefaultGetRoomIdByAliasTask
import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomLocalAliasesTask
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
@ -181,6 +183,9 @@ 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

View file

@ -0,0 +1,36 @@
/*
* 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
) : AliasService {
@AssistedInject.Factory
interface Factory {
fun create(roomId: String): AliasService
}
override suspend fun getRoomAliases(): List<String> {
return getRoomLocalAliasesTask.execute(GetRoomLocalAliasesTask.Params(roomId))
}
}

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(
/**
* The list of aliases currently defined on the local server for the given room
*/
@Json(name = "aliases") val aliases: List<String> = emptyList()
)

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

@ -83,6 +83,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
@ -363,6 +364,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

@ -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
@ -100,6 +101,7 @@ class RoomProfileActivity :
when (sharedAction) {
is RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers()
is RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings()
is RoomProfileSharedAction.OpenRoomAlias -> openRoomAlias()
is RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads()
is RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers()
}
@ -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 OpenRoomAlias : RoomProfileSharedAction()
object OpenRoomUploads : RoomProfileSharedAction()
object OpenRoomMembers : RoomProfileSharedAction()
object OpenBannedRoomMembers : RoomProfileSharedAction()

View file

@ -0,0 +1,31 @@
/*
* 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
sealed class RoomAliasAction : VectorViewModelAction {
// Canonical
data class AddAlias(val alias: String) : RoomAliasAction()
data class RemoveAlias(val alias: String) : RoomAliasAction()
data class SetCanonicalAlias(val canonicalAlias: String) : RoomAliasAction()
object UnSetCanonicalAlias : RoomAliasAction()
// Local
data class AddLocalAlias(val aliasLocalPart: String) : RoomAliasAction()
data class RemoveLocalAlias(val alias: String) : RoomAliasAction()
}

View file

@ -0,0 +1,120 @@
/*
* 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.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail
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.error.ErrorFormatter
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.discovery.settingsInfoItem
import im.vector.app.features.settings.threepids.threePidItem
import javax.inject.Inject
class RoomAliasController @Inject constructor(
private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter,
colorProvider: ColorProvider
) : TypedEpoxyController<RoomAliasViewState>() {
interface Callback {
fun removeAlias(altAlias: String)
fun setCanonicalAlias(alias: String)
fun unsetCanonicalAlias()
fun addLocalAlias(alias: String)
fun removeLocalAlias(alias: String)
}
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
var callback: Callback? = null
init {
setData(null)
}
override fun buildModels(data: RoomAliasViewState?) {
data ?: return
buildProfileSection(
stringProvider.getString(R.string.room_alias_published_alias_title)
)
settingsInfoItem {
id("publishedInfo")
helperTextResId(R.string.room_alias_published_alias_subtitle)
}
// TODO Canonical
if (data.alternativeAliases.isNotEmpty()) {
settingsInfoItem {
id("otherPublished")
helperTextResId(R.string.room_alias_published_other)
}
data.alternativeAliases.forEachIndexed { idx, altAlias ->
// TODO Rename this item to a more generic name
threePidItem {
id("alt_$idx")
title(altAlias)
deleteClickListener { callback?.removeAlias(altAlias) }
}
}
}
// Local
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))
}
buildLocalInfo(data)
}
private fun buildLocalInfo(data: RoomAliasViewState) {
when (val localAliases = data.localAliases) {
is Uninitialized -> {
loadingItem {
id("loadingAliases")
}
}
is Success -> {
localAliases().forEachIndexed { idx, localAlias ->
// TODO Rename this item to a more generic name
threePidItem {
id("loc_$idx")
title(localAlias)
deleteClickListener { callback?.removeLocalAlias(localAlias) }
}
}
}
is Fail -> {
errorWithRetryItem {
id("alt_error")
text(errorFormatter.toHumanReadable(localAliases.error))
}
}
}
}
}

View file

@ -0,0 +1,112 @@
/*
* 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.os.Bundle
import android.view.View
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.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.toast
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roomprofile.RoomProfileArgs
import kotlinx.android.synthetic.main.fragment_room_setting_generic.*
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
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 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)
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
}
}
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) { viewState ->
controller.setData(viewState)
renderRoomSummary(viewState)
}
private fun renderRoomSummary(state: RoomAliasViewState) {
waiting_view.isVisible = state.isLoading
state.roomSummary()?.let {
roomSettingsToolbarTitleView.text = it.displayName
avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView)
}
invalidateOptionsMenu()
}
override fun removeAlias(altAlias: String) {
viewModel.handle(RoomAliasAction.RemoveAlias(altAlias))
}
override fun setCanonicalAlias(alias: String) {
viewModel.handle(RoomAliasAction.SetCanonicalAlias(alias))
}
override fun unsetCanonicalAlias() {
viewModel.handle(RoomAliasAction.UnSetCanonicalAlias)
}
override fun addLocalAlias(alias: String) {
viewModel.handle(RoomAliasAction.AddLocalAlias(alias))
}
override fun removeLocalAlias(alias: String) {
viewModel.handle(RoomAliasAction.RemoveLocalAlias(alias))
}
}

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,182 @@
/*
* 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.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.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()
observeMowerLevel()
observeRoomCanonicalAlias()
getRoomAlias()
}
private fun initHomeServerName() {
setState {
copy(
homeServerName = session.myUserId.substringAfter(":")
)
}
}
private fun getRoomAlias() {
setState {
copy(
localAliases = Loading()
)
}
viewModelScope.launch {
runCatching { room.getRoomAliases() }
.fold(
{
setState { copy(localAliases = Success(it)) }
},
{
setState { copy(localAliases = Fail(it)) }
}
)
}
}
private fun observeRoomSummary() {
room.rx().liveRoomSummary()
.unwrap()
.execute { async ->
copy(
roomSummary = async
)
}
}
private fun observeMowerLevel() {
PowerLevelsObservableFactory(room)
.createObservable()
.subscribe {
val powerLevelsHelper = PowerLevelsHelper(it)
val permissions = RoomAliasViewState.ActionPermissions(
canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true,
EventType.STATE_ROOM_CANONICAL_ALIAS),
)
setState { copy(actionPermissions = permissions) }
}
.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()
)
}
}
.disposeOnClear()
}
override fun handle(action: RoomAliasAction) {
when (action) {
is RoomAliasAction.AddAlias -> handleAddAlias(action)
is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action)
is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action)
RoomAliasAction.UnSetCanonicalAlias -> handleUnsetCanonicalAlias()
is RoomAliasAction.AddLocalAlias -> handleAddLocalAlias(action)
is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action)
}.exhaustive
}
private fun handleAddAlias(action: RoomAliasAction.AddAlias) {
TODO("Not yet implemented")
}
private fun handleRemoveAlias(action: RoomAliasAction.RemoveAlias) {
TODO("Not yet implemented")
}
private fun handleSetCanonicalAlias(action: RoomAliasAction.SetCanonicalAlias) {
//room.updateCanonicalAlias()
TODO("Not yet implemented")
}
private fun handleUnsetCanonicalAlias() {
TODO("Not yet implemented")
}
private fun handleAddLocalAlias(action: RoomAliasAction.AddLocalAlias) {
TODO("Not yet implemented")
}
private fun handleRemoveLocalAlias(action: RoomAliasAction.RemoveLocalAlias) {
TODO("Not yet implemented")
}
private fun postLoading(isLoading: Boolean) {
setState {
copy(isLoading = isLoading)
}
}
}

View file

@ -0,0 +1,41 @@
/*
* 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.RoomSummary
data class RoomAliasViewState(
val roomId: String,
val homeServerName: String = "",
val roomSummary: Async<RoomSummary> = Uninitialized,
val isLoading: Boolean = false,
val canonicalAlias: String? = null,
val alternativeAliases: List<String> = emptyList(),
val localAliases: Async<List<String>> = Uninitialized,
val actionPermissions: ActionPermissions = ActionPermissions()
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
data class ActionPermissions(
val canChangeCanonicalAlias: Boolean = false
)
}

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 onOpenAlias()
}
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?.onOpenAlias() }
)
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 onOpenAlias() {
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomAlias)
}
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

@ -1022,6 +1022,25 @@
<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</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_main_address_hint">Main address</string>
<string name="room_alias_published_other">Other published addresses:</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">No other published addresses yet, add one below</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>
<!-- 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>