add radioItem, RoomNotificationSettingsController and render it in fragment

This commit is contained in:
David Langley 2021-07-23 14:16:32 +01:00
parent 9811d6fefc
commit df7e6bd00d
13 changed files with 281 additions and 52 deletions

View File

@ -106,6 +106,7 @@ import im.vector.app.features.roomprofile.RoomProfileFragment
import im.vector.app.features.roomprofile.alias.RoomAliasFragment import im.vector.app.features.roomprofile.alias.RoomAliasFragment
import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment
import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.members.RoomMemberListFragment
import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment
import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
@ -715,6 +716,12 @@ interface FragmentModule {
@FragmentKey(RoomBannedMemberListFragment::class) @FragmentKey(RoomBannedMemberListFragment::class)
fun bindRoomBannedMemberListFragment(fragment: RoomBannedMemberListFragment): Fragment fun bindRoomBannedMemberListFragment(fragment: RoomBannedMemberListFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomNotificationSettingsFragment::class)
fun bindRoomNotificationSettingsFragment(fragment: RoomNotificationSettingsFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(SearchFragment::class) @FragmentKey(SearchFragment::class)

View File

@ -0,0 +1,68 @@
/*
* 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.ImageView
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
/**
* A action for bottom sheet.
*/
@EpoxyModelClass(layout = R.layout.item_radio)
abstract class RadioButtonItem : VectorEpoxyModel<RadioButtonItem.Holder>() {
@EpoxyAttribute
var title: CharSequence? = null
@StringRes
@EpoxyAttribute
var titleRes: Int? = null
@EpoxyAttribute
var selected = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
lateinit var listener: ClickListener
override fun bind(holder: Holder) {
super.bind(holder)
holder.view.onClick(listener)
if (titleRes != null) {
holder.titleText.setText(titleRes!!)
} else {
holder.titleText.text = title
}
if (selected) {
holder.radioImage.setImageDrawable(ContextCompat.getDrawable(holder.view.context, R.drawable.ic_radio_on))
holder.radioImage.contentDescription = holder.view.context.getString(R.string.a11y_checked)
} else {
holder.radioImage.setImageDrawable(ContextCompat.getDrawable(holder.view.context, R.drawable.ic_radio_off))
holder.radioImage.contentDescription = holder.view.context.getString(R.string.a11y_unchecked)
}
}
class Holder : VectorEpoxyHolder() {
val titleText by bind<TextView>(R.id.actionTitle)
val radioImage by bind<ImageView>(R.id.radioIcon)
}
}

View File

@ -39,6 +39,7 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment
import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.members.RoomMemberListFragment
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
import im.vector.app.features.roomprofile.alias.RoomAliasFragment import im.vector.app.features.roomprofile.alias.RoomAliasFragment
import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment
import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
import javax.inject.Inject import javax.inject.Inject
@ -113,6 +114,7 @@ class RoomProfileActivity :
RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions() RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions()
RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads()
RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers()
RoomProfileSharedAction.OpenRoomNotificaitonSettings -> openRoomNotificationSettings()
}.exhaustive }.exhaustive
} }
.disposeOnDestroy() .disposeOnDestroy()
@ -162,6 +164,10 @@ class RoomProfileActivity :
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomBannedMemberListFragment::class.java, roomProfileArgs) addFragmentToBackstack(R.id.simpleFragmentContainer, RoomBannedMemberListFragment::class.java, roomProfileArgs)
} }
private fun openRoomNotificationSettings() {
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomNotificationSettingsFragment::class.java, roomProfileArgs)
}
override fun configure(toolbar: MaterialToolbar) { override fun configure(toolbar: MaterialToolbar) {
configureToolbar(toolbar) configureToolbar(toolbar)
} }

View File

@ -30,6 +30,7 @@ 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
@ -253,9 +254,14 @@ class RoomProfileFragment @Inject constructor(
} }
override fun onNotificationsClicked() { override fun onNotificationsClicked() {
// TODO: Use BuildConfig here when merged in
if (false) {
RoomListQuickActionsBottomSheet RoomListQuickActionsBottomSheet
.newInstance(roomProfileArgs.roomId, RoomListActionsArgs.Mode.NOTIFICATIONS) .newInstance(roomProfileArgs.roomId, RoomListActionsArgs.Mode.NOTIFICATIONS)
.show(childFragmentManager, "ROOM_PROFILE_NOTIFICATIONS") .show(childFragmentManager, "ROOM_PROFILE_NOTIFICATIONS")
} else {
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomNotificaitonSettings)
}
} }
override fun onUploadsClicked() { override fun onUploadsClicked() {

View File

@ -28,4 +28,5 @@ sealed class RoomProfileSharedAction : VectorSharedAction {
object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction()
object OpenRoomMembers : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction()
object OpenBannedRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction()
object OpenRoomNotificaitonSettings : RoomProfileSharedAction()
} }

View File

@ -21,5 +21,4 @@ import org.matrix.android.sdk.api.session.room.notification.RoomNotificationStat
sealed class RoomNotificationSettingsAction : VectorViewModelAction { sealed class RoomNotificationSettingsAction : VectorViewModelAction {
data class SelectNotificationState(val notificationState: RoomNotificationState): RoomNotificationSettingsAction() data class SelectNotificationState(val notificationState: RoomNotificationState): RoomNotificationSettingsAction()
object Save: RoomNotificationSettingsAction()
} }

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.roomprofile.notifications
import androidx.annotation.StringRes
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.R
import im.vector.app.core.epoxy.radioButtonItem
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
import javax.inject.Inject
class RoomNotificationSettingsController @Inject constructor() : TypedEpoxyController<RoomNotificationSettingsViewState>() {
interface Callback {
fun didSelectRoomNotificationState(roomNotificationState: RoomNotificationState)
}
var callback: Callback? = null
init {
setData(null)
}
@StringRes
private fun titleForNotificationState(notificationState: RoomNotificationState) = when(notificationState) {
RoomNotificationState.ALL_MESSAGES_NOISY -> R.string.room_settings_all_messages_noisy
RoomNotificationState.ALL_MESSAGES -> R.string.room_settings_all_messages
RoomNotificationState.MENTIONS_ONLY -> R.string.room_settings_mention_only
RoomNotificationState.MUTE -> R.string.room_settings_mute
}
override fun buildModels(data: RoomNotificationSettingsViewState?) {
val host = this
data ?: return
data.notificationOptions.forEach { notificationState ->
val title = titleForNotificationState(notificationState)
radioButtonItem {
id(notificationState.name)
titleRes(title)
selected(data.notificationState() == notificationState)
listener {
host.callback?.didSelectRoomNotificationState(notificationState)
}
}
}
}
}

View File

@ -20,40 +20,56 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.ViewEditRoomNotificationSettingsBinding import im.vector.app.databinding.FragmentRoomSettingGenericBinding
import im.vector.app.features.home.AvatarRenderer import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
import javax.inject.Inject import javax.inject.Inject
/**
* In this screen:
* - the account has been created and we propose the user to set an avatar and a display name
*/
class RoomNotificationSettingsFragment @Inject constructor( class RoomNotificationSettingsFragment @Inject constructor(
val roomNotificationSettingsViewModel: RoomNotificationSettingsViewModel.Factory, val roomNotificationSettingsViewModel: RoomNotificationSettingsViewModel.Factory,
private val avatarRenderer: AvatarRenderer, val roomNotificationSettingsController: RoomNotificationSettingsController
) : VectorBaseFragment<ViewEditRoomNotificationSettingsBinding>() { ) : VectorBaseFragment<FragmentRoomSettingGenericBinding>(),
RoomNotificationSettingsController.Callback {
private val viewModel: RoomNotificationSettingsViewModel by fragmentViewModel() private val viewModel: RoomNotificationSettingsViewModel by fragmentViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): ViewEditRoomNotificationSettingsBinding { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomSettingGenericBinding {
return ViewEditRoomNotificationSettingsBinding.inflate(inflater, container, false) return FragmentRoomSettingGenericBinding.inflate(inflater, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupToolbar(views.roomSettingsToolbar)
views.roomSettingsToolbarTitleView.setText(R.string.settings_notifications)
roomNotificationSettingsController.callback = this
views.roomSettingsRecyclerView.configureWith(roomNotificationSettingsController, hasFixedSize = true)
setupWaitingView()
observeViewEvents() observeViewEvents()
} }
private fun setupWaitingView() {
views.waitingView.waitingStatusText.setText(R.string.please_wait)
views.waitingView.waitingStatusText.isVisible = true
}
private fun observeViewEvents() { private fun observeViewEvents() {
viewModel.observeViewEvents { viewModel.observeViewEvents {
when (it) { when (it) {
is RoomNotificationSettingsViewEvents.Failure -> displayErrorDialog(it.throwable) is RoomNotificationSettingsViewEvents.Failure -> displayErrorDialog(it.throwable)
RoomNotificationSettingsViewEvents.SaveComplete -> TODO()
} }
} }
} }
override fun invalidate() = withState(viewModel) { viewState ->
roomNotificationSettingsController.setData(viewState)
views.waitingView.root.isVisible = viewState.isLoading
}
override fun didSelectRoomNotificationState(roomNotificationState: RoomNotificationState) {
viewModel.handle(RoomNotificationSettingsAction.SelectNotificationState(roomNotificationState))
}
} }

View File

@ -20,5 +20,4 @@ import im.vector.app.core.platform.VectorViewEvents
sealed class RoomNotificationSettingsViewEvents : VectorViewEvents { sealed class RoomNotificationSettingsViewEvents : VectorViewEvents {
data class Failure(val throwable: Throwable) : RoomNotificationSettingsViewEvents() data class Failure(val throwable: Throwable) : RoomNotificationSettingsViewEvents()
object SaveComplete : RoomNotificationSettingsViewEvents()
} }

View File

@ -19,18 +19,19 @@ package im.vector.app.features.roomprofile.notifications
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted 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 kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.rx
class RoomNotificationSettingsViewModel @AssistedInject constructor( class RoomNotificationSettingsViewModel @AssistedInject constructor(
@Assisted initialState: RoomNotificationSettingsViewState, @Assisted initialState: RoomNotificationSettingsViewState,
private val room: Room session: Session
) : VectorViewModel<RoomNotificationSettingsViewState, RoomNotificationSettingsAction, RoomNotificationSettingsViewEvents>(initialState) { ) : VectorViewModel<RoomNotificationSettingsViewState, RoomNotificationSettingsAction, RoomNotificationSettingsViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -41,50 +42,46 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor(
companion object : MvRxViewModelFactory<RoomNotificationSettingsViewModel, RoomNotificationSettingsViewState> { companion object : MvRxViewModelFactory<RoomNotificationSettingsViewModel, RoomNotificationSettingsViewState> {
@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 fragment: RoomNotificationSettingsFragment = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.roomNotificationSettingsViewModel.create(state) return fragment.roomNotificationSettingsViewModel.create(state)
} }
} }
private val room = session.getRoom(initialState.roomId)!!
init { init {
initEncrypted()
observeNotificationState() observeNotificationState()
} }
private fun initEncrypted() {
setState {
copy(roomEncrypted = room.isEncrypted())
}
}
private fun observeNotificationState() { private fun observeNotificationState() {
room.rx() room.rx()
.liveNotificationState() .liveNotificationState()
.subscribe{ .execute {
setState { copy(notificationState = it)
copy(notificationState = it )
} }
} }
.disposeOnClear()
}
override fun handle(action: RoomNotificationSettingsAction) { override fun handle(action: RoomNotificationSettingsAction) {
when (action) { when (action) {
is RoomNotificationSettingsAction.SelectNotificationState -> handleSelectNotificationState(action) is RoomNotificationSettingsAction.SelectNotificationState -> handleSelectNotificationState(action)
is RoomNotificationSettingsAction.Save -> handleSaveNotificationSelection(action)
} }
} }
private fun handleSelectNotificationState(action: RoomNotificationSettingsAction.SelectNotificationState) { private fun handleSelectNotificationState(action: RoomNotificationSettingsAction.SelectNotificationState) {
setState {
copy(notificationState = action.notificationState)
}
}
private fun handleSaveNotificationSelection(action: RoomNotificationSettingsAction.Save) {
setState { copy(isLoading = true) } setState { copy(isLoading = true) }
withState { state ->
viewModelScope.launch { viewModelScope.launch {
runCatching { room.setRoomNotificationState(state.notificationState) } runCatching { room.setRoomNotificationState(action.notificationState) }
.onFailure { _viewEvents.post(RoomNotificationSettingsViewEvents.Failure(it)) } .onFailure { _viewEvents.post(RoomNotificationSettingsViewEvents.Failure(it)) }
setState { setState {
copy(isLoading = false) copy(isLoading = false, notificationState = Success(action.notificationState))
}
_viewEvents.post(RoomNotificationSettingsViewEvents.SaveComplete)
} }
} }
} }

View File

@ -16,15 +16,22 @@
package im.vector.app.features.roomprofile.notifications package im.vector.app.features.roomprofile.notifications
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.app.features.roomprofile.RoomProfileArgs
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
data class RoomNotificationSettingsViewState( data class RoomNotificationSettingsViewState(
val isLoading: Boolean, val roomId: String,
val roomEncrypted: Boolean, val isLoading: Boolean = false,
val notificationState: RoomNotificationState, val roomEncrypted: Boolean = false,
val avatarData: AvatarData? val notificationState: Async<RoomNotificationState> = Uninitialized,
) : MvRxState val avatarData: AvatarData? = null
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
}
data class AvatarData ( data class AvatarData (
val displayName: String, val displayName: String,
@ -34,7 +41,7 @@ data class AvatarData (
val RoomNotificationSettingsViewState.notificationOptions: List<RoomNotificationState> val RoomNotificationSettingsViewState.notificationOptions: List<RoomNotificationState>
get() { get() {
return if (roomEncrypted) { return if (roomEncrypted) {
listOf(RoomNotificationState.ALL_MESSAGES, RoomNotificationState.MUTE) listOf(RoomNotificationState.ALL_MESSAGES_NOISY, RoomNotificationState.ALL_MESSAGES, RoomNotificationState.MUTE)
} else } else
RoomNotificationState.values().asList() RoomNotificationState.values().asList()
} }

View File

@ -0,0 +1,40 @@
<?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:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:minHeight="64dp"
android:foreground="?attr/selectableItemBackground"
android:paddingStart="@dimen/layout_horizontal_margin"
android:paddingEnd="@dimen/layout_horizontal_margin">
<TextView
android:id="@+id/actionTitle"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?vctr_content_primary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/radioIcon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/room_settings_all_messages" />
<ImageView
android:id="@+id/radioIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:contentDescription="@string/a11y_checked"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:ignore="MissingPrefix"
tools:src="@drawable/ic_radio_on" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -10,5 +10,26 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<RadioButton
android:id="@+id/allMesssagesRadio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/room_settings_all_messages"
android:textColor="?vctr_content_primary" />
<RadioButton
android:id="@+id/mentionsOnlyRadio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/room_settings_mention_only"
android:textColor="?vctr_content_primary" />
<RadioButton
android:id="@+id/muteRadio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/room_settings_mute"
android:textColor="?vctr_content_primary" />
</RadioGroup> </RadioGroup>
</LinearLayout> </LinearLayout>