This commit is contained in:
Valere 2021-07-09 14:24:13 +02:00
parent 6c2a917d9f
commit 3da20aea29
9 changed files with 114 additions and 272 deletions

View File

@ -27,6 +27,7 @@ import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.roomprofile.RoomProfileArgs
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedState
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
import javax.inject.Inject
class RoomJoinRuleActivity : VectorBaseActivity<ActivitySimpleBinding>(),
RoomJoinRuleChooseRestrictedViewModel.Factory {
@ -35,13 +36,15 @@ class RoomJoinRuleActivity : VectorBaseActivity<ActivitySimpleBinding>(),
private lateinit var roomProfileArgs: RoomProfileArgs
private lateinit var allowListViewModelFactory: RoomJoinRuleChooseRestrictedViewModel.Factory
@Inject
lateinit var allowListViewModelFactory: RoomJoinRuleChooseRestrictedViewModel.Factory
override fun create(initialState: RoomJoinRuleChooseRestrictedState) = allowListViewModelFactory.create(initialState)
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
injector.inject(this)
}
override fun initUiAndData() {
roomProfileArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return
if (isFirstCreation()) {
@ -52,10 +55,6 @@ class RoomJoinRuleActivity : VectorBaseActivity<ActivitySimpleBinding>(),
)
}
}
//
// override fun onBackPressed() {
// super.onBackPressed()
// }
companion object {

View File

@ -1,191 +0,0 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.roomprofile.settings.joinrule
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.platform.VectorViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.roomprofile.RoomProfileArgs
import im.vector.app.features.settings.VectorPreferences
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.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.rx.mapOptional
import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.rx.unwrap
data class RoomJoinRuleAdvancedState(
val roomId: String,
val summary: RoomSummary? = null,
val currentRoomJoinRules: RoomJoinRules? = null,
val initialAllowList: List<MatrixItem>? = null,
val updatedAllowList: List<MatrixItem>? = null,
val choices: Async<List<JoinRulesOptionSupport>> = Uninitialized
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
}
sealed class RoomJoinRuleAdvancedAction : VectorViewModelAction {
data class SelectJoinRules(val rules: RoomJoinRules) : RoomJoinRuleAdvancedAction()
data class UpdateAllowList(val roomIds: List<String>) : RoomJoinRuleAdvancedAction()
}
sealed class RoomJoinRuleAdvancedEvents : VectorViewEvents {
object SelectAllowList : RoomJoinRuleAdvancedEvents()
}
class RoomJoinRuleAdvancedViewModel @AssistedInject constructor(
@Assisted val initialState: RoomJoinRuleAdvancedState,
private val session: Session,
private val vectorPreferences: VectorPreferences
) : VectorViewModel<RoomJoinRuleAdvancedState, RoomJoinRuleAdvancedAction, RoomJoinRuleAdvancedEvents>(initialState) {
private val room = session.getRoom(initialState.roomId)!!
private val homeServerCapabilities = session.getHomeServerCapabilities()
@AssistedFactory
interface Factory {
fun create(initialState: RoomJoinRuleAdvancedState): RoomJoinRuleAdvancedViewModel
}
companion object : MvRxViewModelFactory<RoomJoinRuleAdvancedViewModel, RoomJoinRuleAdvancedState> {
override fun create(viewModelContext: ViewModelContext, state: RoomJoinRuleAdvancedState): RoomJoinRuleAdvancedViewModel? {
val factory = when (viewModelContext) {
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
is ActivityViewModelContext -> viewModelContext.activity as? Factory
}
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
}
}
init {
val initialAllowList = session.getRoom(initialState.roomId)?.getStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition)
?.content
?.toModel<RoomJoinRulesContent>()
?.allowList
setState {
val initialAllowItems = initialAllowList.orEmpty().map {
session.getRoomSummary(it.spaceID)?.toMatrixItem()
?: MatrixItem.RoomItem(it.spaceID, null, null)
}
copy(
summary = session.getRoomSummary(initialState.roomId),
initialAllowList = initialAllowItems,
updatedAllowList = initialAllowItems
)
}
// TODO shouldn't be live
room.rx()
.liveStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition)
.mapOptional { it.content.toModel<RoomJoinRulesContent>() }
.unwrap()
.subscribe { content ->
content.joinRules?.let {
var safeRule: RoomJoinRules = it
// server is not really checking that, just to be sure let's check
val restrictedSupportedByThisVersion = homeServerCapabilities
.isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED, room.getRoomVersion())
if (it == RoomJoinRules.RESTRICTED
&& !restrictedSupportedByThisVersion) {
safeRule = RoomJoinRules.INVITE
}
val allowList = if (safeRule == RoomJoinRules.RESTRICTED) content.allowList else null
val restrictedSupport = homeServerCapabilities.isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
val couldUpgradeToRestricted = when (restrictedSupport) {
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED -> true
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED_UNSTABLE -> vectorPreferences.labsUseExperimentalRestricted()
else -> false
}
val choices = if (restrictedSupportedByThisVersion || couldUpgradeToRestricted) {
listOf(
RoomJoinRules.INVITE.toOption(false),
RoomJoinRules.RESTRICTED.toOption(!restrictedSupportedByThisVersion),
RoomJoinRules.PUBLIC.toOption(false)
)
} else {
listOf(
RoomJoinRules.INVITE.toOption(false),
RoomJoinRules.PUBLIC.toOption(false)
)
}
setState {
copy(
currentRoomJoinRules = safeRule,
choices = Success(choices)
)
}
}
}
.disposeOnClear()
}
override fun handle(action: RoomJoinRuleAdvancedAction) {
when (action) {
is RoomJoinRuleAdvancedAction.SelectJoinRules -> handleSelectRule(action)
is RoomJoinRuleAdvancedAction.UpdateAllowList -> handleUpdateAllowList(action)
}
}
fun handleUpdateAllowList(action: RoomJoinRuleAdvancedAction.UpdateAllowList) = withState { state ->
setState {
copy(
updatedAllowList = action.roomIds.map {
session.getRoomSummary(it)?.toMatrixItem() ?: MatrixItem.RoomItem(it, null, null)
}
)
}
}
fun handleSelectRule(action: RoomJoinRuleAdvancedAction.SelectJoinRules) = withState { state ->
if (action.rules == RoomJoinRules.RESTRICTED) {
// open space select?
_viewEvents.post(RoomJoinRuleAdvancedEvents.SelectAllowList)
}
setState {
copy(
currentRoomJoinRules = action.rules
)
}
}
}

View File

@ -20,25 +20,26 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentJoinRulesRecyclerBinding
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedActions
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import javax.inject.Inject
class RoomJoinRuleFragment @Inject constructor(
val controller: RoomJoinRuleAdvancedController,
// val viewModelFactory: RoomJoinRuleAdvancedViewModel.Factory,
val avatarRenderer: AvatarRenderer
) : VectorBaseFragment<FragmentJoinRulesRecyclerBinding>(),
// RoomJoinRuleAdvancedViewModel.Factory,
OnBackPressed, RoomJoinRuleAdvancedController.InteractionListener {
private val viewModel: RoomJoinRuleChooseRestrictedViewModel by activityViewModel()
@ -48,54 +49,55 @@ class RoomJoinRuleFragment @Inject constructor(
override fun onBackPressed(toolbarButton: Boolean): Boolean {
// TODO
requireActivity().finish()
val hasUnsavedChanges = withState(viewModel) { it.hasUnsavedChanges }
if (!hasUnsavedChanges) {
requireActivity().finish()
}
return true
}
override fun invalidate() = withState(viewModel) { state ->
super.invalidate()
controller.setData(state)
if (state.hasUnsavedChanges) {
// show discard and save
views.cancelButton.isVisible = true
views.positiveButton.text = getString(R.string.warning_unsaved_change_discard)
views.positiveButton.isVisible = true
views.positiveButton.text = getString(R.string.save)
} else {
views.cancelButton.isVisible = false
views.positiveButton.isVisible = true
views.positiveButton.text = getString(R.string.ok)
views.positiveButton.debouncedClicks { requireActivity().finish() }
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::class.java)
// setupRoomHistoryVisibilitySharedActionViewModel()
// setupRoomJoinRuleSharedActionViewModel()
// controller.callback = this
views.genericRecyclerView.configureWith(controller, hasFixedSize = true)
controller.interactionListener = this
// views.waitingView.waitingStatusText.setText(R.string.please_wait)
// views.waitingView.waitingStatusText.isVisible = true
views.cancelButton.debouncedClicks { requireActivity().finish() }
}
// // Use the Kotlin extension in the fragment-ktx artifact
// setFragmentResultListener("SelectAllowList") { requestKey, bundle ->
// // We use a String here, but any type that can be put in a Bundle is supported
// bundle.getStringArrayList("bundleKey")?.toList()?.let {
// viewModel.handle(RoomJoinRuleAdvancedAction.UpdateAllowList(it))
// }
// }
override fun onDestroyView() {
views.genericRecyclerView.cleanup()
super.onDestroyView()
}
viewModel.observeViewEvents {
when (it) {
RoomJoinRuleAdvancedEvents.SelectAllowList -> {
parentFragmentManager.commitTransaction {
setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
val tag = RoomJoinRuleChooseRestrictedFragment::class.simpleName
replace(R.id.simpleFragmentContainer,
RoomJoinRuleChooseRestrictedFragment::class.java,
this@RoomJoinRuleFragment.arguments,
tag
).addToBackStack(tag)
}
}
override fun didSelectRule(rules: RoomJoinRules) {
val oldRule = withState(viewModel) { it.currentRoomJoinRules }
viewModel.handle(RoomJoinRuleChooseRestrictedActions.SelectJoinRules(rules))
if (rules == RoomJoinRules.RESTRICTED && oldRule == RoomJoinRules.RESTRICTED) {
parentFragmentManager.commitTransaction {
setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
val tag = RoomJoinRuleChooseRestrictedFragment::class.simpleName
replace(R.id.simpleFragmentContainer,
RoomJoinRuleChooseRestrictedFragment::class.java,
this@RoomJoinRuleFragment.arguments,
tag
).addToBackStack(tag)
}
}
}
override fun create(initialState: RoomJoinRuleAdvancedState) = viewModelFactory.create(initialState)
override fun didSelectRule(rules: RoomJoinRules) {
viewModel.handle(RoomJoinRuleAdvancedAction.SelectJoinRules(rules))
}
}

View File

@ -53,14 +53,17 @@ class ChooseRestrictedController @Inject constructor(
is Loading -> loadingItem { id("filter_load") }
is Success -> {
if (results.invoke().isEmpty()) {
noResultItem { id("empty") }
noResultItem {
id("empty")
text(host.stringProvider.getString(R.string.no_result_placeholder))
}
} else {
results.invoke().forEach { matrixItem ->
roomSelectionItem {
id(matrixItem.id)
matrixItem(matrixItem)
avatarRenderer(host.avatarRenderer)
selected(data.selectedRoomList.firstOrNull { it == matrixItem.id } != null)
selected(data.updatedAllowList.firstOrNull { it.id == matrixItem.id } != null)
itemClickListener { host.listener?.onItemSelected(matrixItem) }
}
}
@ -77,18 +80,12 @@ class ChooseRestrictedController @Inject constructor(
centered(false)
}
// val testList = mutableListOf<MatrixItem>()
// for(i in 0..20) {
// testList.addAll(data.knownSpaceParents)
// }
// testList
data.possibleSpaceCandidate.forEach { matrixItem ->
roomSelectionItem {
id(matrixItem.id)
matrixItem(matrixItem)
avatarRenderer(host.avatarRenderer)
selected(data.selectedRoomList.firstOrNull { it == matrixItem.id } != null)
selected(data.updatedAllowList.firstOrNull { it.id == matrixItem.id } != null)
itemClickListener { host.listener?.onItemSelected(matrixItem) }
}
}
@ -105,7 +102,7 @@ class ChooseRestrictedController @Inject constructor(
id(matrixItem.id)
matrixItem(matrixItem)
avatarRenderer(host.avatarRenderer)
selected(data.selectedRoomList.firstOrNull { it == matrixItem.id } != null)
selected(data.updatedAllowList.firstOrNull { it.id == matrixItem.id } != null)
itemClickListener { host.listener?.onItemSelected(matrixItem) }
}
}

View File

@ -17,9 +17,11 @@
package im.vector.app.features.roomprofile.settings.joinrule.advanced
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.util.MatrixItem
sealed class RoomJoinRuleChooseRestrictedActions : VectorViewModelAction {
data class FilterWith(val filter: String): RoomJoinRuleChooseRestrictedActions()
data class ToggleSelection(val matrixItem: MatrixItem): RoomJoinRuleChooseRestrictedActions()
data class FilterWith(val filter: String) : RoomJoinRuleChooseRestrictedActions()
data class ToggleSelection(val matrixItem: MatrixItem) : RoomJoinRuleChooseRestrictedActions()
data class SelectJoinRules(val rules: RoomJoinRules) : RoomJoinRuleChooseRestrictedActions()
}

View File

@ -39,15 +39,11 @@ import javax.inject.Inject
class RoomJoinRuleChooseRestrictedFragment @Inject constructor(
val controller: ChooseRestrictedController,
// val viewModelFactory: RoomJoinRuleChooseRestrictedViewModel.Factory,
val avatarRenderer: AvatarRenderer
) : VectorBaseFragment<FragmentSpaceRestrictedSelectBinding>(),
// RoomJoinRuleChooseRestrictedViewModel.Factory,
ChooseRestrictedController.Listener,
OnBackPressed {
// override fun create(initialState: RoomJoinRuleChooseRestrictedState) = viewModelFactory.create(initialState)
private val viewModel: RoomJoinRuleChooseRestrictedViewModel by activityViewModel(RoomJoinRuleChooseRestrictedViewModel::class)
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
@ -65,11 +61,7 @@ class RoomJoinRuleChooseRestrictedFragment @Inject constructor(
.disposeOnDestroyView()
views.okButton.debouncedClicks {
// withState(viewModel) {
// // let's return the updated selection list
// setFragmentResult("SelectAllowList", bundleOf("bundleKey" to it.selectedRoomList))
// parentFragmentManager.popBackStack()
// }
parentFragmentManager.popBackStack()
}
}

View File

@ -32,14 +32,14 @@ data class RoomJoinRuleChooseRestrictedState(
val roomSummary: Async<RoomSummary> = Uninitialized,
val initialRoomJoinRules: RoomJoinRules? = null,
val currentRoomJoinRules: RoomJoinRules? = null,
val updatedAllowList: List<MatrixItem>? = null,
val updatedAllowList: List<MatrixItem> = emptyList(),
val choices: List<JoinRulesOptionSupport>? = null,
val initialAllowList: List<RoomJoinRulesAllowEntry> = emptyList(),
val selectedRoomList: List<String> = emptyList(),
val possibleSpaceCandidate: List<MatrixItem> = emptyList(),
val unknownRestricted: List<MatrixItem> = emptyList(),
val filter: String = "",
val filteredResults: Async<List<MatrixItem>> = Uninitialized
val filteredResults: Async<List<MatrixItem>> = Uninitialized,
val hasUnsavedChanges: Boolean = false
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
}

View File

@ -127,7 +127,10 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor(
currentRoomJoinRules = safeRule,
choices = choices,
initialAllowList = initialAllowList.orEmpty(),
selectedRoomList = initialAllowList.orEmpty().map { it.spaceID },
updatedAllowList = initialAllowList.orEmpty().map {
session.getRoomSummary(it.spaceID)?.toMatrixItem() ?: MatrixItem.RoomItem(it.spaceID, null, null)
},
// selectedRoomList = initialAllowList.orEmpty().map { it.spaceID },
possibleSpaceCandidate = possibleSpaceCandidate,
unknownRestricted = unknownAllowedOrRooms
)
@ -136,6 +139,27 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor(
}
}
fun checkForChanges() = withState { state ->
if (state.initialRoomJoinRules != state.currentRoomJoinRules) {
setState {
copy(hasUnsavedChanges = true)
}
return@withState
}
if (state.currentRoomJoinRules == RoomJoinRules.RESTRICTED) {
val allowDidChange = state.initialAllowList.map { it.spaceID } != state.updatedAllowList.map { it.id }
setState {
copy(hasUnsavedChanges = allowDidChange)
}
return@withState
}
setState {
copy(hasUnsavedChanges = false)
}
}
@AssistedFactory
interface Factory {
fun create(initialState: RoomJoinRuleChooseRestrictedState): RoomJoinRuleChooseRestrictedViewModel
@ -145,35 +169,50 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor(
when (action) {
is RoomJoinRuleChooseRestrictedActions.FilterWith -> handleFilter(action)
is RoomJoinRuleChooseRestrictedActions.ToggleSelection -> handleToggleSelection(action)
is RoomJoinRuleChooseRestrictedActions.SelectJoinRules -> handleSelectRule(action)
}.exhaustive
checkForChanges()
}
fun handleSelectRule(action: RoomJoinRuleChooseRestrictedActions.SelectJoinRules) = withState { state ->
setState {
copy(
currentRoomJoinRules = action.rules
)
}
}
private fun handleToggleSelection(action: RoomJoinRuleChooseRestrictedActions.ToggleSelection) = withState { state ->
val selection = state.selectedRoomList.toMutableList()
if (selection.contains(action.matrixItem.id)) {
selection.remove(action.matrixItem.id)
val selection = state.updatedAllowList.toMutableList()
if (selection.indexOfFirst { action.matrixItem.id == it.id } != -1) {
selection.removeAll { it.id == action.matrixItem.id }
} else {
selection.add(action.matrixItem.id)
selection.add(action.matrixItem)
}
val unknownAllowedOrRooms = mutableListOf<MatrixItem>()
// we would like to keep initial allowed here to show them unchecked
// to make it easier for users to spot the changes
state.initialAllowList.map { it.spaceID }.union(selection).sorted().forEach { entry ->
val summary = session.getRoomSummary(entry)
val union = mutableListOf<MatrixItem>().apply {
addAll(
state.initialAllowList.map {
session.getRoomSummary(it.spaceID)?.toMatrixItem() ?: MatrixItem.RoomItem(it.spaceID, null, null)
}
)
addAll(selection)
}.distinctBy { it.id }.sortedBy { it.id }
union.forEach { entry ->
val summary = session.getRoomSummary(entry.id)
if (summary == null) {
unknownAllowedOrRooms.add(
MatrixItem.RoomItem(entry, null, null)
entry
)
} else if (summary.roomType != RoomType.SPACE) {
unknownAllowedOrRooms.add(
summary.toMatrixItem()
)
} else if (!state.roomSummary.invoke()!!.flattenParentIds.contains(entry)) {
unknownAllowedOrRooms.add(entry)
} else if (!state.roomSummary.invoke()!!.flattenParentIds.contains(entry.id)) {
// it's a space but not a direct parent
unknownAllowedOrRooms.add(
summary.toMatrixItem()
)
unknownAllowedOrRooms.add(entry)
} else {
// nop
}
@ -181,7 +220,7 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor(
setState {
copy(
selectedRoomList = selection.toList(),
updatedAllowList = selection.toList(),
unknownRestricted = unknownAllowedOrRooms
)
}

View File

@ -31,6 +31,7 @@
app:layout_constraintTop_toBottomOf="@id/genericRecyclerView">
<Button
android:id="@+id/cancelButton"
style="@style/Widget.Vector.Button.Outlined"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -38,6 +39,7 @@
android:text="@string/cancel" />
<Button
android:id="@+id/positiveButton"
style="@style/Widget.Vector.Button.Positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"