From df7e6bd00dc8acb9290743fabbe643ca753b761b Mon Sep 17 00:00:00 2001 From: David Langley Date: Fri, 23 Jul 2021 14:16:32 +0100 Subject: [PATCH] add radioItem, RoomNotificationSettingsController and render it in fragment --- .../im/vector/app/core/di/FragmentModule.kt | 7 ++ .../vector/app/core/epoxy/RadioButtonItem.kt | 68 +++++++++++++++++++ .../roomprofile/RoomProfileActivity.kt | 18 +++-- .../roomprofile/RoomProfileFragment.kt | 12 +++- .../roomprofile/RoomProfileSharedAction.kt | 1 + .../RoomNotificationSettingsAction.kt | 1 - .../RoomNotificationSettingsController.kt | 62 +++++++++++++++++ .../RoomNotificationSettingsFragment.kt | 40 +++++++---- .../RoomNotificationSettingsViewEvents.kt | 1 - .../RoomNotificationSettingsViewModel.kt | 43 ++++++------ .../RoomNotificationSettingsViewState.kt | 19 ++++-- vector/src/main/res/layout/item_radio.xml | 40 +++++++++++ .../view_edit_room_notification_settings.xml | 21 ++++++ 13 files changed, 281 insertions(+), 52 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/epoxy/RadioButtonItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsController.kt create mode 100644 vector/src/main/res/layout/item_radio.xml diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 8580543022..8020abd43b 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -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.banned.RoomBannedMemberListFragment 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.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment @@ -715,6 +716,12 @@ interface FragmentModule { @FragmentKey(RoomBannedMemberListFragment::class) fun bindRoomBannedMemberListFragment(fragment: RoomBannedMemberListFragment): Fragment + @Binds + @IntoMap + @FragmentKey(RoomNotificationSettingsFragment::class) + fun bindRoomNotificationSettingsFragment(fragment: RoomNotificationSettingsFragment): Fragment + + @Binds @IntoMap @FragmentKey(SearchFragment::class) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/RadioButtonItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/RadioButtonItem.kt new file mode 100644 index 0000000000..e0c7028711 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/epoxy/RadioButtonItem.kt @@ -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() { + + @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(R.id.actionTitle) + val radioImage by bind(R.id.radioIcon) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 07ba442621..d8e080f6a4 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -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.settings.RoomSettingsFragment 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.uploads.RoomUploadsFragment import javax.inject.Inject @@ -107,12 +108,13 @@ class RoomProfileActivity : .observe() .subscribe { sharedAction -> when (sharedAction) { - RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() - RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() - RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias() - RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions() - RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() - RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() + RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() + RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() + RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias() + RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions() + RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() + RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() + RoomProfileSharedAction.OpenRoomNotificaitonSettings -> openRoomNotificationSettings() }.exhaustive } .disposeOnDestroy() @@ -162,6 +164,10 @@ class RoomProfileActivity : addFragmentToBackstack(R.id.simpleFragmentContainer, RoomBannedMemberListFragment::class.java, roomProfileArgs) } + private fun openRoomNotificationSettings() { + addFragmentToBackstack(R.id.simpleFragmentContainer, RoomNotificationSettingsFragment::class.java, roomProfileArgs) + } + override fun configure(toolbar: MaterialToolbar) { configureToolbar(toolbar) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 14ddf896ca..69e05f372d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -30,6 +30,7 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.animations.AppBarStateChangeListener import im.vector.app.core.animations.MatrixItemAppBarStateChangeListener @@ -253,9 +254,14 @@ class RoomProfileFragment @Inject constructor( } override fun onNotificationsClicked() { - RoomListQuickActionsBottomSheet - .newInstance(roomProfileArgs.roomId, RoomListActionsArgs.Mode.NOTIFICATIONS) - .show(childFragmentManager, "ROOM_PROFILE_NOTIFICATIONS") + // TODO: Use BuildConfig here when merged in + if (false) { + RoomListQuickActionsBottomSheet + .newInstance(roomProfileArgs.roomId, RoomListActionsArgs.Mode.NOTIFICATIONS) + .show(childFragmentManager, "ROOM_PROFILE_NOTIFICATIONS") + } else { + roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomNotificaitonSettings) + } } override fun onUploadsClicked() { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index 2a5775d1af..6a9533a2eb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -28,4 +28,5 @@ sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction() + object OpenRoomNotificaitonSettings : RoomProfileSharedAction() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsAction.kt index 9eca3ac5ea..10c8861183 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsAction.kt @@ -21,5 +21,4 @@ import org.matrix.android.sdk.api.session.room.notification.RoomNotificationStat sealed class RoomNotificationSettingsAction : VectorViewModelAction { data class SelectNotificationState(val notificationState: RoomNotificationState): RoomNotificationSettingsAction() - object Save: RoomNotificationSettingsAction() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsController.kt new file mode 100644 index 0000000000..0e8d7cb2c1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsController.kt @@ -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() { + + 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) + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsFragment.kt index 4ee9b60dc2..44ae9a3395 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsFragment.kt @@ -20,40 +20,56 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible import com.airbnb.mvrx.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.databinding.ViewEditRoomNotificationSettingsBinding -import im.vector.app.features.home.AvatarRenderer +import im.vector.app.databinding.FragmentRoomSettingGenericBinding +import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState 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( val roomNotificationSettingsViewModel: RoomNotificationSettingsViewModel.Factory, - private val avatarRenderer: AvatarRenderer, -) : VectorBaseFragment() { + val roomNotificationSettingsController: RoomNotificationSettingsController +) : VectorBaseFragment(), + RoomNotificationSettingsController.Callback { private val viewModel: RoomNotificationSettingsViewModel by fragmentViewModel() - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): ViewEditRoomNotificationSettingsBinding { - return ViewEditRoomNotificationSettingsBinding.inflate(inflater, container, false) + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomSettingGenericBinding { + return FragmentRoomSettingGenericBinding.inflate(inflater, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 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() } + private fun setupWaitingView() { + views.waitingView.waitingStatusText.setText(R.string.please_wait) + views.waitingView.waitingStatusText.isVisible = true + } private fun observeViewEvents() { viewModel.observeViewEvents { when (it) { 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)) + } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewEvents.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewEvents.kt index 1fe1a98340..dda858283b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewEvents.kt @@ -20,5 +20,4 @@ import im.vector.app.core.platform.VectorViewEvents sealed class RoomNotificationSettingsViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : RoomNotificationSettingsViewEvents() - object SaveComplete : RoomNotificationSettingsViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt index d8fcc102d3..c92d4dd422 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt @@ -19,18 +19,19 @@ package im.vector.app.features.roomprofile.notifications import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel 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 class RoomNotificationSettingsViewModel @AssistedInject constructor( @Assisted initialState: RoomNotificationSettingsViewState, - private val room: Room + session: Session ) : VectorViewModel(initialState) { @AssistedFactory @@ -41,50 +42,46 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor( companion object : MvRxViewModelFactory { @JvmStatic - override fun create(viewModelContext: ViewModelContext, state: RoomNotificationSettingsViewState): RoomNotificationSettingsViewModel? { + override fun create(viewModelContext: ViewModelContext, state: RoomNotificationSettingsViewState): RoomNotificationSettingsViewModel { val fragment: RoomNotificationSettingsFragment = (viewModelContext as FragmentViewModelContext).fragment() return fragment.roomNotificationSettingsViewModel.create(state) } } + private val room = session.getRoom(initialState.roomId)!! + init { + initEncrypted() observeNotificationState() } + private fun initEncrypted() { + setState { + copy(roomEncrypted = room.isEncrypted()) + } + } + private fun observeNotificationState() { room.rx() .liveNotificationState() - .subscribe{ - setState { - copy(notificationState = it ) - } + .execute { + copy(notificationState = it) } - .disposeOnClear() } override fun handle(action: RoomNotificationSettingsAction) { when (action) { is RoomNotificationSettingsAction.SelectNotificationState -> handleSelectNotificationState(action) - is RoomNotificationSettingsAction.Save -> handleSaveNotificationSelection(action) } } private fun handleSelectNotificationState(action: RoomNotificationSettingsAction.SelectNotificationState) { - setState { - copy(notificationState = action.notificationState) - } - } - - private fun handleSaveNotificationSelection(action: RoomNotificationSettingsAction.Save) { setState { copy(isLoading = true) } - withState { state -> - viewModelScope.launch { - runCatching { room.setRoomNotificationState(state.notificationState) } - .onFailure { _viewEvents.post(RoomNotificationSettingsViewEvents.Failure(it)) } - setState { - copy(isLoading = false) - } - _viewEvents.post(RoomNotificationSettingsViewEvents.SaveComplete) + viewModelScope.launch { + runCatching { room.setRoomNotificationState(action.notificationState) } + .onFailure { _viewEvents.post(RoomNotificationSettingsViewEvents.Failure(it)) } + setState { + copy(isLoading = false, notificationState = Success(action.notificationState)) } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt index 4844521ca3..f67947ca9f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt @@ -16,15 +16,22 @@ package im.vector.app.features.roomprofile.notifications +import com.airbnb.mvrx.Async 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 data class RoomNotificationSettingsViewState( - val isLoading: Boolean, - val roomEncrypted: Boolean, - val notificationState: RoomNotificationState, - val avatarData: AvatarData? -) : MvRxState + val roomId: String, + val isLoading: Boolean = false, + val roomEncrypted: Boolean = false, + val notificationState: Async = Uninitialized, + val avatarData: AvatarData? = null +) : MvRxState { + + constructor(args: RoomProfileArgs) : this(roomId = args.roomId) +} data class AvatarData ( val displayName: String, @@ -34,7 +41,7 @@ data class AvatarData ( val RoomNotificationSettingsViewState.notificationOptions: List get() { return if (roomEncrypted) { - listOf(RoomNotificationState.ALL_MESSAGES, RoomNotificationState.MUTE) + listOf(RoomNotificationState.ALL_MESSAGES_NOISY, RoomNotificationState.ALL_MESSAGES, RoomNotificationState.MUTE) } else RoomNotificationState.values().asList() } diff --git a/vector/src/main/res/layout/item_radio.xml b/vector/src/main/res/layout/item_radio.xml new file mode 100644 index 0000000000..4cd5312dc0 --- /dev/null +++ b/vector/src/main/res/layout/item_radio.xml @@ -0,0 +1,40 @@ + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/view_edit_room_notification_settings.xml b/vector/src/main/res/layout/view_edit_room_notification_settings.xml index ecb5c742f7..3d4cafc40f 100644 --- a/vector/src/main/res/layout/view_edit_room_notification_settings.xml +++ b/vector/src/main/res/layout/view_edit_room_notification_settings.xml @@ -10,5 +10,26 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + + + + +