leaving space aligned with ios (#5942)

This commit is contained in:
fedrunov 2022-05-19 11:49:08 +02:00 committed by GitHub
parent 6bc97df0bb
commit 18842b5e3d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 372 additions and 387 deletions

1
changelog.d/5728.misc Normal file
View file

@ -0,0 +1 @@
leaving space experience changed to be aligned with iOS

View file

@ -104,11 +104,10 @@ class SpaceMenuRobot {
fun leaveSpace() {
clickOnSheet(R.id.leaveSpace)
waitUntilDialogVisible(ViewMatchers.withId(R.id.leaveButton))
clickOn(R.id.leave_selected)
waitUntilActivityVisible<SpaceLeaveAdvancedActivity> {
waitUntilViewVisible(ViewMatchers.withId(R.id.roomList))
}
clickOn(R.id.spaceLeaveSelectAll)
clickOn(R.id.spaceLeaveButton)
waitUntilViewVisible(ViewMatchers.withId(R.id.groupListView))
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2022 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.utils
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.appbar.AppBarLayout
/**
* [AppBarLayout.Behavior] subclass with a possibility to disable behavior.
* Useful for cases when in some view state we want prevent toolbar from collapsing/expanding by scroll events
*/
class ToggleableAppBarLayoutBehavior : AppBarLayout.Behavior {
constructor() : super()
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
var isEnabled = true
override fun onStartNestedScroll(parent: CoordinatorLayout,
child: AppBarLayout,
directTargetChild: View,
target: View,
nestedScrollAxes: Int,
type: Int): Boolean {
return isEnabled && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type)
}
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout,
child: AppBarLayout,
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int,
consumed: IntArray) {
if (!isEnabled) return
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed)
}
override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout,
child: AppBarLayout,
target: View,
dx: Int,
dy: Int,
consumed: IntArray,
type: Int) {
if (!isEnabled) return
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
}
}

View file

@ -1,194 +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.spaces
import android.app.Activity
import android.graphics.Typeface
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.text.toSpannable
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.args
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.styleMatchingText
import im.vector.app.databinding.BottomSheetLeaveSpaceBinding
import im.vector.app.features.displayname.getBestName
import im.vector.app.features.spaces.leave.SpaceLeaveAdvancedActivity
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize
import me.gujun.android.span.span
import org.matrix.android.sdk.api.util.toMatrixItem
import reactivecircus.flowbinding.android.widget.checkedChanges
import javax.inject.Inject
@AndroidEntryPoint
class LeaveSpaceBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetLeaveSpaceBinding>() {
val settingsViewModel: SpaceMenuViewModel by parentFragmentViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetLeaveSpaceBinding {
return BottomSheetLeaveSpaceBinding.inflate(inflater, container, false)
}
@Inject lateinit var colorProvider: ColorProvider
@Inject lateinit var errorFormatter: ErrorFormatter
@Parcelize
data class Args(
val spaceId: String
) : Parcelable
override val showExpanded = true
private val spaceArgs: SpaceBottomSheetSettingsArgs by args()
private val cherryPickLeaveActivityResult = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
// nothing actually?
} else {
// move back to default
settingsViewModel.handle(SpaceLeaveViewAction.SetAutoLeaveAll)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
views.autoLeaveRadioGroup.checkedChanges()
.onEach {
when (it) {
views.leaveAll.id -> {
settingsViewModel.handle(SpaceLeaveViewAction.SetAutoLeaveAll)
}
views.leaveNone.id -> {
settingsViewModel.handle(SpaceLeaveViewAction.SetAutoLeaveNone)
}
views.leaveSelected.id -> {
settingsViewModel.handle(SpaceLeaveViewAction.SetAutoLeaveSelected)
// launch dedicated activity
cherryPickLeaveActivityResult.launch(
SpaceLeaveAdvancedActivity.newIntent(requireContext(), spaceArgs.spaceId)
)
}
}
}
.launchIn(viewLifecycleOwner.lifecycleScope)
views.leaveButton.debouncedClicks {
settingsViewModel.handle(SpaceLeaveViewAction.LeaveSpace)
}
views.cancelButton.debouncedClicks {
dismiss()
}
}
override fun invalidate() = withState(settingsViewModel) { state ->
super.invalidate()
val spaceSummary = state.spaceSummary ?: return@withState
val bestName = spaceSummary.toMatrixItem().getBestName()
val commonText = getString(R.string.space_leave_prompt_msg_with_name, bestName)
.toSpannable().styleMatchingText(bestName, Typeface.BOLD)
val warningMessage: CharSequence = if (spaceSummary.otherMemberIds.isEmpty()) {
span {
+commonText
+"\n\n"
span(getString(R.string.space_leave_prompt_msg_only_you)) {
textColor = colorProvider.getColorFromAttribute(R.attr.colorError)
}
}
} else if (state.isLastAdmin) {
span {
+commonText
+"\n\n"
span(getString(R.string.space_leave_prompt_msg_as_admin)) {
textColor = colorProvider.getColorFromAttribute(R.attr.colorError)
}
}
} else if (!spaceSummary.isPublic) {
span {
+commonText
+"\n\n"
span(getString(R.string.space_leave_prompt_msg_private)) {
textColor = colorProvider.getColorFromAttribute(R.attr.colorError)
}
}
} else {
commonText
}
views.bottomLeaveSpaceWarningText.setTextOrHide(warningMessage)
views.inlineErrorText.setTextOrHide(null)
if (state.leavingState is Loading) {
views.leaveButton.isInvisible = true
views.cancelButton.isInvisible = true
views.leaveProgress.isVisible = true
} else {
views.leaveButton.isInvisible = false
views.cancelButton.isInvisible = false
views.leaveProgress.isVisible = false
if (state.leavingState is Fail) {
views.inlineErrorText.setTextOrHide(errorFormatter.toHumanReadable(state.leavingState.error))
}
}
val hasChildren = (spaceSummary.spaceChildren?.size ?: 0) > 0
if (hasChildren) {
views.autoLeaveRadioGroup.isVisible = true
when (state.leaveMode) {
SpaceMenuState.LeaveMode.LEAVE_ALL -> {
views.autoLeaveRadioGroup.check(views.leaveAll.id)
}
SpaceMenuState.LeaveMode.LEAVE_NONE -> {
views.autoLeaveRadioGroup.check(views.leaveNone.id)
}
SpaceMenuState.LeaveMode.LEAVE_SELECTED -> {
views.autoLeaveRadioGroup.check(views.leaveSelected.id)
}
}
} else {
views.autoLeaveRadioGroup.isVisible = false
}
}
companion object {
fun newInstance(spaceId: String): LeaveSpaceBottomSheet {
return LeaveSpaceBottomSheet().apply {
setArguments(SpaceBottomSheetSettingsArgs(spaceId))
}
}
}
}

View file

@ -35,6 +35,7 @@ import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.rageshake.BugReporter
import im.vector.app.features.roomprofile.RoomProfileActivity
import im.vector.app.features.spaces.leave.SpaceLeaveAdvancedActivity
import im.vector.app.features.spaces.manage.ManageType
import im.vector.app.features.spaces.manage.SpaceManageActivity
import kotlinx.parcelize.Parcelize
@ -109,7 +110,7 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
}
views.leaveSpace.views.bottomSheetActionClickableZone.debouncedClicks {
LeaveSpaceBottomSheet.newInstance(spaceArgs.spaceId).show(childFragmentManager, "LOGOUT")
startActivity(SpaceLeaveAdvancedActivity.newIntent(requireContext(), spaceArgs.spaceId))
}
}

View file

@ -21,6 +21,9 @@ import im.vector.app.core.platform.VectorViewModelAction
sealed class SpaceLeaveAdvanceViewAction : VectorViewModelAction {
data class ToggleSelection(val roomId: String) : SpaceLeaveAdvanceViewAction()
data class UpdateFilter(val filter: String) : SpaceLeaveAdvanceViewAction()
data class SetFilteringEnabled(val isEnabled: Boolean) : SpaceLeaveAdvanceViewAction()
object DoLeave : SpaceLeaveAdvanceViewAction()
object ClearError : SpaceLeaveAdvanceViewAction()
object SelectAll : SpaceLeaveAdvanceViewAction()
object SelectNone : SpaceLeaveAdvanceViewAction()
}

View file

@ -28,8 +28,11 @@ data class SpaceLeaveAdvanceViewState(
val allChildren: Async<List<RoomSummary>> = Uninitialized,
val selectedRooms: List<String> = emptyList(),
val currentFilter: String = "",
val leaveState: Async<Unit> = Uninitialized
val leaveState: Async<Unit> = Uninitialized,
val isFilteringEnabled: Boolean = false,
val isLastAdmin: Boolean = false
) : MavericksState {
constructor(args: SpaceBottomSheetSettingsArgs) : this(
spaceId = args.spaceId
)

View file

@ -18,20 +18,23 @@ package im.vector.app.features.spaces.leave
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.lifecycle.lifecycleScope
import androidx.appcompat.widget.SearchView
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
import com.airbnb.mvrx.Success
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.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.ToggleableAppBarLayoutBehavior
import im.vector.app.databinding.FragmentSpaceLeaveAdvancedBinding
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import reactivecircus.flowbinding.appcompat.queryTextChanges
import javax.inject.Inject
class SpaceLeaveAdvancedFragment @Inject constructor(
@ -44,11 +47,33 @@ class SpaceLeaveAdvancedFragment @Inject constructor(
val viewModel: SpaceLeaveAdvancedViewModel by activityViewModel()
override fun getMenuRes() = R.menu.menu_space_leave
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar(views.toolbar)
.allowBack()
controller.listener = this
withState(viewModel) { state ->
setupToolbar(views.toolbar)
.setSubtitle(state.spaceSummary?.name)
.allowBack()
state.spaceSummary?.let { summary ->
val warningMessage: CharSequence? = when {
summary.otherMemberIds.isEmpty() -> getString(R.string.space_leave_prompt_msg_only_you)
state.isLastAdmin -> getString(R.string.space_leave_prompt_msg_as_admin)
!summary.isPublic -> getString(R.string.space_leave_prompt_msg_private)
else -> null
}
views.spaceLeavePromptDescription.isVisible = warningMessage != null
views.spaceLeavePromptDescription.text = warningMessage
}
views.spaceLeavePromptTitle.text = getString(R.string.space_leave_prompt_msg_with_name, state.spaceSummary?.name ?: "")
}
views.roomList.configureWith(controller)
views.spaceLeaveCancel.debouncedClicks { requireActivity().finish() }
@ -56,12 +81,23 @@ class SpaceLeaveAdvancedFragment @Inject constructor(
viewModel.handle(SpaceLeaveAdvanceViewAction.DoLeave)
}
views.publicRoomsFilter.queryTextChanges()
.debounce(100)
.onEach {
viewModel.handle(SpaceLeaveAdvanceViewAction.UpdateFilter(it.toString()))
}
.launchIn(viewLifecycleOwner.lifecycleScope)
views.spaceLeaveSelectGroup.setOnCheckedChangeListener { _, optionId ->
when (optionId) {
R.id.spaceLeaveSelectAll -> viewModel.handle(SpaceLeaveAdvanceViewAction.SelectAll)
R.id.spaceLeaveSelectNone -> viewModel.handle(SpaceLeaveAdvanceViewAction.SelectNone)
}
}
}
override fun onPrepareOptionsMenu(menu: Menu) {
menu.findItem(R.id.menu_space_leave_search)?.let { searchItem ->
searchItem.bind(
onExpanded = { viewModel.handle(SpaceLeaveAdvanceViewAction.SetFilteringEnabled(isEnabled = true)) },
onCollapsed = { viewModel.handle(SpaceLeaveAdvanceViewAction.SetFilteringEnabled(isEnabled = false)) },
onTextChanged = { viewModel.handle(SpaceLeaveAdvanceViewAction.UpdateFilter(it)) }
)
}
super.onPrepareOptionsMenu(menu)
}
override fun onDestroyView() {
@ -72,10 +108,63 @@ class SpaceLeaveAdvancedFragment @Inject constructor(
override fun invalidate() = withState(viewModel) { state ->
super.invalidate()
if (state.isFilteringEnabled) {
views.appBarLayout.setExpanded(false)
}
updateAppBarBehaviorState(state)
updateRadioButtonsState(state)
controller.setData(state)
}
override fun onItemSelected(roomSummary: RoomSummary) {
viewModel.handle(SpaceLeaveAdvanceViewAction.ToggleSelection(roomSummary.roomId))
}
private fun updateAppBarBehaviorState(state: SpaceLeaveAdvanceViewState) {
val behavior = (views.appBarLayout.layoutParams as CoordinatorLayout.LayoutParams).behavior as ToggleableAppBarLayoutBehavior
behavior.isEnabled = !state.isFilteringEnabled
}
private fun updateRadioButtonsState(state: SpaceLeaveAdvanceViewState) {
(state.allChildren as? Success)?.invoke()?.size?.let { allChildrenCount ->
when (state.selectedRooms.size) {
0 -> views.spaceLeaveSelectNone.isChecked = true
allChildrenCount -> views.spaceLeaveSelectAll.isChecked = true
else -> views.spaceLeaveSelectSemi.isChecked = true
}
}
}
private fun MenuItem.bind(
onExpanded: () -> Unit,
onCollapsed: () -> Unit,
onTextChanged: (String) -> Unit) {
setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
onExpanded()
return true
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
onCollapsed()
return true
}
})
val searchView = actionView as SearchView
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
onTextChanged(newText ?: "")
return true
}
})
}
}

View file

@ -36,9 +36,14 @@ import okhttp3.internal.toImmutableList
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter
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.getRoom
import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.room.getStateEvent
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.api.session.room.powerlevels.Role
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.flow.unwrap
@ -50,52 +55,24 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor(
private val appStateHandler: AppStateHandler
) : VectorViewModel<SpaceLeaveAdvanceViewState, SpaceLeaveAdvanceViewAction, EmptyViewEvents>(initialState) {
override fun handle(action: SpaceLeaveAdvanceViewAction) = withState { state ->
when (action) {
is SpaceLeaveAdvanceViewAction.ToggleSelection -> {
val existing = state.selectedRooms.toMutableList()
if (existing.contains(action.roomId)) {
existing.remove(action.roomId)
} else {
existing.add(action.roomId)
}
setState {
copy(
selectedRooms = existing.toImmutableList()
)
}
}
is SpaceLeaveAdvanceViewAction.UpdateFilter -> {
setState { copy(currentFilter = action.filter) }
}
SpaceLeaveAdvanceViewAction.DoLeave -> {
setState { copy(leaveState = Loading()) }
viewModelScope.launch {
try {
state.selectedRooms.forEach {
try {
session.roomService().leaveRoom(it)
} catch (failure: Throwable) {
// silently ignore?
Timber.e(failure, "Fail to leave sub rooms/spaces")
}
}
init {
val space = session.getRoom(initialState.spaceId)
val spaceSummary = space?.roomSummary()
session.spaceService().leaveSpace(initialState.spaceId)
// We observe the membership and to dismiss when we have remote echo of leaving
} catch (failure: Throwable) {
setState { copy(leaveState = Fail(failure)) }
}
}
}
SpaceLeaveAdvanceViewAction.ClearError -> {
setState { copy(leaveState = Uninitialized) }
val powerLevelsEvent = space?.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
powerLevelsEvent?.content?.toModel<PowerLevelsContent>()?.let { powerLevelsContent ->
val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent)
val isAdmin = powerLevelsHelper.getUserRole(session.myUserId) is Role.Admin
val otherAdminCount = spaceSummary?.otherMemberIds
?.map { powerLevelsHelper.getUserRole(it) }
?.count { it is Role.Admin }
?: 0
val isLastAdmin = isAdmin && otherAdminCount == 0
setState {
copy(isLastAdmin = isLastAdmin)
}
}
}
init {
val spaceSummary = session.getRoomSummary(initialState.spaceId)
setState { copy(spaceSummary = spaceSummary) }
session.getRoom(initialState.spaceId)?.let { room ->
room.flow().liveRoomSummary()
@ -127,6 +104,62 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor(
}
}
override fun handle(action: SpaceLeaveAdvanceViewAction) {
when (action) {
is SpaceLeaveAdvanceViewAction.UpdateFilter -> setState { copy(currentFilter = action.filter) }
SpaceLeaveAdvanceViewAction.ClearError -> setState { copy(leaveState = Uninitialized) }
SpaceLeaveAdvanceViewAction.SelectNone -> setState { copy(selectedRooms = emptyList()) }
is SpaceLeaveAdvanceViewAction.SetFilteringEnabled -> setState { copy(isFilteringEnabled = action.isEnabled) }
is SpaceLeaveAdvanceViewAction.ToggleSelection -> handleSelectionToggle(action)
SpaceLeaveAdvanceViewAction.DoLeave -> handleLeave()
SpaceLeaveAdvanceViewAction.SelectAll -> handleSelectAll()
}
}
private fun handleSelectAll() = withState { state ->
val filteredRooms = (state.allChildren as? Success)?.invoke()?.filter {
it.name.contains(state.currentFilter, true)
}
filteredRooms?.let {
setState { copy(selectedRooms = it.map { it.roomId }) }
}
}
private fun handleLeave() = withState { state ->
setState { copy(leaveState = Loading()) }
viewModelScope.launch {
try {
state.selectedRooms.forEach {
try {
session.roomService().leaveRoom(it)
} catch (failure: Throwable) {
// silently ignore?
Timber.e(failure, "Fail to leave sub rooms/spaces")
}
}
session.spaceService().leaveSpace(initialState.spaceId)
// We observe the membership and to dismiss when we have remote echo of leaving
} catch (failure: Throwable) {
setState { copy(leaveState = Fail(failure)) }
}
}
}
private fun handleSelectionToggle(action: SpaceLeaveAdvanceViewAction.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.toImmutableList(),
)
}
}
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<SpaceLeaveAdvancedViewModel, SpaceLeaveAdvanceViewState> {
override fun create(initialState: SpaceLeaveAdvanceViewState): SpaceLeaveAdvancedViewModel

View file

@ -1,105 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:colorBackground"
android:orientation="vertical">
<TextView
android:id="@+id/bottom_leave_space_warning_text"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginTop="20dp"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginBottom="8dp"
android:textColor="?vctr_content_primary"
tools:text="@string/space_leave_prompt_msg_with_name" />
<RadioGroup
android:id="@+id/autoLeaveRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="?dialogPreferredPadding"
android:paddingTop="12dp"
android:paddingEnd="?dialogPreferredPadding"
android:paddingBottom="12dp">
<RadioButton
android:id="@+id/leave_all"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/leave_all_rooms_and_spaces"
tools:checked="true" />
<RadioButton
android:id="@+id/leave_none"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="180dp"
android:text="@string/dont_leave_any" />
<RadioButton
android:id="@+id/leave_selected"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="180dp"
android:text="@string/leave_specific_ones" />
</RadioGroup>
<TextView
android:id="@+id/inlineErrorText"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginTop="4dp"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:textColor="?colorError"
tools:visibility="visible"
tools:text="@string/error_no_network"
android:visibility="gone" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="16dp">
<ProgressBar
android:id="@+id/leaveProgress"
style="?android:attr/progressBarStyle"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:visibility="gone"
tools:visibility="visible" />
<Button
android:id="@+id/leaveButton"
style="@style/Widget.Vector.Button.Destructive"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_gravity="center_horizontal"
android:text="@string/leave_space" />
</FrameLayout>
<Button
android:id="@+id/cancelButton"
style="@style/Widget.Vector.Button.Text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:text="@string/action_cancel" />
</LinearLayout>

View file

@ -16,41 +16,107 @@
tools:listitem="@layout/item_room_to_add_in_space" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
app:layout_behavior="im.vector.app.core.utils.ToggleableAppBarLayoutBehavior">
<!-- minHeight="0dp" is important to collapse on scroll -->
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/spaceExploreCollapsingToolbarLayout"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:minHeight="0dp"
app:title="@string/pick_tings_to_leave"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways"/>
android:layout_height="match_parent"
app:contentScrim="?android:colorBackground"
app:layout_scrollFlags="scroll|exitUntilCollapsed|enterAlways|snap"
app:scrimAnimationDuration="250"
app:scrimVisibleHeightTrigger="120dp"
app:titleEnabled="false"
app:toolbarId="@id/toolbar">
<androidx.appcompat.widget.SearchView
android:id="@+id/publicRoomsFilter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/addRoomToSpaceToolbar"
app:queryHint="@string/search_hint_room_name" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="?attr/actionBarSize"
android:minHeight="0dp"
android:orientation="vertical"
android:paddingHorizontal="16dp">
<TextView
android:id="@+id/spaceLeavePromptTitle"
style="@style/TextAppearance.Vector.Body.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/space_leave_prompt_msg_with_name" />
<TextView
android:id="@+id/spaceLeavePromptDescription"
style="@style/TextAppearance.Vector.Body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/space_leave_prompt_msg_only_you"
android:textColor="?vctr_content_secondary" />
<TextView
android:id="@+id/spaceLeaveRadioButtonsTitle"
style="@style/TextAppearance.Vector.Subtitle.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/space_leave_radio_buttons_title"
android:textAllCaps="true"
android:textColor="?vctr_content_primary" />
<RadioGroup
android:id="@+id/spaceLeaveSelectGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2">
<androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/spaceLeaveSelectAll"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/space_leave_radio_button_all" />
<androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/spaceLeaveSelectNone"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/space_leave_radio_button_none" />
<!-- This view should never be visible! There are three possible states but only two buttons by design.-->
<!-- Third button is needed to make radiogroup work as expected, it's selected, but never shown-->
<androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/spaceLeaveSelectSemi"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone" />
</RadioGroup>
</androidx.appcompat.widget.LinearLayoutCompat>
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:title="Leave space" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:id="@+id/spacePreviewButtonBar"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?vctr_system"
android:background="@color/palette_white"
android:elevation="2dp"
android:orientation="horizontal"
android:padding="8dp">
android:padding="8dp"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior">
<Button
android:id="@+id/spaceLeaveCancel"
@ -68,7 +134,6 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/leave_space" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,13 @@
<?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/menu_space_leave_search"
android:icon="@drawable/ic_filter"
android:title="@string/search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:searchIcon="@drawable/ic_filter"
app:showAsAction="ifRoom|collapseActionView" />
</menu>

View file

@ -2837,14 +2837,24 @@
<string name="space_explore_activity_title">Explore rooms</string>
<string name="space_add_child_title">Add rooms</string>
<string name="leave_space">Leave</string>
<string name="space_leave_radio_buttons_title">Things in this space</string>
<string name="space_leave_radio_button_all">Leave all</string>
<string name="space_leave_radio_button_none">Leave none</string>
<string name="space_leave_prompt_msg_with_name">Are you sure you want to leave %s?</string>
<string name="space_leave_prompt_msg_only_you">You are the only person here. If you leave, no one will be able to join in the future, including you.</string>
<string name="space_leave_prompt_msg_private">You won\'t be able to rejoin unless you are re-invited.</string>
<string name="space_leave_prompt_msg_as_admin">You\'re the only admin of this space. Leaving it will mean no one has control over it.</string>
<string name="leave_all_rooms_and_spaces">Leave all rooms and spaces</string>
<string name="dont_leave_any">Dont leave any rooms and spaces</string>
<string name="leave_specific_ones">Leave specific rooms and spaces…</string>
<string name="pick_tings_to_leave">Pick things to leave</string>
<!-- TODO delete -->
<string name="leave_all_rooms_and_spaces" tools:ignore="UnusedResources">Leave all rooms and spaces</string>
<!-- TODO delete -->
<string name="dont_leave_any" tools:ignore="UnusedResources">Dont leave any rooms and spaces</string>
<!-- TODO delete -->
<string name="leave_specific_ones" tools:ignore="UnusedResources">Leave specific rooms and spaces…</string>
<!-- TODO delete -->
<string name="pick_tings_to_leave" tools:ignore="UnusedResources">Pick things to leave</string>
<string name="space_add_existing_rooms">Add existing rooms and space</string>
<string name="space_add_existing_rooms_only">Add existing rooms</string>