implement bottom sheet and error handling

This commit is contained in:
David Langley 2021-07-26 22:54:32 +01:00
parent 42a1ed2abe
commit a76a936e21
9 changed files with 108 additions and 137 deletions

View file

@ -473,9 +473,17 @@ class RoomListFragment @Inject constructor(
// refresh footer // refresh footer
footerController.setData(it) footerController.setData(it)
} }
RoomListQuickActionsBottomSheet val bottomSheet = RoomListQuickActionsBottomSheet
.newInstance(room.roomId, RoomListActionsArgs.Mode.FULL) .newInstance(room.roomId, RoomListActionsArgs.Mode.FULL)
.show(childFragmentManager, "ROOM_LIST_QUICK_ACTIONS") bottomSheet.listener = object : RoomListQuickActionsBottomSheet.Listener {
override fun handleFailure(throwable: Throwable) {
showFailure(throwable)
}
}
bottomSheet.show(childFragmentManager, "ROOM_LIST_QUICK_ACTIONS")
return true return true
} }

View file

@ -22,6 +22,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.core.di.ScreenComponent import im.vector.app.core.di.ScreenComponent
@ -30,7 +31,12 @@ import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.databinding.BottomSheetGenericListBinding import im.vector.app.databinding.BottomSheetGenericListBinding
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsAction
import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsViewEvents
import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsViewModel
import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsViewState
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
import javax.inject.Inject import javax.inject.Inject
@Parcelize @Parcelize
@ -45,6 +51,11 @@ data class RoomListActionsArgs(
} }
} }
data class RoomListQuickActionViewState(
val roomListActionsArgs: RoomListActionsArgs,
val notificationSettingsViewState: RoomNotificationSettingsViewState
)
/** /**
* Bottom sheet fragment that shows room information with list of contextual actions * Bottom sheet fragment that shows room information with list of contextual actions
*/ */
@ -52,15 +63,20 @@ class RoomListQuickActionsBottomSheet :
VectorBaseBottomSheetDialogFragment<BottomSheetGenericListBinding>(), VectorBaseBottomSheetDialogFragment<BottomSheetGenericListBinding>(),
RoomListQuickActionsEpoxyController.Listener { RoomListQuickActionsEpoxyController.Listener {
interface Listener {
fun handleFailure(throwable: Throwable)
}
private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel
@Inject lateinit var sharedViewPool: RecyclerView.RecycledViewPool @Inject lateinit var sharedViewPool: RecyclerView.RecycledViewPool
@Inject lateinit var roomListActionsViewModelFactory: RoomListQuickActionsViewModel.Factory @Inject lateinit var roomNotificationSettingsViewModelFactory: RoomNotificationSettingsViewModel.Factory
@Inject lateinit var roomListActionsEpoxyController: RoomListQuickActionsEpoxyController @Inject lateinit var roomListActionsEpoxyController: RoomListQuickActionsEpoxyController
@Inject lateinit var navigator: Navigator @Inject lateinit var navigator: Navigator
private val viewModel: RoomListQuickActionsViewModel by fragmentViewModel(RoomListQuickActionsViewModel::class) private val roomListActionsArgs: RoomListActionsArgs by args()
private val viewModel: RoomNotificationSettingsViewModel by fragmentViewModel(RoomNotificationSettingsViewModel::class)
override val showExpanded = true override val showExpanded = true
var listener: Listener? = null
override fun injectWith(injector: ScreenComponent) { override fun injectWith(injector: ScreenComponent) {
injector.inject(this) injector.inject(this)
@ -80,6 +96,12 @@ class RoomListQuickActionsBottomSheet :
disableItemAnimation = true disableItemAnimation = true
) )
roomListActionsEpoxyController.listener = this roomListActionsEpoxyController.listener = this
viewModel.observeViewEvents {
when(it){
is RoomNotificationSettingsViewEvents.Failure -> listener?.handleFailure(it.throwable)
}
}
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -89,7 +111,11 @@ class RoomListQuickActionsBottomSheet :
} }
override fun invalidate() = withState(viewModel) { override fun invalidate() = withState(viewModel) {
roomListActionsEpoxyController.setData(it) val roomListViewState = RoomListQuickActionViewState(
roomListActionsArgs,
it
)
roomListActionsEpoxyController.setData(roomListViewState)
super.invalidate() super.invalidate()
} }
@ -103,6 +129,10 @@ class RoomListQuickActionsBottomSheet :
} }
} }
override fun didSelectRoomNotificationState(roomNotificationState: RoomNotificationState) {
viewModel.handle(RoomNotificationSettingsAction.SelectNotificationState(roomNotificationState))
}
companion object { companion object {
fun newInstance(roomId: String, mode: RoomListActionsArgs.Mode): RoomListQuickActionsBottomSheet { fun newInstance(roomId: String, mode: RoomListActionsArgs.Mode): RoomListQuickActionsBottomSheet {
return RoomListQuickActionsBottomSheet().apply { return RoomListQuickActionsBottomSheet().apply {

View file

@ -15,13 +15,18 @@
*/ */
package im.vector.app.features.home.room.list.actions package im.vector.app.features.home.room.list.actions
import androidx.annotation.StringRes
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.R
import im.vector.app.core.epoxy.bottomSheetDividerItem import im.vector.app.core.epoxy.bottomSheetDividerItem
import im.vector.app.core.epoxy.bottomsheet.bottomSheetActionItem import im.vector.app.core.epoxy.bottomsheet.bottomSheetActionItem
import im.vector.app.core.epoxy.bottomsheet.bottomSheetRoomPreviewItem import im.vector.app.core.epoxy.bottomsheet.bottomSheetRoomPreviewItem
import im.vector.app.core.epoxy.profiles.notifications.radioButtonItem
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roomprofile.notifications.notificationOptions
import im.vector.app.features.roomprofile.notifications.notificationStateMapped
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject import javax.inject.Inject
@ -33,16 +38,26 @@ class RoomListQuickActionsEpoxyController @Inject constructor(
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val stringProvider: StringProvider private val stringProvider: StringProvider
) : TypedEpoxyController<RoomListQuickActionsState>() { ) : TypedEpoxyController<RoomListQuickActionViewState>() {
var listener: Listener? = null var listener: Listener? = null
override fun buildModels(state: RoomListQuickActionsState) { @StringRes
val roomSummary = state.roomSummary() ?: return private fun titleForNotificationState(notificationState: RoomNotificationState): Int? = when(notificationState) {
val host = this RoomNotificationState.ALL_MESSAGES_NOISY -> R.string.room_settings_all_messages
val showAll = state.mode == RoomListActionsArgs.Mode.FULL RoomNotificationState.MENTIONS_ONLY -> R.string.room_settings_mention_and_keyword_only
RoomNotificationState.MUTE -> R.string.room_settings_none
else -> null
}
if (showAll) { override fun buildModels(state: RoomListQuickActionViewState) {
val notificationViewState = state.notificationSettingsViewState
val roomSummary = notificationViewState.roomSummary() ?: return
val host = this
val showFull = state.roomListActionsArgs.mode == RoomListActionsArgs.Mode.FULL
var isV2 = true
if (showFull || isV2) {
// Preview, favorite, settings // Preview, favorite, settings
bottomSheetRoomPreviewItem { bottomSheetRoomPreviewItem {
id("room_preview") id("room_preview")
@ -63,15 +78,30 @@ class RoomListQuickActionsEpoxyController @Inject constructor(
} }
} }
val selectedRoomState = state.roomNotificationState() if (isV2) {
RoomListQuickActionsSharedAction.NotificationsAllNoisy(roomSummary.roomId).toBottomSheetItem(0, selectedRoomState) notificationViewState.notificationOptions.forEach { notificationState ->
RoomListQuickActionsSharedAction.NotificationsAll(roomSummary.roomId).toBottomSheetItem(1, selectedRoomState) val title = titleForNotificationState(notificationState)
RoomListQuickActionsSharedAction.NotificationsMentionsOnly(roomSummary.roomId).toBottomSheetItem(2, selectedRoomState) radioButtonItem {
RoomListQuickActionsSharedAction.NotificationsMute(roomSummary.roomId).toBottomSheetItem(3, selectedRoomState) id(notificationState.name)
titleRes(title)
if (showAll) { selected(notificationViewState.notificationStateMapped() == notificationState)
RoomListQuickActionsSharedAction.Leave(roomSummary.roomId).toBottomSheetItem(5) listener {
host.listener?.didSelectRoomNotificationState(notificationState)
}
}
}
} else {
val selectedRoomState = notificationViewState.notificationState()
RoomListQuickActionsSharedAction.NotificationsAllNoisy(roomSummary.roomId).toBottomSheetItem(0, selectedRoomState)
RoomListQuickActionsSharedAction.NotificationsAll(roomSummary.roomId).toBottomSheetItem(1, selectedRoomState)
RoomListQuickActionsSharedAction.NotificationsMentionsOnly(roomSummary.roomId).toBottomSheetItem(2, selectedRoomState)
RoomListQuickActionsSharedAction.NotificationsMute(roomSummary.roomId).toBottomSheetItem(3, selectedRoomState)
} }
if (showFull || isV2) {
RoomListQuickActionsSharedAction.Leave(roomSummary.roomId, showIcon = !isV2).toBottomSheetItem(5)
}
} }
private fun RoomListQuickActionsSharedAction.toBottomSheetItem(index: Int, roomNotificationState: RoomNotificationState? = null) { private fun RoomListQuickActionsSharedAction.toBottomSheetItem(index: Int, roomNotificationState: RoomNotificationState? = null) {
@ -86,7 +116,11 @@ class RoomListQuickActionsEpoxyController @Inject constructor(
return bottomSheetActionItem { return bottomSheetActionItem {
id("action_$index") id("action_$index")
selected(selected) selected(selected)
iconRes(iconResId) if(iconResId != null){
iconRes(iconResId)
} else{
showIcon(false)
}
textRes(titleRes) textRes(titleRes)
destructive(this@toBottomSheetItem.destructive) destructive(this@toBottomSheetItem.destructive)
listener { host.listener?.didSelectMenuAction(this@toBottomSheetItem) } listener { host.listener?.didSelectMenuAction(this@toBottomSheetItem) }
@ -95,5 +129,6 @@ class RoomListQuickActionsEpoxyController @Inject constructor(
interface Listener { interface Listener {
fun didSelectMenuAction(quickAction: RoomListQuickActionsSharedAction) fun didSelectMenuAction(quickAction: RoomListQuickActionsSharedAction)
fun didSelectRoomNotificationState(roomNotificationState: RoomNotificationState)
} }
} }

View file

@ -21,9 +21,10 @@ import androidx.annotation.StringRes
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.platform.VectorSharedAction import im.vector.app.core.platform.VectorSharedAction
sealed class RoomListQuickActionsSharedAction( sealed class RoomListQuickActionsSharedAction(
@StringRes val titleRes: Int, @StringRes val titleRes: Int,
@DrawableRes val iconResId: Int, @DrawableRes val iconResId: Int?,
val destructive: Boolean = false) val destructive: Boolean = false)
: VectorSharedAction { : VectorSharedAction {
@ -60,9 +61,9 @@ sealed class RoomListQuickActionsSharedAction(
R.string.room_list_quick_actions_favorite_add, R.string.room_list_quick_actions_favorite_add,
R.drawable.ic_star_24dp) R.drawable.ic_star_24dp)
data class Leave(val roomId: String) : RoomListQuickActionsSharedAction( data class Leave(val roomId: String, val showIcon: Boolean=true) : RoomListQuickActionsSharedAction(
R.string.room_list_quick_actions_leave, R.string.room_list_quick_actions_leave,
R.drawable.ic_room_actions_leave, if (showIcon) R.drawable.ic_room_actions_leave else null,
true true
) )
} }

View file

@ -1,33 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.list.actions
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.notification.RoomNotificationState
data class RoomListQuickActionsState(
val roomId: String,
val mode: RoomListActionsArgs.Mode,
val roomSummary: Async<RoomSummary> = Uninitialized,
val roomNotificationState: Async<RoomNotificationState> = Uninitialized
) : MvRxState {
constructor(args: RoomListActionsArgs) : this(roomId = args.roomId, mode = args.mode)
}

View file

@ -1,78 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.list.actions
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.rx.unwrap
class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initialState: RoomListQuickActionsState,
session: Session
) : VectorViewModel<RoomListQuickActionsState, EmptyAction, EmptyViewEvents>(initialState) {
@AssistedFactory
interface Factory {
fun create(initialState: RoomListQuickActionsState): RoomListQuickActionsViewModel
}
companion object : MvRxViewModelFactory<RoomListQuickActionsViewModel, RoomListQuickActionsState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomListQuickActionsState): RoomListQuickActionsViewModel? {
val fragment: RoomListQuickActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.roomListActionsViewModelFactory.create(state)
}
}
private val room = session.getRoom(initialState.roomId)!!
init {
observeRoomSummary()
observeNotificationState()
}
private fun observeNotificationState() {
room
.rx()
.liveNotificationState()
.execute {
copy(roomNotificationState = it)
}
}
private fun observeRoomSummary() {
room
.rx()
.liveRoomSummary()
.unwrap()
.execute {
copy(roomSummary = it)
}
}
override fun handle(action: EmptyAction) {
// No op
}
}

View file

@ -30,7 +30,6 @@ import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.BuildConfig
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.animations.AppBarStateChangeListener import im.vector.app.core.animations.AppBarStateChangeListener
import im.vector.app.core.animations.MatrixItemAppBarStateChangeListener import im.vector.app.core.animations.MatrixItemAppBarStateChangeListener

View file

@ -25,6 +25,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.rx
@ -44,8 +45,14 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor(
@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomNotificationSettingsViewState): RoomNotificationSettingsViewModel { override fun create(viewModelContext: ViewModelContext, state: RoomNotificationSettingsViewState): RoomNotificationSettingsViewModel {
val fragment: RoomNotificationSettingsFragment = (viewModelContext as FragmentViewModelContext).fragment() val fragmentModelContext = (viewModelContext as FragmentViewModelContext)
return fragment.viewModelFactory.create(state) return if (fragmentModelContext.fragment is RoomNotificationSettingsFragment) {
val fragment: RoomNotificationSettingsFragment = fragmentModelContext.fragment()
fragment.viewModelFactory.create(state)
} else {
val fragment: RoomListQuickActionsBottomSheet = fragmentModelContext.fragment()
fragment.roomNotificationSettingsViewModelFactory.create(state)
}
} }
} }

View file

@ -20,6 +20,7 @@ import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.RoomProfileArgs
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
@ -32,6 +33,7 @@ data class RoomNotificationSettingsViewState(
val notificationState: Async<RoomNotificationState> = Uninitialized, val notificationState: Async<RoomNotificationState> = Uninitialized,
) : MvRxState { ) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId) constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
constructor(args: RoomListActionsArgs) : this(roomId = args.roomId)
} }
data class AvatarData ( data class AvatarData (