Merge pull request #3302 from vector-im/feature/bca/spaces_admin_manage
Feature/bca/spaces admin manage
This commit is contained in:
commit
d81d971ce0
|
@ -29,5 +29,6 @@ data class SpaceChildInfo(
|
|||
val activeMemberCount: Int?,
|
||||
val autoJoin: Boolean,
|
||||
val viaServers: List<String>,
|
||||
val parentRoomId: String?
|
||||
val parentRoomId: String?,
|
||||
val suggested: Boolean?
|
||||
)
|
||||
|
|
|
@ -46,5 +46,8 @@ interface Space {
|
|||
@Throws
|
||||
suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean)
|
||||
|
||||
@Throws
|
||||
suspend fun setChildrenSuggested(roomId: String, suggested: Boolean)
|
||||
|
||||
// fun getChildren() : List<IRoomSummary>
|
||||
}
|
||||
|
|
|
@ -269,5 +269,9 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||
|
||||
obj.setString(RoomSummaryEntityFields.JOIN_RULES_STR, roomJoinRules?.name)
|
||||
}
|
||||
|
||||
realm.schema.get("SpaceChildSummaryEntity")
|
||||
?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
|
||||
?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,7 +89,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
|||
order = it.order,
|
||||
autoJoin = it.autoJoin ?: false,
|
||||
viaServers = it.viaServers.toList(),
|
||||
parentRoomId = roomSummaryEntity.roomId
|
||||
parentRoomId = roomSummaryEntity.roomId,
|
||||
suggested = it.suggested
|
||||
)
|
||||
},
|
||||
flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList()
|
||||
|
|
|
@ -29,6 +29,8 @@ internal open class SpaceChildSummaryEntity(
|
|||
|
||||
var autoJoin: Boolean? = null,
|
||||
|
||||
var suggested: Boolean? = null,
|
||||
|
||||
var childRoomId: String? = null,
|
||||
// Link to the actual space summary if it is known locally
|
||||
var childSummaryEntity: RoomSummaryEntity? = null,
|
||||
|
|
|
@ -63,20 +63,21 @@ internal class DefaultSpace(
|
|||
}
|
||||
|
||||
override suspend fun removeChildren(roomId: String) {
|
||||
val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
|
||||
.firstOrNull()
|
||||
?.content.toModel<SpaceChildContent>()
|
||||
?: // should we throw here?
|
||||
return
|
||||
// val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
|
||||
// .firstOrNull()
|
||||
// ?.content.toModel<SpaceChildContent>()
|
||||
// ?: // should we throw here?
|
||||
// return
|
||||
|
||||
// edit state event and set via to null
|
||||
room.sendStateEvent(
|
||||
eventType = EventType.STATE_SPACE_CHILD,
|
||||
stateKey = roomId,
|
||||
body = SpaceChildContent(
|
||||
order = existing.order,
|
||||
order = null,
|
||||
via = null,
|
||||
autoJoin = existing.autoJoin
|
||||
autoJoin = null,
|
||||
suggested = null
|
||||
).toContent()
|
||||
)
|
||||
}
|
||||
|
@ -94,7 +95,8 @@ internal class DefaultSpace(
|
|||
body = SpaceChildContent(
|
||||
order = order,
|
||||
via = existing.via,
|
||||
autoJoin = existing.autoJoin
|
||||
autoJoin = existing.autoJoin,
|
||||
suggested = existing.suggested
|
||||
).toContent()
|
||||
)
|
||||
}
|
||||
|
@ -105,6 +107,11 @@ internal class DefaultSpace(
|
|||
?.content.toModel<SpaceChildContent>()
|
||||
?: throw IllegalArgumentException("$roomId is not a child of this space")
|
||||
|
||||
if (existing.autoJoin == autoJoin) {
|
||||
// nothing to do?
|
||||
return
|
||||
}
|
||||
|
||||
// edit state event and set via to null
|
||||
room.sendStateEvent(
|
||||
eventType = EventType.STATE_SPACE_CHILD,
|
||||
|
@ -112,7 +119,31 @@ internal class DefaultSpace(
|
|||
body = SpaceChildContent(
|
||||
order = existing.order,
|
||||
via = existing.via,
|
||||
autoJoin = autoJoin
|
||||
autoJoin = autoJoin,
|
||||
suggested = existing.suggested
|
||||
).toContent()
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun setChildrenSuggested(roomId: String, suggested: Boolean) {
|
||||
val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
|
||||
.firstOrNull()
|
||||
?.content.toModel<SpaceChildContent>()
|
||||
?: throw IllegalArgumentException("$roomId is not a child of this space")
|
||||
|
||||
if (existing.suggested == suggested) {
|
||||
// nothing to do?
|
||||
return
|
||||
}
|
||||
// edit state event and set via to null
|
||||
room.sendStateEvent(
|
||||
eventType = EventType.STATE_SPACE_CHILD,
|
||||
stateKey = roomId,
|
||||
body = SpaceChildContent(
|
||||
order = existing.order,
|
||||
via = existing.via,
|
||||
autoJoin = existing.autoJoin,
|
||||
suggested = suggested
|
||||
).toContent()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -145,7 +145,8 @@ internal class DefaultSpaceService @Inject constructor(
|
|||
autoJoin = childStateEvContent.autoJoin ?: false,
|
||||
viaServers = childStateEvContent.via.orEmpty(),
|
||||
activeMemberCount = childSummary.numJoinedMembers,
|
||||
parentRoomId = childStateEv.roomId
|
||||
parentRoomId = childStateEv.roomId,
|
||||
suggested = childStateEvContent.suggested
|
||||
)
|
||||
}
|
||||
}.orEmpty()
|
||||
|
|
|
@ -125,6 +125,8 @@ import im.vector.app.features.spaces.create.CreateSpaceDefaultRoomsFragment
|
|||
import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment
|
||||
import im.vector.app.features.spaces.explore.SpaceDirectoryFragment
|
||||
import im.vector.app.features.spaces.manage.SpaceAddRoomFragment
|
||||
import im.vector.app.features.spaces.manage.SpaceManageRoomsFragment
|
||||
import im.vector.app.features.spaces.manage.SpaceSettingsFragment
|
||||
import im.vector.app.features.spaces.people.SpacePeopleFragment
|
||||
import im.vector.app.features.spaces.preview.SpacePreviewFragment
|
||||
import im.vector.app.features.terms.ReviewTermsFragment
|
||||
|
@ -684,4 +686,14 @@ interface FragmentModule {
|
|||
@IntoMap
|
||||
@FragmentKey(SpacePeopleFragment::class)
|
||||
fun bindSpacePeopleFragment(fragment: SpacePeopleFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(SpaceSettingsFragment::class)
|
||||
fun bindSpaceSettingsFragment(fragment: SpaceSettingsFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(SpaceManageRoomsFragment::class)
|
||||
fun bindSpaceManageRoomsFragment(fragment: SpaceManageRoomsFragment): Fragment
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.core.epoxy
|
||||
|
||||
import android.widget.CompoundButton
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
|
||||
fun VectorEpoxyHolder.setValueOnce(textInputEditText: TextInputEditText, value: String?) {
|
||||
if (view.isAttachedToWindow) {
|
||||
// the view is attached to the window
|
||||
// So it is a rebind of new data and you could ignore it assuming this is text that was already inputted into the view.
|
||||
// Downside is if you ever wanted to programmatically change the content of the edit text while it is on screen you would not be able to
|
||||
} else {
|
||||
textInputEditText.setText(value)
|
||||
}
|
||||
}
|
||||
|
||||
fun VectorEpoxyHolder.setValueOnce(switchView: SwitchMaterial, switchChecked: Boolean, listener: CompoundButton.OnCheckedChangeListener) {
|
||||
if (view.isAttachedToWindow) {
|
||||
// the view is attached to the window
|
||||
// So it is a rebind of new data and you could ignore it assuming this is value that was already inputted into the view.
|
||||
} else {
|
||||
switchView.isChecked = switchChecked
|
||||
switchView.setOnCheckedChangeListener(listener)
|
||||
}
|
||||
}
|
|
@ -57,15 +57,3 @@ fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_searc
|
|||
return@OnTouchListener false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the edit text value, only if necessary and move the cursor to the end of the text
|
||||
*/
|
||||
fun EditText.setTextSafe(value: String?) {
|
||||
if (value != null && text.toString() != value) {
|
||||
setText(value)
|
||||
// To fix jumping cursor to the start https://github.com/airbnb/epoxy/issues/426
|
||||
// Note: there is still a known bug if deleting char in the middle of the text, by long pressing on the backspace button.
|
||||
setSelection(value.length)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import com.google.android.material.textfield.TextInputLayout
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.extensions.setTextSafe
|
||||
import im.vector.app.core.epoxy.setValueOnce
|
||||
import im.vector.app.core.platform.SimpleTextWatcher
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_form_text_input)
|
||||
|
@ -60,7 +60,7 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
|
|||
@EpoxyAttribute
|
||||
var endIconMode: Int? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||
var onTextChange: ((String) -> Unit)? = null
|
||||
|
||||
private val onTextChangeListener = object : SimpleTextWatcher() {
|
||||
|
@ -76,8 +76,8 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
|
|||
holder.textInputLayout.error = errorMessage
|
||||
holder.textInputLayout.endIconMode = endIconMode ?: TextInputLayout.END_ICON_NONE
|
||||
|
||||
// Update only if text is different and value is not null
|
||||
holder.textInputEditText.setTextSafe(value)
|
||||
holder.setValueOnce(holder.textInputEditText, value)
|
||||
|
||||
holder.textInputEditText.isEnabled = enabled
|
||||
inputType?.let { holder.textInputEditText.inputType = it }
|
||||
holder.textInputEditText.isSingleLine = singleLine ?: false
|
||||
|
|
|
@ -26,7 +26,7 @@ import com.google.android.material.textfield.TextInputLayout
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.extensions.setTextSafe
|
||||
import im.vector.app.core.epoxy.setValueOnce
|
||||
import im.vector.app.core.platform.SimpleTextWatcher
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_form_text_input_with_button)
|
||||
|
@ -61,8 +61,8 @@ abstract class FormEditTextWithButtonItem : VectorEpoxyModel<FormEditTextWithBut
|
|||
holder.textInputLayout.isEnabled = enabled
|
||||
holder.textInputLayout.hint = hint
|
||||
|
||||
// Update only if text is different
|
||||
holder.textInputEditText.setTextSafe(value)
|
||||
holder.setValueOnce(holder.textInputEditText, value)
|
||||
|
||||
holder.textInputEditText.isEnabled = enabled
|
||||
|
||||
holder.textInputEditText.addTextChangedListener(onTextChangeListener)
|
||||
|
|
|
@ -83,7 +83,6 @@ abstract class FormEditableSquareAvatarItem : EpoxyModelWithHolder<FormEditableS
|
|||
|
||||
override fun unbind(holder: Holder) {
|
||||
avatarRenderer?.clear(holder.image)
|
||||
GlideApp.with(holder.image).clear(holder.image)
|
||||
super.unbind(holder)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import com.google.android.material.textfield.TextInputLayout
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.extensions.setTextSafe
|
||||
import im.vector.app.core.epoxy.setValueOnce
|
||||
import im.vector.app.core.platform.SimpleTextWatcher
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_form_multiline_text_input)
|
||||
|
@ -57,7 +57,7 @@ abstract class FormMultiLineEditTextItem : VectorEpoxyModel<FormMultiLineEditTex
|
|||
@EpoxyAttribute
|
||||
var typeFace: Typeface = Typeface.DEFAULT
|
||||
|
||||
@EpoxyAttribute
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||
var onTextChange: ((String) -> Unit)? = null
|
||||
|
||||
private val onTextChangeListener = object : SimpleTextWatcher() {
|
||||
|
@ -76,8 +76,8 @@ abstract class FormMultiLineEditTextItem : VectorEpoxyModel<FormMultiLineEditTex
|
|||
holder.textInputEditText.textSize = textSizeSp?.toFloat() ?: 14f
|
||||
holder.textInputEditText.minLines = minLines
|
||||
|
||||
// Update only if text is different and value is not null
|
||||
holder.textInputEditText.setTextSafe(value)
|
||||
holder.setValueOnce(holder.textInputEditText, value)
|
||||
|
||||
holder.textInputEditText.isEnabled = enabled
|
||||
|
||||
holder.textInputEditText.addTextChangedListener(onTextChangeListener)
|
||||
|
|
|
@ -25,6 +25,7 @@ import com.google.android.material.switchmaterial.SwitchMaterial
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.epoxy.setValueOnce
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_form_switch)
|
||||
|
@ -40,7 +41,7 @@ abstract class FormSwitchItem : VectorEpoxyModel<FormSwitchItem.Holder>() {
|
|||
var switchChecked: Boolean = false
|
||||
|
||||
@EpoxyAttribute
|
||||
var title: String? = null
|
||||
var title: CharSequence? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var summary: String? = null
|
||||
|
@ -61,11 +62,10 @@ abstract class FormSwitchItem : VectorEpoxyModel<FormSwitchItem.Holder>() {
|
|||
|
||||
holder.switchView.isEnabled = enabled
|
||||
|
||||
holder.switchView.setOnCheckedChangeListener(null)
|
||||
holder.switchView.isChecked = switchChecked
|
||||
holder.switchView.setOnCheckedChangeListener { _, isChecked ->
|
||||
holder.setValueOnce(holder.switchView, switchChecked) { _, isChecked ->
|
||||
listener?.invoke(isChecked)
|
||||
}
|
||||
|
||||
holder.divider.isVisible = showDivider
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ import im.vector.app.features.share.SharedData
|
|||
import im.vector.app.features.spaces.InviteRoomSpaceChooserBottomSheet
|
||||
import im.vector.app.features.spaces.SpaceExploreActivity
|
||||
import im.vector.app.features.spaces.SpacePreviewActivity
|
||||
import im.vector.app.features.spaces.manage.ManageType
|
||||
import im.vector.app.features.spaces.manage.SpaceManageActivity
|
||||
import im.vector.app.features.spaces.people.SpacePeopleActivity
|
||||
import im.vector.app.features.terms.ReviewTermsActivity
|
||||
|
@ -123,7 +124,7 @@ class DefaultNavigator @Inject constructor(
|
|||
}
|
||||
}
|
||||
Navigator.PostSwitchSpaceAction.OpenAddExistingRooms -> {
|
||||
startActivity(context, SpaceManageActivity.newIntent(context, spaceId), false)
|
||||
startActivity(context, SpaceManageActivity.newIntent(context, spaceId, ManageType.AddRooms), false)
|
||||
}
|
||||
is Navigator.PostSwitchSpaceAction.OpenDefaultRoom -> {
|
||||
val args = RoomDetailArgs(
|
||||
|
|
|
@ -27,7 +27,7 @@ import com.google.android.material.textfield.TextInputLayout
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.extensions.setTextSafe
|
||||
import im.vector.app.core.epoxy.setValueOnce
|
||||
import im.vector.app.core.platform.SimpleTextWatcher
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_room_alias_text_input)
|
||||
|
@ -62,8 +62,7 @@ abstract class RoomAliasEditItem : VectorEpoxyModel<RoomAliasEditItem.Holder>()
|
|||
holder.textInputLayout.isEnabled = enabled
|
||||
holder.textInputLayout.error = errorMessage
|
||||
|
||||
// Update only if text is different and value is not null
|
||||
holder.textInputEditText.setTextSafe(value)
|
||||
holder.setValueOnce(holder.textInputEditText, value)
|
||||
holder.textInputEditText.isEnabled = enabled
|
||||
holder.textInputEditText.addTextChangedListener(onTextChangeListener)
|
||||
holder.homeServerText.text = homeServer
|
||||
|
|
|
@ -121,7 +121,7 @@ class RoomSettingsController @Inject constructor(
|
|||
buildProfileAction(
|
||||
id = "joinRule",
|
||||
title = stringProvider.getString(R.string.room_settings_room_access_title),
|
||||
subtitle = data.getJoinRuleWording(),
|
||||
subtitle = data.getJoinRuleWording(stringProvider),
|
||||
dividerColor = dividerColor,
|
||||
divider = false,
|
||||
editable = data.actionPermissions.canChangeJoinRule,
|
||||
|
@ -142,24 +142,4 @@ class RoomSettingsController @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun RoomSettingsViewState.getJoinRuleWording(): String {
|
||||
return when (val joinRule = newRoomJoinRules.newJoinRules ?: currentRoomJoinRules) {
|
||||
RoomJoinRules.INVITE -> {
|
||||
stringProvider.getString(R.string.room_settings_room_access_private_title)
|
||||
}
|
||||
RoomJoinRules.PUBLIC -> {
|
||||
stringProvider.getString(R.string.room_settings_room_access_public_title)
|
||||
}
|
||||
RoomJoinRules.KNOCK -> {
|
||||
stringProvider.getString(R.string.room_settings_room_access_entry_knock)
|
||||
}
|
||||
RoomJoinRules.RESTRICTED -> {
|
||||
stringProvider.getString(R.string.room_settings_room_access_restricted_title)
|
||||
}
|
||||
else -> {
|
||||
stringProvider.getString(R.string.room_settings_room_access_entry_unknown, joinRule.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,8 @@ class RoomSettingsFragment @Inject constructor(
|
|||
VectorBaseFragment<FragmentRoomSettingGenericBinding>(),
|
||||
RoomSettingsController.Callback,
|
||||
OnBackPressed,
|
||||
GalleryOrCameraDialogHelper.Listener {
|
||||
GalleryOrCameraDialogHelper.Listener,
|
||||
RoomSettingsViewModel.Factory {
|
||||
|
||||
private val viewModel: RoomSettingsViewModel by fragmentViewModel()
|
||||
private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel
|
||||
|
@ -76,6 +77,10 @@ class RoomSettingsFragment @Inject constructor(
|
|||
|
||||
override fun getMenuRes() = R.menu.vector_room_settings
|
||||
|
||||
override fun create(initialState: RoomSettingsViewState): RoomSettingsViewModel {
|
||||
return viewModelFactory.create(initialState)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::class.java)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.app.features.roomprofile.settings
|
||||
|
||||
import androidx.core.net.toFile
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
|
@ -55,8 +56,11 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: RoomSettingsViewState): RoomSettingsViewModel? {
|
||||
val fragment: RoomSettingsFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
return fragment.viewModelFactory.create(state)
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,7 +127,9 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
canChangeJoinRule = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true,
|
||||
EventType.STATE_ROOM_JOIN_RULES)
|
||||
&& powerLevelsHelper.isUserAllowedToSend(session.myUserId, true,
|
||||
EventType.STATE_ROOM_GUEST_ACCESS)
|
||||
EventType.STATE_ROOM_GUEST_ACCESS),
|
||||
canAddChildren = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true,
|
||||
EventType.STATE_SPACE_CHILD)
|
||||
)
|
||||
setState { copy(actionPermissions = permissions) }
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ import android.net.Uri
|
|||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.roomprofile.RoomProfileArgs
|
||||
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
||||
|
@ -51,7 +53,8 @@ data class RoomSettingsViewState(
|
|||
val canChangeName: Boolean = false,
|
||||
val canChangeTopic: Boolean = false,
|
||||
val canChangeHistoryVisibility: Boolean = false,
|
||||
val canChangeJoinRule: Boolean = false
|
||||
val canChangeJoinRule: Boolean = false,
|
||||
val canAddChildren: Boolean = false
|
||||
)
|
||||
|
||||
sealed class AvatarAction {
|
||||
|
@ -67,4 +70,24 @@ data class RoomSettingsViewState(
|
|||
) {
|
||||
fun hasChanged() = newJoinRules != null || newGuestAccess != null
|
||||
}
|
||||
|
||||
fun getJoinRuleWording(stringProvider: StringProvider): String {
|
||||
return when (val joinRule = newRoomJoinRules.newJoinRules ?: currentRoomJoinRules) {
|
||||
RoomJoinRules.INVITE -> {
|
||||
stringProvider.getString(R.string.room_settings_room_access_private_title)
|
||||
}
|
||||
RoomJoinRules.PUBLIC -> {
|
||||
stringProvider.getString(R.string.room_settings_room_access_public_title)
|
||||
}
|
||||
RoomJoinRules.KNOCK -> {
|
||||
stringProvider.getString(R.string.room_settings_room_access_entry_knock)
|
||||
}
|
||||
RoomJoinRules.RESTRICTED -> {
|
||||
stringProvider.getString(R.string.room_settings_room_access_restricted_title)
|
||||
}
|
||||
else -> {
|
||||
stringProvider.getString(R.string.room_settings_room_access_entry_unknown, joinRule.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import im.vector.app.features.navigation.Navigator
|
|||
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
||||
import im.vector.app.features.roomprofile.RoomProfileActivity
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.spaces.manage.ManageType
|
||||
import im.vector.app.features.spaces.manage.SpaceManageActivity
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
|
@ -94,6 +95,13 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
|
|||
val powerLevelsHelper = PowerLevelsHelper(powerLevelContent)
|
||||
val canInvite = powerLevelsHelper.isUserAbleToInvite(session.myUserId)
|
||||
val canAddChild = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_SPACE_CHILD)
|
||||
|
||||
val canChangeAvatar = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR)
|
||||
val canChangeName = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME)
|
||||
val canChangeTopic = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC)
|
||||
|
||||
views.spaceSettings.isVisible = canChangeAvatar || canChangeName || canChangeTopic
|
||||
|
||||
views.invitePeople.isVisible = canInvite
|
||||
views.addRooms.isVisible = canAddChild
|
||||
}.disposeOnDestroyView()
|
||||
|
@ -107,9 +115,9 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
|
|||
navigator.openRoomProfile(requireContext(), spaceArgs.spaceId, RoomProfileActivity.EXTRA_DIRECT_ACCESS_ROOM_MEMBERS)
|
||||
}
|
||||
|
||||
views.spaceSettings.isVisible = vectorPreferences.developerMode()
|
||||
views.spaceSettings.views.bottomSheetActionClickableZone.debouncedClicks {
|
||||
navigator.openRoomProfile(requireContext(), spaceArgs.spaceId)
|
||||
// navigator.openRoomProfile(requireContext(), spaceArgs.spaceId)
|
||||
startActivity(SpaceManageActivity.newIntent(requireActivity(), spaceArgs.spaceId, ManageType.Settings))
|
||||
}
|
||||
|
||||
views.exploreRooms.views.bottomSheetActionClickableZone.debouncedClicks {
|
||||
|
@ -118,7 +126,7 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
|
|||
|
||||
views.addRooms.views.bottomSheetActionClickableZone.debouncedClicks {
|
||||
dismiss()
|
||||
startActivity(SpaceManageActivity.newIntent(requireActivity(), spaceArgs.spaceId))
|
||||
startActivity(SpaceManageActivity.newIntent(requireActivity(), spaceArgs.spaceId, ManageType.AddRooms))
|
||||
}
|
||||
|
||||
views.leaveSpace.views.bottomSheetActionClickableZone.debouncedClicks {
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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.spaces.manage
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_room_to_manage_in_space)
|
||||
abstract class RoomManageSelectionItem : VectorEpoxyModel<RoomManageSelectionItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||
@EpoxyAttribute var space: Boolean = false
|
||||
@EpoxyAttribute var selected: Boolean = false
|
||||
@EpoxyAttribute var suggested: Boolean = false
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
if (space) {
|
||||
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
|
||||
} else {
|
||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||
}
|
||||
holder.titleText.text = matrixItem.getBestName()
|
||||
|
||||
if (selected) {
|
||||
holder.checkboxImage.setImageDrawable(ContextCompat.getDrawable(holder.view.context, R.drawable.ic_checkbox_on))
|
||||
holder.checkboxImage.contentDescription = holder.view.context.getString(R.string.a11y_checked)
|
||||
} else {
|
||||
holder.checkboxImage.setImageDrawable(ContextCompat.getDrawable(holder.view.context, R.drawable.ic_checkbox_off))
|
||||
holder.checkboxImage.contentDescription = holder.view.context.getString(R.string.a11y_unchecked)
|
||||
}
|
||||
|
||||
holder.suggestedText.isVisible = suggested
|
||||
|
||||
holder.view.setOnClickListener {
|
||||
itemClickListener?.onClick(it)
|
||||
}
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val avatarImageView by bind<ImageView>(R.id.itemAddRoomRoomAvatar)
|
||||
val titleText by bind<TextView>(R.id.itemAddRoomRoomNameText)
|
||||
val suggestedText by bind<TextView>(R.id.itemManageRoomSuggested)
|
||||
val checkboxImage by bind<ImageView>(R.id.itemAddRoomRoomCheckBox)
|
||||
}
|
||||
}
|
|
@ -54,6 +54,11 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
|
|||
private val session: Session
|
||||
) : VectorViewModel<SpaceAddRoomsState, SpaceAddRoomActions, SpaceAddRoomsViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(initialState: SpaceAddRoomsState): SpaceAddRoomsViewModel
|
||||
}
|
||||
|
||||
val updatableLiveSpacePageResult: UpdatableLivePageResult by lazy {
|
||||
session.getFilteredPagedRoomSummariesLive(
|
||||
roomSummaryQueryParams {
|
||||
|
@ -106,11 +111,6 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(initialState: SpaceAddRoomsState): SpaceAddRoomsViewModel
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<SpaceAddRoomsViewModel, SpaceAddRoomsState> {
|
||||
override fun create(viewModelContext: ViewModelContext, state: SpaceAddRoomsState): SpaceAddRoomsViewModel? {
|
||||
val factory = when (viewModelContext) {
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.spaces.manage
|
||||
|
||||
import io.reactivex.functions.Predicate
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||
|
||||
class SpaceChildInfoMatchFilter : Predicate<SpaceChildInfo> {
|
||||
var filter: String = ""
|
||||
|
||||
override fun test(spaceChildInfo: SpaceChildInfo): Boolean {
|
||||
if (filter.isEmpty()) {
|
||||
// No filter
|
||||
return true
|
||||
}
|
||||
// if filter is "Jo Do", it should match "John Doe"
|
||||
return filter.split(" ").all {
|
||||
spaceChildInfo.name?.contains(it, ignoreCase = true).orFalse()
|
||||
|| spaceChildInfo.topic?.contains(it, ignoreCase = true).orFalse()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,28 +20,37 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ScreenComponent
|
||||
import im.vector.app.core.extensions.addFragmentToBackstack
|
||||
import im.vector.app.core.extensions.commitTransaction
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.platform.ToolbarConfigurable
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.databinding.ActivitySimpleBinding
|
||||
import im.vector.app.databinding.ActivitySimpleLoadingBinding
|
||||
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
|
||||
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
|
||||
import im.vector.app.features.roomdirectory.createroom.CreateRoomArgs
|
||||
import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment
|
||||
import im.vector.app.features.roomprofile.RoomProfileArgs
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
|
||||
@Parcelize
|
||||
data class SpaceManageArgs(
|
||||
val spaceId: String
|
||||
val spaceId: String,
|
||||
val manageType: ManageType
|
||||
) : Parcelable
|
||||
|
||||
class SpaceManageActivity : VectorBaseActivity<ActivitySimpleBinding>(), SpaceManageSharedViewModel.Factory {
|
||||
class SpaceManageActivity : VectorBaseActivity<ActivitySimpleLoadingBinding>(),
|
||||
ToolbarConfigurable,
|
||||
SpaceManageSharedViewModel.Factory {
|
||||
|
||||
@Inject lateinit var sharedViewModelFactory: SpaceManageSharedViewModel.Factory
|
||||
private lateinit var sharedDirectoryActionViewModel: RoomDirectorySharedActionViewModel
|
||||
|
@ -50,12 +59,26 @@ class SpaceManageActivity : VectorBaseActivity<ActivitySimpleBinding>(), SpaceMa
|
|||
injector.inject(this)
|
||||
}
|
||||
|
||||
override fun getBinding(): ActivitySimpleBinding = ActivitySimpleBinding.inflate(layoutInflater)
|
||||
override fun getBinding(): ActivitySimpleLoadingBinding = ActivitySimpleLoadingBinding.inflate(layoutInflater)
|
||||
|
||||
override fun getTitleRes(): Int = R.string.space_add_existing_rooms
|
||||
|
||||
val sharedViewModel: SpaceManageSharedViewModel by viewModel()
|
||||
|
||||
override fun showWaitingView(text: String?) {
|
||||
hideKeyboard()
|
||||
views.waitingView.waitingStatusText.isGone = views.waitingView.waitingStatusText.text.isNullOrBlank()
|
||||
super.showWaitingView(text)
|
||||
}
|
||||
|
||||
override fun hideWaitingView() {
|
||||
views.waitingView.waitingStatusText.text = null
|
||||
views.waitingView.waitingStatusText.isGone = true
|
||||
views.waitingView.waitingHorizontalProgress.progress = 0
|
||||
views.waitingView.waitingHorizontalProgress.isVisible = false
|
||||
super.hideWaitingView()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
@ -72,14 +95,35 @@ class SpaceManageActivity : VectorBaseActivity<ActivitySimpleBinding>(), SpaceMa
|
|||
|
||||
val args = intent?.getParcelableExtra<SpaceManageArgs>(MvRx.KEY_ARG)
|
||||
if (isFirstCreation()) {
|
||||
val simpleName = SpaceAddRoomFragment::class.java.simpleName
|
||||
if (supportFragmentManager.findFragmentByTag(simpleName) == null) {
|
||||
supportFragmentManager.commitTransaction {
|
||||
replace(R.id.simpleFragmentContainer,
|
||||
SpaceAddRoomFragment::class.java,
|
||||
Bundle().apply { this.putParcelable(MvRx.KEY_ARG, args) },
|
||||
simpleName
|
||||
)
|
||||
withState(sharedViewModel) {
|
||||
when (it.manageType) {
|
||||
ManageType.AddRooms -> {
|
||||
val simpleName = SpaceAddRoomFragment::class.java.simpleName
|
||||
if (supportFragmentManager.findFragmentByTag(simpleName) == null) {
|
||||
supportFragmentManager.commitTransaction {
|
||||
replace(R.id.simpleFragmentContainer,
|
||||
SpaceAddRoomFragment::class.java,
|
||||
Bundle().apply { this.putParcelable(MvRx.KEY_ARG, args) },
|
||||
simpleName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
ManageType.Settings -> {
|
||||
val simpleName = SpaceSettingsFragment::class.java.simpleName
|
||||
if (supportFragmentManager.findFragmentByTag(simpleName) == null && args?.spaceId != null) {
|
||||
supportFragmentManager.commitTransaction {
|
||||
replace(R.id.simpleFragmentContainer,
|
||||
SpaceSettingsFragment::class.java,
|
||||
Bundle().apply { this.putParcelable(MvRx.KEY_ARG, RoomProfileArgs(args.spaceId)) },
|
||||
simpleName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
ManageType.ManageRooms -> {
|
||||
// no direct access for now
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,10 +134,10 @@ class SpaceManageActivity : VectorBaseActivity<ActivitySimpleBinding>(), SpaceMa
|
|||
finish()
|
||||
}
|
||||
SpaceManagedSharedViewEvents.HideLoading -> {
|
||||
views.simpleActivityWaitingView.isVisible = false
|
||||
hideWaitingView()
|
||||
}
|
||||
SpaceManagedSharedViewEvents.ShowLoading -> {
|
||||
views.simpleActivityWaitingView.isVisible = true
|
||||
showWaitingView()
|
||||
}
|
||||
SpaceManagedSharedViewEvents.NavigateToCreateRoom -> {
|
||||
addFragmentToBackstack(
|
||||
|
@ -102,17 +146,30 @@ class SpaceManageActivity : VectorBaseActivity<ActivitySimpleBinding>(), SpaceMa
|
|||
CreateRoomArgs("", parentSpaceId = args?.spaceId)
|
||||
)
|
||||
}
|
||||
SpaceManagedSharedViewEvents.NavigateToManageRooms -> {
|
||||
args?.spaceId?.let { spaceId ->
|
||||
addFragmentToBackstack(
|
||||
R.id.simpleFragmentContainer,
|
||||
SpaceManageRoomsFragment::class.java,
|
||||
SpaceManageArgs(spaceId, ManageType.ManageRooms)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newIntent(context: Context, spaceId: String): Intent {
|
||||
fun newIntent(context: Context, spaceId: String, manageType: ManageType): Intent {
|
||||
return Intent(context, SpaceManageActivity::class.java).apply {
|
||||
putExtra(MvRx.KEY_ARG, SpaceManageArgs(spaceId))
|
||||
putExtra(MvRx.KEY_ARG, SpaceManageArgs(spaceId, manageType))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun create(initialState: SpaceManageViewState) = sharedViewModelFactory.create(initialState)
|
||||
|
||||
override fun configure(toolbar: Toolbar) {
|
||||
configureToolbar(toolbar)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.spaces.manage
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class SpaceManageRoomViewAction : VectorViewModelAction {
|
||||
data class ToggleSelection(val roomId: String) : SpaceManageRoomViewAction()
|
||||
data class UpdateFilter(val filter: String) : SpaceManageRoomViewAction()
|
||||
object ClearSelection : SpaceManageRoomViewAction()
|
||||
data class MarkAllAsSuggested(val suggested: Boolean) : SpaceManageRoomViewAction()
|
||||
object BulkRemove : SpaceManageRoomViewAction()
|
||||
object RefreshFromServer : SpaceManageRoomViewAction()
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.spaces.manage
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
|
||||
sealed class SpaceManageRoomViewEvents : VectorViewEvents {
|
||||
// object BulkActionSuccess: SpaceManageRoomViewEvents()
|
||||
data class BulkActionFailure(val errorList: List<Throwable>) : SpaceManageRoomViewEvents()
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.spaces.manage
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||
|
||||
data class SpaceManageRoomViewState(
|
||||
val spaceId: String,
|
||||
val spaceSummary: Async<RoomSummary> = Uninitialized,
|
||||
val childrenInfo: Async<List<SpaceChildInfo>> = Uninitialized,
|
||||
val selectedRooms: List<String> = emptyList(),
|
||||
val currentFilter: String = "",
|
||||
val actionState: Async<Unit> = Uninitialized
|
||||
) : MvRxState {
|
||||
constructor(args: SpaceManageArgs) : this(
|
||||
spaceId = args.spaceId
|
||||
)
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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.spaces.manage
|
||||
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Incomplete
|
||||
import im.vector.app.core.epoxy.errorWithRetryItem
|
||||
import im.vector.app.core.epoxy.loadingItem
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.core.utils.DebouncedClickListener
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class SpaceManageRoomsController @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val errorFormatter: ErrorFormatter
|
||||
) : TypedEpoxyController<SpaceManageRoomViewState>() {
|
||||
|
||||
interface Listener {
|
||||
fun toggleSelection(childInfo: SpaceChildInfo)
|
||||
fun retry()
|
||||
}
|
||||
|
||||
var listener: Listener? = null
|
||||
private val matchFilter = SpaceChildInfoMatchFilter()
|
||||
|
||||
override fun buildModels(data: SpaceManageRoomViewState?) {
|
||||
val roomListAsync = data?.childrenInfo
|
||||
if (roomListAsync is Incomplete) {
|
||||
loadingItem { id("loading") }
|
||||
return
|
||||
}
|
||||
if (roomListAsync is Fail) {
|
||||
errorWithRetryItem {
|
||||
id("Api Error")
|
||||
text(errorFormatter.toHumanReadable(roomListAsync.error))
|
||||
listener { listener?.retry() }
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val roomList = roomListAsync?.invoke() ?: return
|
||||
|
||||
val directChildren = roomList.filter {
|
||||
it.parentRoomId == data.spaceId
|
||||
/** Only direct children **/
|
||||
}
|
||||
matchFilter.filter = data.currentFilter
|
||||
val filteredResult = directChildren.filter { matchFilter.test(it) }
|
||||
|
||||
filteredResult.forEach { childInfo ->
|
||||
roomManageSelectionItem {
|
||||
id(childInfo.childRoomId)
|
||||
matrixItem(childInfo.toMatrixItem())
|
||||
avatarRenderer(avatarRenderer)
|
||||
suggested(childInfo.suggested ?: false)
|
||||
space(childInfo.roomType == RoomType.SPACE)
|
||||
selected(data.selectedRooms.contains(childInfo.childRoomId))
|
||||
itemClickListener(DebouncedClickListener({
|
||||
listener?.toggleSelection(childInfo)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* 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.spaces.manage
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.view.ActionMode.Callback
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.appcompat.queryTextChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.platform.OnBackPressed
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.databinding.FragmentSpaceAddRoomsBinding
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
class SpaceManageRoomsFragment @Inject constructor(
|
||||
private val viewModelFactory: SpaceManageRoomsViewModel.Factory,
|
||||
private val epoxyController: SpaceManageRoomsController
|
||||
) : VectorBaseFragment<FragmentSpaceAddRoomsBinding>(),
|
||||
SpaceManageRoomsViewModel.Factory,
|
||||
OnBackPressed,
|
||||
SpaceManageRoomsController.Listener,
|
||||
Callback {
|
||||
|
||||
private val viewModel by fragmentViewModel(SpaceManageRoomsViewModel::class)
|
||||
private val sharedViewModel: SpaceManageSharedViewModel by activityViewModel()
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = FragmentSpaceAddRoomsBinding.inflate(inflater)
|
||||
|
||||
override fun onBackPressed(toolbarButton: Boolean): Boolean {
|
||||
parentFragmentManager.popBackStack()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setupToolbar(views.addRoomToSpaceToolbar)
|
||||
views.appBarTitle.text = getString(R.string.space_manage_rooms_and_spaces)
|
||||
views.createNewRoom.isVisible = false
|
||||
epoxyController.listener = this
|
||||
views.roomList.configureWith(epoxyController, hasFixedSize = true, showDivider = true)
|
||||
|
||||
views.publicRoomsFilter.queryTextChanges()
|
||||
.debounce(200, TimeUnit.MILLISECONDS)
|
||||
.subscribeBy {
|
||||
viewModel.handle(SpaceManageRoomViewAction.UpdateFilter(it.toString()))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
|
||||
viewModel.selectSubscribe(SpaceManageRoomViewState::actionState) { actionState ->
|
||||
when (actionState) {
|
||||
is Loading -> {
|
||||
sharedViewModel.handle(SpaceManagedSharedAction.ShowLoading)
|
||||
}
|
||||
else -> {
|
||||
sharedViewModel.handle(SpaceManagedSharedAction.HideLoading)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is SpaceManageRoomViewEvents.BulkActionFailure -> {
|
||||
vectorBaseActivity.toast(errorFormatter.toHumanReadable(it.errorList.firstOrNull()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
epoxyController.listener = null
|
||||
views.roomList.cleanup()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun create(initialState: SpaceManageRoomViewState) = viewModelFactory.create(initialState)
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
epoxyController.setData(state)
|
||||
|
||||
state.spaceSummary.invoke()?.let {
|
||||
views.appBarSpaceInfo.text = it.displayName
|
||||
}
|
||||
if (state.selectedRooms.isNotEmpty()) {
|
||||
if (currentActionMode == null) {
|
||||
views.addRoomToSpaceToolbar.isVisible = true
|
||||
vectorBaseActivity.startSupportActionMode(this)
|
||||
} else {
|
||||
currentActionMode?.title = "${state.selectedRooms.size} selected"
|
||||
}
|
||||
// views.addRoomToSpaceToolbar.isVisible = false
|
||||
// views.addRoomToSpaceToolbar.startActionMode(this)
|
||||
} else {
|
||||
currentActionMode?.finish()
|
||||
}
|
||||
Unit
|
||||
}
|
||||
|
||||
var currentActionMode: ActionMode? = null
|
||||
|
||||
override fun toggleSelection(childInfo: SpaceChildInfo) {
|
||||
viewModel.handle(SpaceManageRoomViewAction.ToggleSelection(childInfo.childRoomId))
|
||||
}
|
||||
|
||||
override fun retry() {
|
||||
viewModel.handle(SpaceManageRoomViewAction.RefreshFromServer)
|
||||
}
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
val inflater = mode?.menuInflater
|
||||
inflater?.inflate(R.menu.menu_manage_space, menu)
|
||||
withState(viewModel) {
|
||||
mode?.title = resources.getQuantityString(R.plurals.room_details_selected, it.selectedRooms.size, it.selectedRooms.size)
|
||||
}
|
||||
currentActionMode = mode
|
||||
views.addRoomToSpaceToolbar.isVisible = false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
withState(viewModel) { state ->
|
||||
// check if we show mark as suggested or not
|
||||
val areAllSuggested = state.childrenInfo.invoke().orEmpty().filter { state.selectedRooms.contains(it.childRoomId) }
|
||||
.all { it.suggested == true }
|
||||
menu?.findItem(R.id.action_mark_as_suggested)?.isVisible = !areAllSuggested
|
||||
menu?.findItem(R.id.action_mark_as_not_suggested)?.isVisible = areAllSuggested
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||
when (item?.itemId) {
|
||||
R.id.action_delete -> {
|
||||
handleDeleteSelection()
|
||||
}
|
||||
R.id.action_mark_as_suggested -> {
|
||||
viewModel.handle(SpaceManageRoomViewAction.MarkAllAsSuggested(true))
|
||||
}
|
||||
R.id.action_mark_as_not_suggested -> {
|
||||
viewModel.handle(SpaceManageRoomViewAction.MarkAllAsSuggested(false))
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
mode?.finish()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun handleDeleteSelection() {
|
||||
viewModel.handle(SpaceManageRoomViewAction.BulkRemove)
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
// should force a refresh
|
||||
currentActionMode = null
|
||||
viewModel.handle(SpaceManageRoomViewAction.ClearSelection)
|
||||
views.coordinatorLayout.post {
|
||||
if (isAdded) {
|
||||
TransitionManager.beginDelayedTransition(views.coordinatorLayout)
|
||||
views.addRoomToSpaceToolbar.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* 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.spaces.manage
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
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 dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.mvrx.runCatchingToAsync
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.session.coroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
||||
class SpaceManageRoomsViewModel @AssistedInject constructor(
|
||||
@Assisted val initialState: SpaceManageRoomViewState,
|
||||
private val session: Session
|
||||
) : VectorViewModel<SpaceManageRoomViewState, SpaceManageRoomViewAction, SpaceManageRoomViewEvents>(initialState) {
|
||||
|
||||
init {
|
||||
val spaceSummary = session.getRoomSummary(initialState.spaceId)
|
||||
setState {
|
||||
copy(
|
||||
spaceSummary = spaceSummary?.let { Success(it) } ?: Uninitialized,
|
||||
childrenInfo = Loading()
|
||||
)
|
||||
}
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val apiResult = runCatchingToAsync {
|
||||
session.spaceService().querySpaceChildren(spaceId = initialState.spaceId).second
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
childrenInfo = apiResult
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(initialState: SpaceManageRoomViewState): SpaceManageRoomsViewModel
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<SpaceManageRoomsViewModel, SpaceManageRoomViewState> {
|
||||
override fun create(viewModelContext: ViewModelContext, state: SpaceManageRoomViewState): SpaceManageRoomsViewModel? {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
override fun handle(action: SpaceManageRoomViewAction) {
|
||||
when (action) {
|
||||
is SpaceManageRoomViewAction.ToggleSelection -> handleToggleSelection(action)
|
||||
is SpaceManageRoomViewAction.UpdateFilter -> {
|
||||
setState { copy(currentFilter = action.filter) }
|
||||
}
|
||||
SpaceManageRoomViewAction.ClearSelection -> {
|
||||
setState { copy(selectedRooms = emptyList()) }
|
||||
}
|
||||
SpaceManageRoomViewAction.BulkRemove -> {
|
||||
handleBulkRemove()
|
||||
}
|
||||
is SpaceManageRoomViewAction.MarkAllAsSuggested -> {
|
||||
handleBulkMarkAsSuggested(action.suggested)
|
||||
}
|
||||
SpaceManageRoomViewAction.RefreshFromServer -> {
|
||||
refreshSummaryAPI()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBulkRemove() = withState { state ->
|
||||
setState { copy(actionState = Loading()) }
|
||||
val selection = state.selectedRooms
|
||||
session.coroutineScope.launch(Dispatchers.IO) {
|
||||
val errorList = mutableListOf<Throwable>()
|
||||
selection.forEach {
|
||||
try {
|
||||
session.spaceService().getSpace(state.spaceId)?.removeChildren(it)
|
||||
} catch (failure: Throwable) {
|
||||
errorList.add(failure)
|
||||
}
|
||||
}
|
||||
if (errorList.isEmpty()) {
|
||||
// success
|
||||
} else {
|
||||
_viewEvents.post(SpaceManageRoomViewEvents.BulkActionFailure(errorList))
|
||||
}
|
||||
refreshSummaryAPI()
|
||||
setState { copy(actionState = Uninitialized) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBulkMarkAsSuggested(suggested: Boolean) = withState { state ->
|
||||
setState { copy(actionState = Loading()) }
|
||||
val selection = state.childrenInfo.invoke()?.filter {
|
||||
state.selectedRooms.contains(it.childRoomId)
|
||||
}.orEmpty()
|
||||
session.coroutineScope.launch(Dispatchers.IO) {
|
||||
val errorList = mutableListOf<Throwable>()
|
||||
selection.forEach { info ->
|
||||
try {
|
||||
session.spaceService().getSpace(state.spaceId)?.addChildren(
|
||||
roomId = info.childRoomId,
|
||||
viaServers = info.viaServers,
|
||||
order = info.order,
|
||||
suggested = suggested,
|
||||
autoJoin = info.autoJoin
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
errorList.add(failure)
|
||||
}
|
||||
}
|
||||
if (errorList.isEmpty()) {
|
||||
// success
|
||||
} else {
|
||||
_viewEvents.post(SpaceManageRoomViewEvents.BulkActionFailure(errorList))
|
||||
}
|
||||
refreshSummaryAPI()
|
||||
setState { copy(actionState = Uninitialized) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshSummaryAPI() {
|
||||
setState {
|
||||
copy(
|
||||
childrenInfo = Loading()
|
||||
)
|
||||
}
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val apiResult = runCatchingToAsync {
|
||||
session.spaceService().querySpaceChildren(spaceId = initialState.spaceId).second
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
childrenInfo = apiResult
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleToggleSelection(action: SpaceManageRoomViewAction.ToggleSelection) = withState { state ->
|
||||
val existing = state.selectedRooms.toMutableList()
|
||||
if (existing.contains(action.roomId)) {
|
||||
existing.remove(action.roomId)
|
||||
} else {
|
||||
existing.add(action.roomId)
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
selectedRooms = existing.toList()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,6 +55,7 @@ class SpaceManageSharedViewModel @AssistedInject constructor(
|
|||
SpaceManagedSharedAction.HideLoading -> _viewEvents.post(SpaceManagedSharedViewEvents.HideLoading)
|
||||
SpaceManagedSharedAction.ShowLoading -> _viewEvents.post(SpaceManagedSharedViewEvents.ShowLoading)
|
||||
SpaceManagedSharedAction.CreateRoom -> _viewEvents.post(SpaceManagedSharedViewEvents.NavigateToCreateRoom)
|
||||
SpaceManagedSharedAction.ManageRooms -> _viewEvents.post(SpaceManagedSharedViewEvents.NavigateToManageRooms)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,17 @@ package im.vector.app.features.spaces.manage
|
|||
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
|
||||
enum class ManageType {
|
||||
AddRooms,
|
||||
Settings,
|
||||
ManageRooms
|
||||
}
|
||||
data class SpaceManageViewState(
|
||||
val spaceId: String = ""
|
||||
val spaceId: String = "",
|
||||
val manageType: ManageType
|
||||
) : MvRxState {
|
||||
constructor(args: SpaceManageArgs) : this(
|
||||
spaceId = args.spaceId
|
||||
spaceId = args.spaceId,
|
||||
manageType = args.manageType
|
||||
)
|
||||
}
|
||||
|
|
|
@ -23,4 +23,5 @@ sealed class SpaceManagedSharedAction : VectorViewModelAction {
|
|||
object ShowLoading : SpaceManagedSharedAction()
|
||||
object HideLoading : SpaceManagedSharedAction()
|
||||
object CreateRoom : SpaceManagedSharedAction()
|
||||
object ManageRooms : SpaceManagedSharedAction()
|
||||
}
|
||||
|
|
|
@ -23,4 +23,5 @@ sealed class SpaceManagedSharedViewEvents : VectorViewEvents {
|
|||
object ShowLoading : SpaceManagedSharedViewEvents()
|
||||
object HideLoading : SpaceManagedSharedViewEvents()
|
||||
object NavigateToCreateRoom : SpaceManagedSharedViewEvents()
|
||||
object NavigateToManageRooms : SpaceManagedSharedViewEvents()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* 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.spaces.manage
|
||||
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.profiles.buildProfileAction
|
||||
import im.vector.app.core.epoxy.profiles.buildProfileSection
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.form.formEditTextItem
|
||||
import im.vector.app.features.form.formEditableSquareAvatarItem
|
||||
import im.vector.app.features.form.formMultiLineEditTextItem
|
||||
import im.vector.app.features.form.formSwitchItem
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.roomprofile.settings.RoomSettingsViewState
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class SpaceSettingsController @Inject constructor(
|
||||
private val stringProvider: StringProvider,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
colorProvider: ColorProvider,
|
||||
private val vectorPreferences: VectorPreferences
|
||||
) : TypedEpoxyController<RoomSettingsViewState>() {
|
||||
|
||||
interface Callback {
|
||||
// Delete the avatar, or cancel an avatar change
|
||||
fun onAvatarDelete()
|
||||
fun onAvatarChange()
|
||||
fun onNameChanged(name: String)
|
||||
fun onTopicChanged(topic: String)
|
||||
fun onHistoryVisibilityClicked()
|
||||
fun onJoinRuleClicked()
|
||||
fun onToggleGuestAccess()
|
||||
fun onDevTools()
|
||||
fun onDevRoomSettings()
|
||||
fun onManageRooms()
|
||||
fun setIsPublic(public: Boolean)
|
||||
}
|
||||
|
||||
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
override fun buildModels(data: RoomSettingsViewState?) {
|
||||
val roomSummary = data?.roomSummary?.invoke() ?: return
|
||||
|
||||
formEditableSquareAvatarItem {
|
||||
id("avatar")
|
||||
enabled(data.actionPermissions.canChangeAvatar)
|
||||
when (val avatarAction = data.avatarAction) {
|
||||
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 ->
|
||||
imageUri(null)
|
||||
is RoomSettingsViewState.AvatarAction.UpdateAvatar ->
|
||||
imageUri(avatarAction.newAvatarUri)
|
||||
}
|
||||
clickListener { callback?.onAvatarChange() }
|
||||
deleteListener { callback?.onAvatarDelete() }
|
||||
}
|
||||
|
||||
buildProfileSection(
|
||||
stringProvider.getString(R.string.settings)
|
||||
)
|
||||
|
||||
formEditTextItem {
|
||||
id("name")
|
||||
enabled(data.actionPermissions.canChangeName)
|
||||
value(data.newName ?: roomSummary.displayName)
|
||||
hint(stringProvider.getString(R.string.create_room_name_hint))
|
||||
showBottomSeparator(false)
|
||||
onTextChange { text ->
|
||||
callback?.onNameChanged(text)
|
||||
}
|
||||
}
|
||||
|
||||
formMultiLineEditTextItem {
|
||||
id("topic")
|
||||
enabled(data.actionPermissions.canChangeTopic)
|
||||
value(data.newTopic ?: roomSummary.topic)
|
||||
hint(stringProvider.getString(R.string.create_space_topic_hint))
|
||||
showBottomSeparator(false)
|
||||
onTextChange { text ->
|
||||
callback?.onTopicChanged(text)
|
||||
}
|
||||
}
|
||||
|
||||
if (vectorPreferences.labsUseExperimentalRestricted()) {
|
||||
buildProfileAction(
|
||||
id = "joinRule",
|
||||
title = stringProvider.getString(R.string.room_settings_room_access_title),
|
||||
subtitle = data.getJoinRuleWording(stringProvider),
|
||||
dividerColor = dividerColor,
|
||||
divider = true,
|
||||
editable = data.actionPermissions.canChangeJoinRule,
|
||||
action = { if (data.actionPermissions.canChangeJoinRule) callback?.onJoinRuleClicked() }
|
||||
)
|
||||
} else {
|
||||
val isPublic = (data.newRoomJoinRules.newJoinRules ?: data.currentRoomJoinRules) == RoomJoinRules.PUBLIC
|
||||
formSwitchItem {
|
||||
id("isPublic")
|
||||
enabled(data.actionPermissions.canChangeJoinRule)
|
||||
title(stringProvider.getString(R.string.make_this_space_public))
|
||||
switchChecked(isPublic)
|
||||
|
||||
listener { value ->
|
||||
callback?.setIsPublic(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildProfileAction(
|
||||
id = "manage_rooms",
|
||||
title = stringProvider.getString(R.string.space_settings_manage_rooms),
|
||||
// subtitle = data.getJoinRuleWording(stringProvider),
|
||||
dividerColor = dividerColor,
|
||||
divider = vectorPreferences.developerMode(),
|
||||
editable = data.actionPermissions.canAddChildren,
|
||||
action = {
|
||||
if (data.actionPermissions.canAddChildren) callback?.onManageRooms()
|
||||
}
|
||||
)
|
||||
|
||||
if (vectorPreferences.developerMode()) {
|
||||
buildProfileAction(
|
||||
id = "dev_tools",
|
||||
title = stringProvider.getString(R.string.settings_dev_tools),
|
||||
icon = R.drawable.ic_verification_glasses,
|
||||
tintIcon = false,
|
||||
dividerColor = dividerColor,
|
||||
divider = true,
|
||||
action = {
|
||||
callback?.onDevTools()
|
||||
}
|
||||
)
|
||||
|
||||
buildProfileAction(
|
||||
id = "room_tools",
|
||||
title = stringProvider.getString(R.string.room_list_quick_actions_room_settings),
|
||||
icon = R.drawable.ic_verification_glasses,
|
||||
tintIcon = false,
|
||||
dividerColor = dividerColor,
|
||||
divider = false,
|
||||
action = {
|
||||
callback?.onDevRoomSettings()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
* 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.spaces.manage
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
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.GalleryOrCameraDialogHelper
|
||||
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.intent.getFilenameFromUri
|
||||
import im.vector.app.core.platform.OnBackPressed
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.DrawableProvider
|
||||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.databinding.FragmentRoomSettingGenericBinding
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.roomprofile.RoomProfileArgs
|
||||
import im.vector.app.features.roomprofile.settings.RoomSettingsAction
|
||||
import im.vector.app.features.roomprofile.settings.RoomSettingsViewEvents
|
||||
import im.vector.app.features.roomprofile.settings.RoomSettingsViewModel
|
||||
import im.vector.app.features.roomprofile.settings.RoomSettingsViewState
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel
|
||||
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
class SpaceSettingsFragment @Inject constructor(
|
||||
private val epoxyController: SpaceSettingsController,
|
||||
private val colorProvider: ColorProvider,
|
||||
val viewModelFactory: RoomSettingsViewModel.Factory,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val drawableProvider: DrawableProvider
|
||||
) : VectorBaseFragment<FragmentRoomSettingGenericBinding>(),
|
||||
RoomSettingsViewModel.Factory,
|
||||
SpaceSettingsController.Callback,
|
||||
GalleryOrCameraDialogHelper.Listener,
|
||||
OnBackPressed {
|
||||
|
||||
private val viewModel: RoomSettingsViewModel by fragmentViewModel()
|
||||
private val sharedViewModel: SpaceManageSharedViewModel by activityViewModel()
|
||||
|
||||
private lateinit var roomJoinRuleSharedActionViewModel: RoomJoinRuleSharedActionViewModel
|
||||
|
||||
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider)
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = FragmentRoomSettingGenericBinding.inflate(inflater)
|
||||
|
||||
private val roomProfileArgs: RoomProfileArgs by args()
|
||||
|
||||
override fun getMenuRes() = R.menu.vector_room_settings
|
||||
|
||||
override fun create(initialState: RoomSettingsViewState): RoomSettingsViewModel {
|
||||
return viewModelFactory.create(initialState)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setupToolbar(views.roomSettingsToolbar)
|
||||
// roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::class.java)
|
||||
// setupRoomHistoryVisibilitySharedActionViewModel()
|
||||
setupRoomJoinRuleSharedActionViewModel()
|
||||
epoxyController.callback = this
|
||||
views.roomSettingsRecyclerView.configureWith(epoxyController, hasFixedSize = true)
|
||||
views.waitingView.waitingStatusText.setText(R.string.please_wait)
|
||||
views.waitingView.waitingStatusText.isVisible = true
|
||||
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is RoomSettingsViewEvents.Failure -> showFailure(it.throwable)
|
||||
RoomSettingsViewEvents.Success -> showSuccess()
|
||||
RoomSettingsViewEvents.GoBack -> {
|
||||
ignoreChanges = true
|
||||
vectorBaseActivity.onBackPressed()
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
epoxyController.callback = null
|
||||
views.roomSettingsRecyclerView.cleanup()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
withState(viewModel) { state ->
|
||||
menu.findItem(R.id.roomSettingsSaveAction).isVisible = state.showSaveAction
|
||||
}
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.roomSettingsSaveAction) {
|
||||
viewModel.handle(RoomSettingsAction.Save)
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun renderRoomSummary(state: RoomSettingsViewState) {
|
||||
views.waitingView.root.isVisible = state.isLoading
|
||||
|
||||
state.roomSummary()?.let {
|
||||
views.roomSettingsToolbarTitleView.text = it.displayName
|
||||
views.roomSettingsToolbarTitleView.setCompoundDrawablesWithIntrinsicBounds(
|
||||
null,
|
||||
null,
|
||||
drawableProvider.getDrawable(R.drawable.ic_beta_pill),
|
||||
null
|
||||
)
|
||||
avatarRenderer.renderSpace(it.toMatrixItem(), views.roomSettingsToolbarAvatarImageView)
|
||||
views.roomSettingsDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel)
|
||||
}
|
||||
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
epoxyController.setData(state)
|
||||
renderRoomSummary(state)
|
||||
}
|
||||
|
||||
private fun setupRoomJoinRuleSharedActionViewModel() {
|
||||
roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java)
|
||||
roomJoinRuleSharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { action ->
|
||||
viewModel.handle(RoomSettingsAction.SetRoomJoinRule(action.roomJoinRule))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
}
|
||||
|
||||
private var ignoreChanges = false
|
||||
|
||||
override fun onBackPressed(toolbarButton: Boolean): Boolean {
|
||||
if (ignoreChanges) return false
|
||||
|
||||
return withState(viewModel) {
|
||||
return@withState if (it.showSaveAction) {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.dialog_title_warning)
|
||||
.setMessage(R.string.warning_unsaved_change)
|
||||
.setPositiveButton(R.string.warning_unsaved_change_discard) { _, _ ->
|
||||
viewModel.handle(RoomSettingsAction.Cancel)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSuccess() {
|
||||
activity?.toast(R.string.room_settings_save_success)
|
||||
}
|
||||
|
||||
override fun onNameChanged(name: String) {
|
||||
viewModel.handle(RoomSettingsAction.SetRoomName(name))
|
||||
}
|
||||
|
||||
override fun onTopicChanged(topic: String) {
|
||||
viewModel.handle(RoomSettingsAction.SetRoomTopic(topic))
|
||||
}
|
||||
|
||||
override fun onHistoryVisibilityClicked() {
|
||||
// N/A for space settings screen
|
||||
}
|
||||
|
||||
override fun onJoinRuleClicked() = withState(viewModel) { state ->
|
||||
val currentJoinRule = state.newRoomJoinRules.newJoinRules ?: state.currentRoomJoinRules
|
||||
RoomJoinRuleBottomSheet.newInstance(currentJoinRule)
|
||||
.show(childFragmentManager, "RoomJoinRuleBottomSheet")
|
||||
}
|
||||
|
||||
override fun onToggleGuestAccess() = withState(viewModel) { state ->
|
||||
val currentGuestAccess = state.newRoomJoinRules.newGuestAccess ?: state.currentGuestAccess
|
||||
val toggled = if (currentGuestAccess == GuestAccess.Forbidden) GuestAccess.CanJoin else GuestAccess.Forbidden
|
||||
viewModel.handle(RoomSettingsAction.SetRoomGuestAccess(toggled))
|
||||
}
|
||||
|
||||
override fun onDevTools() = withState(viewModel) { state ->
|
||||
navigator.openDevTools(requireContext(), state.roomId)
|
||||
}
|
||||
|
||||
override fun onDevRoomSettings() = withState(viewModel) { state ->
|
||||
navigator.openRoomProfile(requireContext(), state.roomId)
|
||||
}
|
||||
|
||||
override fun onManageRooms() {
|
||||
sharedViewModel.handle(SpaceManagedSharedAction.ManageRooms)
|
||||
}
|
||||
|
||||
override fun setIsPublic(public: Boolean) {
|
||||
if (public) {
|
||||
viewModel.handle(RoomSettingsAction.SetRoomJoinRule(RoomJoinRules.PUBLIC))
|
||||
viewModel.handle(RoomSettingsAction.SetRoomHistoryVisibility(RoomHistoryVisibility.WORLD_READABLE))
|
||||
} else {
|
||||
viewModel.handle(RoomSettingsAction.SetRoomJoinRule(RoomJoinRules.INVITE))
|
||||
viewModel.handle(RoomSettingsAction.SetRoomHistoryVisibility(RoomHistoryVisibility.INVITED))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onImageReady(uri: Uri?) {
|
||||
uri ?: return
|
||||
viewModel.handle(
|
||||
RoomSettingsAction.SetAvatarAction(
|
||||
RoomSettingsViewState.AvatarAction.UpdateAvatar(
|
||||
newAvatarUri = uri,
|
||||
newAvatarFileName = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onAvatarDelete() {
|
||||
withState(viewModel) {
|
||||
when (it.avatarAction) {
|
||||
RoomSettingsViewState.AvatarAction.None -> {
|
||||
viewModel.handle(RoomSettingsAction.SetAvatarAction(RoomSettingsViewState.AvatarAction.DeleteAvatar))
|
||||
}
|
||||
RoomSettingsViewState.AvatarAction.DeleteAvatar -> {
|
||||
/* Should not happen */
|
||||
}
|
||||
is RoomSettingsViewState.AvatarAction.UpdateAvatar -> {
|
||||
// Cancel the update of the avatar
|
||||
viewModel.handle(RoomSettingsAction.SetAvatarAction(RoomSettingsViewState.AvatarAction.None))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAvatarChange() {
|
||||
galleryOrCameraDialogHelper.show()
|
||||
}
|
||||
}
|
|
@ -94,12 +94,10 @@
|
|||
android:id="@+id/spaceSettings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:actionTitle="@string/settings"
|
||||
app:leftIcon="@drawable/ic_settings_root_general"
|
||||
app:tint="?attr/riotx_text_primary"
|
||||
app:titleTextColor="?attr/riotx_text_primary"
|
||||
tools:visibility="visible" />
|
||||
app:titleTextColor="?attr/riotx_text_primary"/>
|
||||
|
||||
<im.vector.app.core.ui.views.BottomSheetActionButton
|
||||
android:id="@+id/exploreRooms"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
android:id="@+id/rootConstraintLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?riotx_header_panel_background">
|
||||
android:background="?riotx_background">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/roomSettingsToolbar"
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
android:id="@+id/formSwitchDivider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:background="?riotx_header_panel_border_mobile"
|
||||
android:background="?vctr_list_divider_color"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/itemPublicRoomLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?riotx_background"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="50dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/itemAddRoomRoomAvatar"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:contentDescription="@string/avatar"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemAddRoomRoomNameText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="17dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/itemManageRoomSuggested"
|
||||
app:layout_constraintStart_toEndOf="@id/itemAddRoomRoomAvatar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="@sample/matrix.json/data/roomName" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemManageRoomSuggested"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/itemAddRoomRoomCheckBox"
|
||||
app:layout_constraintStart_toEndOf="@id/itemAddRoomRoomNameText"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
android:text="@string/space_suggested" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/itemAddRoomRoomCheckBox"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:contentDescription="@string/a11y_checked"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/itemManageRoomSuggested"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_checkbox_on" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_delete"
|
||||
android:title="@string/delete"
|
||||
android:icon="@drawable/ic_delete_unsent_messages"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_mark_as_suggested"
|
||||
android:title="@string/space_mark_as_suggested"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_mark_as_not_suggested"
|
||||
android:title="@string/space_mark_as_not_suggested"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
</menu>
|
|
@ -3356,4 +3356,10 @@
|
|||
<string name="user_invites_you">%s invites you</string>
|
||||
|
||||
<string name="looking_for_someone_not_in_space">Looking for someone not in %s?</string>
|
||||
<string name="space_settings_manage_rooms">Manage rooms</string>
|
||||
<string name="make_this_space_public">Make this space public</string>
|
||||
<string name="space_suggested">Suggested</string>
|
||||
<string name="space_mark_as_suggested">Mark as suggested</string>
|
||||
<string name="space_mark_as_not_suggested">Mark as not suggested</string>
|
||||
<string name="space_manage_rooms_and_spaces">Manage rooms and spaces</string>
|
||||
</resources>
|
||||
|
|
|
@ -11,6 +11,13 @@
|
|||
<item name="android:background">?riotx_background</item>
|
||||
</style>
|
||||
|
||||
<style name="ActionModeTheme" parent="Widget.AppCompat.ActionMode">
|
||||
<item name="background">?riotx_background</item>
|
||||
<item name="titleTextStyle">@style/Vector.Toolbar.Title</item>
|
||||
<item name="subtitleTextStyle">@style/Vector.Toolbar.SubTitle</item>
|
||||
<item name="actionMenuTextColor">?colorOnPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="VectorToolbarStyle" parent="VectorToolbarStyleWithPadding">
|
||||
<item name="contentInsetStartWithNavigation">0dp</item>
|
||||
</style>
|
||||
|
@ -65,7 +72,7 @@
|
|||
|
||||
<!-- actionbar icons color -->
|
||||
<style name="Vector.ActionBarTheme" parent="ThemeOverlay.MaterialComponents.ActionBar">
|
||||
<item name="colorControlNormal">@android:color/white</item>
|
||||
<item name="colorControlNormal">?android:attr/textColorPrimary</item>
|
||||
</style>
|
||||
|
||||
<!-- custom action bar -->
|
||||
|
|
|
@ -181,6 +181,8 @@
|
|||
|
||||
<!-- chat effect -->
|
||||
<item name="vctr_chat_effect_snow_background">@android:color/transparent</item>
|
||||
|
||||
<item name="actionModeStyle">@style/ActionModeTheme</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Dark" parent="AppTheme.Base.Dark" />
|
||||
|
|
|
@ -183,6 +183,9 @@
|
|||
|
||||
<!-- chat effect -->
|
||||
<item name="vctr_chat_effect_snow_background">@color/black_alpha</item>
|
||||
|
||||
<item name="actionModeStyle">@style/ActionModeTheme</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Light" parent="AppTheme.Base.Light" />
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<im.vector.app.core.preference.VectorSwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY"
|
||||
android:icon="@drawable/ic_verification_glasses"
|
||||
android:summary="@string/settings_developer_mode_summary"
|
||||
android:title="@string/settings_developer_mode" />
|
||||
|
||||
|
|
Loading…
Reference in New Issue