diff --git a/changelog.d/8007.feature b/changelog.d/8007.feature new file mode 100644 index 0000000000..0ae4cc49af --- /dev/null +++ b/changelog.d/8007.feature @@ -0,0 +1 @@ +[Poll] Synchronize polls push rules with message push rules diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt index 4f35fb79c3..a3755f85b7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt @@ -47,8 +47,36 @@ object RuleIds { const val RULE_ID_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message" const val RULE_ID_ENCRYPTED = ".m.rule.encrypted" + const val RULE_ID_POLL_START_ONE_TO_ONE = ".m.rule.poll_start_one_to_one" + const val RULE_ID_POLL_START_ONE_TO_ONE_UNSTABLE = ".org.matrix.msc3930.rule.poll_start_one_to_one" + const val RULE_ID_POLL_END_ONE_TO_ONE = ".m.rule.poll_end_one_to_one" + const val RULE_ID_POLL_END_ONE_TO_ONE_UNSTABLE = ".org.matrix.msc3930.rule.poll_end_one_to_one" + + const val RULE_ID_POLL_START = ".m.rule.poll_start" + const val RULE_ID_POLL_START_UNSTABLE = ".org.matrix.msc3930.rule.poll_start" + const val RULE_ID_POLL_END = ".m.rule.poll_end" + const val RULE_ID_POLL_END_UNSTABLE = ".org.matrix.msc3930.rule.poll_end" + // Not documented const val RULE_ID_FALLBACK = ".m.rule.fallback" const val RULE_ID_REACTION = ".m.rule.reaction" + + fun getSyncedRules(ruleId: String): List<String> { + return when (ruleId) { + RULE_ID_ONE_TO_ONE_ROOM -> listOf( + RULE_ID_POLL_START_ONE_TO_ONE, + RULE_ID_POLL_START_ONE_TO_ONE_UNSTABLE, + RULE_ID_POLL_END_ONE_TO_ONE, + RULE_ID_POLL_END_ONE_TO_ONE_UNSTABLE, + ) + RULE_ID_ALL_OTHER_MESSAGES_ROOMS -> listOf( + RULE_ID_POLL_START, + RULE_ID_POLL_START_UNSTABLE, + RULE_ID_POLL_END, + RULE_ID_POLL_END_UNSTABLE, + ) + else -> emptyList() + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt index 9498ed002c..9287a7828d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt @@ -47,21 +47,14 @@ data class RuleSet( * @param ruleId a RULE_ID_XX value * @return the matched bing rule or null it doesn't exist. */ - fun findDefaultRule(ruleId: String?): PushRuleAndKind? { - var result: PushRuleAndKind? = null - // sanity check - if (null != ruleId) { - if (RuleIds.RULE_ID_CONTAIN_USER_NAME == ruleId) { - result = findRule(content, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.CONTENT) } - } else { - // assume that the ruleId is unique. - result = findRule(override, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.OVERRIDE) } - if (null == result) { - result = findRule(underride, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.UNDERRIDE) } - } - } + fun findDefaultRule(ruleId: String): PushRuleAndKind? { + return if (RuleIds.RULE_ID_CONTAIN_USER_NAME == ruleId) { + findRule(content, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.CONTENT) } + } else { + // assume that the ruleId is unique. + findRule(override, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.OVERRIDE) } + ?: findRule(underride, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.UNDERRIDE) } } - return result } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt index 9fe93d8262..3dfac694ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt @@ -57,6 +57,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor( val allEvents = (newJoinEvents + inviteEvents).filter { event -> when (event.type) { in EventType.POLL_START.values, + in EventType.POLL_END.values, in EventType.STATE_ROOM_BEACON_INFO.values, EventType.MESSAGE, EventType.REDACTION, diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index c2e2f9f695..35d8d0e896 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -108,7 +108,8 @@ import im.vector.app.features.settings.ignored.IgnoredUsersViewModel import im.vector.app.features.settings.labs.VectorSettingsLabsViewModel import im.vector.app.features.settings.legals.LegalsViewModel import im.vector.app.features.settings.locale.LocalePickerViewModel -import im.vector.app.features.settings.notifications.VectorSettingsNotificationPreferenceViewModel +import im.vector.app.features.settings.notifications.VectorSettingsNotificationViewModel +import im.vector.app.features.settings.notifications.VectorSettingsPushRuleNotificationViewModel import im.vector.app.features.settings.push.PushGatewaysViewModel import im.vector.app.features.settings.threepids.ThreePidsSettingsViewModel import im.vector.app.features.share.IncomingShareViewModel @@ -690,9 +691,16 @@ interface MavericksViewModelModule { @Binds @IntoMap - @MavericksViewModelKey(VectorSettingsNotificationPreferenceViewModel::class) + @MavericksViewModelKey(VectorSettingsNotificationViewModel::class) fun vectorSettingsNotificationPreferenceViewModelFactory( - factory: VectorSettingsNotificationPreferenceViewModel.Factory + factory: VectorSettingsNotificationViewModel.Factory + ): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(VectorSettingsPushRuleNotificationViewModel::class) + fun vectorSettingsPushRuleNotificationPreferenceViewModelFactory( + factory: VectorSettingsPushRuleNotificationViewModel.Factory ): MavericksAssistedViewModelFactory<*, *> @Binds diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index a69958ef25..14a36d9922 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -67,7 +67,7 @@ class NotifiableEventResolver @Inject constructor( ) { private val nonEncryptedNotifiableEventTypes: List<String> = - listOf(EventType.MESSAGE) + EventType.POLL_START.values + EventType.STATE_ROOM_BEACON_INFO.values + listOf(EventType.MESSAGE) + EventType.POLL_START.values + EventType.POLL_END.values + EventType.STATE_ROOM_BEACON_INFO.values suspend fun resolveEvent(event: Event, session: Session, isNoisy: Boolean): NotifiableEvent? { val roomID = event.roomId ?: return null diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt index 4a9db49c67..33557fabef 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt @@ -32,7 +32,7 @@ import im.vector.app.databinding.ActivityVectorSettingsBinding import im.vector.app.features.discovery.DiscoverySettingsFragment import im.vector.app.features.navigation.SettingsActivityPayload import im.vector.app.features.settings.devices.VectorSettingsDevicesFragment -import im.vector.app.features.settings.notifications.VectorSettingsNotificationPreferenceFragment +import im.vector.app.features.settings.notifications.VectorSettingsNotificationFragment import im.vector.app.features.settings.threepids.ThreePidsSettingsFragment import im.vector.lib.core.utils.compat.getParcelableExtraCompat import org.matrix.android.sdk.api.failure.GlobalError @@ -92,7 +92,7 @@ class VectorSettingsActivity : VectorBaseActivity<ActivityVectorSettingsBinding> } SettingsActivityPayload.Notifications -> { requestHighlightPreferenceKeyOnResume(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY) - replaceFragment(views.vectorSettingsPage, VectorSettingsNotificationPreferenceFragment::class.java, null, FRAGMENT_TAG) + replaceFragment(views.vectorSettingsPage, VectorSettingsNotificationFragment::class.java, null, FRAGMENT_TAG) } is SettingsActivityPayload.DiscoverySettings -> { replaceFragment(views.vectorSettingsPage, DiscoverySettingsFragment::class.java, payload, FRAGMENT_TAG) diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/PushRuleDefinitions.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/PushRuleDefinitions.kt index 264da7c4a3..fff6ffe933 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/PushRuleDefinitions.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/PushRuleDefinitions.kt @@ -38,7 +38,12 @@ fun getStandardAction(ruleId: String, index: NotificationIndex): StandardActions NotificationIndex.SILENT -> StandardActions.Notify NotificationIndex.NOISY -> StandardActions.Highlight } - RuleIds.RULE_ID_ONE_TO_ONE_ROOM -> + RuleIds.RULE_ID_ONE_TO_ONE_ROOM, + RuleIds.RULE_ID_POLL_START_ONE_TO_ONE, + RuleIds.RULE_ID_POLL_START_ONE_TO_ONE_UNSTABLE, + RuleIds.RULE_ID_POLL_END_ONE_TO_ONE, + RuleIds.RULE_ID_POLL_END_ONE_TO_ONE_UNSTABLE, + -> when (index) { NotificationIndex.OFF -> StandardActions.DontNotify NotificationIndex.SILENT -> StandardActions.Notify @@ -50,7 +55,11 @@ fun getStandardAction(ruleId: String, index: NotificationIndex): StandardActions NotificationIndex.SILENT -> StandardActions.Notify NotificationIndex.NOISY -> StandardActions.NotifyDefaultSound } - RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS -> + RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS, + RuleIds.RULE_ID_POLL_START, + RuleIds.RULE_ID_POLL_START_UNSTABLE, + RuleIds.RULE_ID_POLL_END, + RuleIds.RULE_ID_POLL_END_UNSTABLE -> when (index) { NotificationIndex.OFF -> StandardActions.DontNotify NotificationIndex.SILENT -> StandardActions.Notify diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationFragment.kt similarity index 95% rename from vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt rename to vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationFragment.kt index 490a47ef61..dab7a9ed3a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationFragment.kt @@ -71,7 +71,7 @@ import javax.inject.Inject // Referenced in vector_settings_preferences_root.xml @AndroidEntryPoint -class VectorSettingsNotificationPreferenceFragment : +class VectorSettingsNotificationFragment : VectorSettingsBaseFragment(), BackgroundSyncModeChooserDialog.InteractionListener { @@ -90,7 +90,7 @@ class VectorSettingsNotificationPreferenceFragment : private var interactionListener: VectorSettingsFragmentInteractionListener? = null - private val viewModel: VectorSettingsNotificationPreferenceViewModel by fragmentViewModel() + private val viewModel: VectorSettingsNotificationViewModel by fragmentViewModel() private val notificationStartForActivityResult = registerStartForActivityResult { _ -> // No op @@ -116,10 +116,10 @@ class VectorSettingsNotificationPreferenceFragment : private fun observeViewEvents() { viewModel.observeViewEvents { when (it) { - VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceEnabled -> onNotificationsForDeviceEnabled() - VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceDisabled -> onNotificationsForDeviceDisabled() - is VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor -> askUserToSelectPushDistributor() - VectorSettingsNotificationPreferenceViewEvent.NotificationMethodChanged -> onNotificationMethodChanged() + VectorSettingsNotificationViewEvent.NotificationsForDeviceEnabled -> onNotificationsForDeviceEnabled() + VectorSettingsNotificationViewEvent.NotificationsForDeviceDisabled -> onNotificationsForDeviceDisabled() + is VectorSettingsNotificationViewEvent.AskUserForPushDistributor -> askUserToSelectPushDistributor() + VectorSettingsNotificationViewEvent.NotificationMethodChanged -> onNotificationMethodChanged() } } } @@ -143,9 +143,9 @@ class VectorSettingsNotificationPreferenceFragment : findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY) ?.setOnPreferenceChangeListener { _, isChecked -> val action = if (isChecked as Boolean) { - VectorSettingsNotificationPreferenceViewAction.EnableNotificationsForDevice(pushDistributor = "") + VectorSettingsNotificationViewAction.EnableNotificationsForDevice(pushDistributor = "") } else { - VectorSettingsNotificationPreferenceViewAction.DisableNotificationsForDevice + VectorSettingsNotificationViewAction.DisableNotificationsForDevice } viewModel.handle(action) // preference will be updated on ViewEvent reception @@ -231,9 +231,9 @@ class VectorSettingsNotificationPreferenceFragment : private fun askUserToSelectPushDistributor(withUnregister: Boolean = false) { unifiedPushHelper.showSelectDistributorDialog(requireContext()) { selection -> if (withUnregister) { - viewModel.handle(VectorSettingsNotificationPreferenceViewAction.RegisterPushDistributor(selection)) + viewModel.handle(VectorSettingsNotificationViewAction.RegisterPushDistributor(selection)) } else { - viewModel.handle(VectorSettingsNotificationPreferenceViewAction.EnableNotificationsForDevice(selection)) + viewModel.handle(VectorSettingsNotificationViewAction.EnableNotificationsForDevice(selection)) } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewAction.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationViewAction.kt similarity index 80% rename from vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewAction.kt rename to vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationViewAction.kt index 949dc99993..4257f0a72b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewAction.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationViewAction.kt @@ -18,8 +18,8 @@ package im.vector.app.features.settings.notifications import im.vector.app.core.platform.VectorViewModelAction -sealed interface VectorSettingsNotificationPreferenceViewAction : VectorViewModelAction { - data class EnableNotificationsForDevice(val pushDistributor: String) : VectorSettingsNotificationPreferenceViewAction - object DisableNotificationsForDevice : VectorSettingsNotificationPreferenceViewAction - data class RegisterPushDistributor(val pushDistributor: String) : VectorSettingsNotificationPreferenceViewAction +sealed interface VectorSettingsNotificationViewAction : VectorViewModelAction { + data class EnableNotificationsForDevice(val pushDistributor: String) : VectorSettingsNotificationViewAction + object DisableNotificationsForDevice : VectorSettingsNotificationViewAction + data class RegisterPushDistributor(val pushDistributor: String) : VectorSettingsNotificationViewAction } diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewEvent.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationViewEvent.kt similarity index 84% rename from vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewEvent.kt rename to vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationViewEvent.kt index b0ee107769..dd284a8bb1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationViewEvent.kt @@ -18,9 +18,9 @@ package im.vector.app.features.settings.notifications import im.vector.app.core.platform.VectorViewEvents -sealed interface VectorSettingsNotificationPreferenceViewEvent : VectorViewEvents { - object NotificationsForDeviceEnabled : VectorSettingsNotificationPreferenceViewEvent - object NotificationsForDeviceDisabled : VectorSettingsNotificationPreferenceViewEvent - object AskUserForPushDistributor : VectorSettingsNotificationPreferenceViewEvent - object NotificationMethodChanged : VectorSettingsNotificationPreferenceViewEvent +sealed interface VectorSettingsNotificationViewEvent : VectorViewEvents { + object NotificationsForDeviceEnabled : VectorSettingsNotificationViewEvent + object NotificationsForDeviceDisabled : VectorSettingsNotificationViewEvent + object AskUserForPushDistributor : VectorSettingsNotificationViewEvent + object NotificationMethodChanged : VectorSettingsNotificationViewEvent } diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationViewModel.kt similarity index 74% rename from vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt rename to vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationViewModel.kt index 9530be599e..c980288d76 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationViewModel.kt @@ -31,9 +31,12 @@ import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.RegisterUnifiedPushUseCase import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.settings.notifications.usecase.DisableNotificationsForCurrentSessionUseCase +import im.vector.app.features.settings.notifications.usecase.EnableNotificationsForCurrentSessionUseCase +import im.vector.app.features.settings.notifications.usecase.ToggleNotificationsForCurrentSessionUseCase import kotlinx.coroutines.launch -class VectorSettingsNotificationPreferenceViewModel @AssistedInject constructor( +class VectorSettingsNotificationViewModel @AssistedInject constructor( @Assisted initialState: VectorDummyViewState, private val pushersManager: PushersManager, private val vectorPreferences: VectorPreferences, @@ -43,23 +46,23 @@ class VectorSettingsNotificationPreferenceViewModel @AssistedInject constructor( private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase, private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase, private val toggleNotificationsForCurrentSessionUseCase: ToggleNotificationsForCurrentSessionUseCase, -) : VectorViewModel<VectorDummyViewState, VectorSettingsNotificationPreferenceViewAction, VectorSettingsNotificationPreferenceViewEvent>(initialState) { +) : VectorViewModel<VectorDummyViewState, VectorSettingsNotificationViewAction, VectorSettingsNotificationViewEvent>(initialState) { @AssistedFactory - interface Factory : MavericksAssistedViewModelFactory<VectorSettingsNotificationPreferenceViewModel, VectorDummyViewState> { - override fun create(initialState: VectorDummyViewState): VectorSettingsNotificationPreferenceViewModel + interface Factory : MavericksAssistedViewModelFactory<VectorSettingsNotificationViewModel, VectorDummyViewState> { + override fun create(initialState: VectorDummyViewState): VectorSettingsNotificationViewModel } - companion object : MavericksViewModelFactory<VectorSettingsNotificationPreferenceViewModel, VectorDummyViewState> by hiltMavericksViewModelFactory() + companion object : MavericksViewModelFactory<VectorSettingsNotificationViewModel, VectorDummyViewState> by hiltMavericksViewModelFactory() @VisibleForTesting val notificationsPreferenceListener: SharedPreferences.OnSharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> if (key == VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY) { if (vectorPreferences.areNotificationEnabledForDevice()) { - _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceEnabled) + _viewEvents.post(VectorSettingsNotificationViewEvent.NotificationsForDeviceEnabled) } else { - _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceDisabled) + _viewEvents.post(VectorSettingsNotificationViewEvent.NotificationsForDeviceDisabled) } } } @@ -77,18 +80,18 @@ class VectorSettingsNotificationPreferenceViewModel @AssistedInject constructor( super.onCleared() } - override fun handle(action: VectorSettingsNotificationPreferenceViewAction) { + override fun handle(action: VectorSettingsNotificationViewAction) { when (action) { - VectorSettingsNotificationPreferenceViewAction.DisableNotificationsForDevice -> handleDisableNotificationsForDevice() - is VectorSettingsNotificationPreferenceViewAction.EnableNotificationsForDevice -> handleEnableNotificationsForDevice(action.pushDistributor) - is VectorSettingsNotificationPreferenceViewAction.RegisterPushDistributor -> handleRegisterPushDistributor(action.pushDistributor) + VectorSettingsNotificationViewAction.DisableNotificationsForDevice -> handleDisableNotificationsForDevice() + is VectorSettingsNotificationViewAction.EnableNotificationsForDevice -> handleEnableNotificationsForDevice(action.pushDistributor) + is VectorSettingsNotificationViewAction.RegisterPushDistributor -> handleRegisterPushDistributor(action.pushDistributor) } } private fun handleDisableNotificationsForDevice() { viewModelScope.launch { disableNotificationsForCurrentSessionUseCase.execute() - _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceDisabled) + _viewEvents.post(VectorSettingsNotificationViewEvent.NotificationsForDeviceDisabled) } } @@ -96,10 +99,10 @@ class VectorSettingsNotificationPreferenceViewModel @AssistedInject constructor( viewModelScope.launch { when (enableNotificationsForCurrentSessionUseCase.execute(distributor)) { is EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.NeedToAskUserForDistributor -> { - _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor) + _viewEvents.post(VectorSettingsNotificationViewEvent.AskUserForPushDistributor) } EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.Success -> { - _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceEnabled) + _viewEvents.post(VectorSettingsNotificationViewEvent.NotificationsForDeviceEnabled) } } } @@ -110,13 +113,13 @@ class VectorSettingsNotificationPreferenceViewModel @AssistedInject constructor( unregisterUnifiedPushUseCase.execute(pushersManager) when (registerUnifiedPushUseCase.execute(distributor)) { RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor -> { - _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor) + _viewEvents.post(VectorSettingsNotificationViewEvent.AskUserForPushDistributor) } RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success -> { val areNotificationsEnabled = vectorPreferences.areNotificationEnabledForDevice() ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = areNotificationsEnabled) toggleNotificationsForCurrentSessionUseCase.execute(enabled = areNotificationsEnabled) - _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.NotificationMethodChanged) + _viewEvents.post(VectorSettingsNotificationViewEvent.NotificationMethodChanged) } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationFragment.kt new file mode 100644 index 0000000000..0f991867fe --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationFragment.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023 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.settings.notifications + +import android.os.Bundle +import android.view.View +import androidx.preference.Preference +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.core.preference.VectorCheckboxPreference +import im.vector.app.features.settings.VectorSettingsBaseFragment + +abstract class VectorSettingsPushRuleNotificationFragment : + VectorSettingsBaseFragment() { + + private val viewModel: VectorSettingsPushRuleNotificationViewModel by fragmentViewModel() + + abstract val prefKeyToPushRuleId: Map<String, String> + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeViewEvents() + viewModel.onEach(VectorSettingsPushRuleNotificationViewState::isLoading) { isLoading -> + if (isLoading) { + displayLoadingView() + } else { + hideLoadingView() + } + } + } + + private fun observeViewEvents() { + viewModel.observeViewEvents { + when (it) { + is VectorSettingsPushRuleNotificationViewEvent.Failure -> refreshDisplay() + is VectorSettingsPushRuleNotificationViewEvent.PushRuleUpdated -> updatePreference(it.ruleId, it.checked) + } + } + } + + override fun bindPref() { + for (preferenceKey in prefKeyToPushRuleId.keys) { + val preference = findPreference<VectorCheckboxPreference>(preferenceKey)!! + preference.isIconSpaceReserved = false + val ruleAndKind = prefKeyToPushRuleId[preferenceKey]?.let { viewModel.getPushRuleAndKind(it) } + if (ruleAndKind == null) { + // The rule is not defined, hide the preference + preference.isVisible = false + } else { + preference.isVisible = true + updatePreference(ruleAndKind.pushRule.ruleId, viewModel.isPushRuleChecked(ruleAndKind.pushRule.ruleId)) + preference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + viewModel.handle(VectorSettingsPushRuleNotificationViewAction.UpdatePushRule(ruleAndKind, newValue as Boolean)) + false + } + } + } + } + + override fun invalidate() = withState(viewModel) { state -> + if (state.isLoading) { + displayLoadingView() + } else { + hideLoadingView() + } + } + + protected fun refreshDisplay() { + listView?.adapter?.notifyDataSetChanged() + } + + private fun updatePreference(ruleId: String, checked: Boolean) { + val preferenceKey = prefKeyToPushRuleId.entries.find { it.value == ruleId }?.key ?: return + val preference = findPreference<VectorCheckboxPreference>(preferenceKey) ?: return + preference.isChecked = checked + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationPreferenceFragment.kt deleted file mode 100644 index 7f856298ea..0000000000 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationPreferenceFragment.kt +++ /dev/null @@ -1,86 +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.settings.notifications - -import androidx.lifecycle.lifecycleScope -import androidx.preference.Preference -import im.vector.app.core.preference.VectorCheckboxPreference -import im.vector.app.features.settings.VectorSettingsBaseFragment -import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.session.pushrules.RuleKind -import org.matrix.android.sdk.api.session.pushrules.rest.PushRuleAndKind - -abstract class VectorSettingsPushRuleNotificationPreferenceFragment : - VectorSettingsBaseFragment() { - - abstract val prefKeyToPushRuleId: Map<String, String> - - override fun bindPref() { - for (preferenceKey in prefKeyToPushRuleId.keys) { - val preference = findPreference<VectorCheckboxPreference>(preferenceKey)!! - preference.isIconSpaceReserved = false - val ruleAndKind: PushRuleAndKind? = session.pushRuleService().getPushRules().findDefaultRule(prefKeyToPushRuleId[preferenceKey]) - if (ruleAndKind == null) { - // The rule is not defined, hide the preference - preference.isVisible = false - } else { - preference.isVisible = true - val initialIndex = ruleAndKind.pushRule.notificationIndex - preference.isChecked = initialIndex != NotificationIndex.OFF - preference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - updatePushRule(ruleAndKind.pushRule.ruleId, ruleAndKind.kind, newValue as Boolean, preference) - false - } - } - } - } - - fun updatePushRule(ruleId: String, kind: RuleKind, checked: Boolean, preference: VectorCheckboxPreference) { - val newIndex = if (checked) NotificationIndex.NOISY else NotificationIndex.OFF - val standardAction = getStandardAction(ruleId, newIndex) ?: return - val enabled = standardAction != StandardActions.Disabled - val newActions = standardAction.actions - displayLoadingView() - - lifecycleScope.launch { - val result = runCatching { - session.pushRuleService().updatePushRuleActions( - kind, - ruleId, - enabled, - newActions - ) - } - hideLoadingView() - if (!isAdded) { - return@launch - } - result.onSuccess { - preference.isChecked = checked - } - result.onFailure { failure -> - // Restore the previous value - refreshDisplay() - displayErrorDialog(failure) - } - } - } - - fun refreshDisplay() { - listView?.adapter?.notifyDataSetChanged() - } -} diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewAction.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewAction.kt new file mode 100644 index 0000000000..61bf7c5b15 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewAction.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023 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.settings.notifications + +import im.vector.app.core.platform.VectorViewModelAction +import org.matrix.android.sdk.api.session.pushrules.rest.PushRuleAndKind + +sealed interface VectorSettingsPushRuleNotificationViewAction : VectorViewModelAction { + data class UpdatePushRule(val pushRuleAndKind: PushRuleAndKind, val checked: Boolean) : VectorSettingsPushRuleNotificationViewAction +} diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewEvent.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewEvent.kt new file mode 100644 index 0000000000..adfc17f827 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewEvent.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 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.settings.notifications + +import im.vector.app.core.platform.VectorViewEvents + +sealed interface VectorSettingsPushRuleNotificationViewEvent : VectorViewEvents { + /** + * A global push rule checked state has changed. + * + * @property ruleId the global rule id which has been updated. + * @property checked whether the global rule is checked. + * @property failure whether there has been a failure when updating the global rule (ie. a sub rule has not been updated). + */ + data class PushRuleUpdated(val ruleId: String, val checked: Boolean, val failure: Throwable? = null) : VectorSettingsPushRuleNotificationViewEvent + + /** + * A failure has occurred. + * + * @property throwable the related exception, if any. + */ + data class Failure(val throwable: Throwable?) : VectorSettingsPushRuleNotificationViewEvent +} diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewModel.kt new file mode 100644 index 0000000000..39969ec13e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewModel.kt @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2023 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.settings.notifications + +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.settings.notifications.VectorSettingsPushRuleNotificationViewEvent.Failure +import im.vector.app.features.settings.notifications.VectorSettingsPushRuleNotificationViewEvent.PushRuleUpdated +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.failure.Failure.ServerError +import org.matrix.android.sdk.api.failure.MatrixError +import org.matrix.android.sdk.api.session.pushrules.Action +import org.matrix.android.sdk.api.session.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.rest.PushRuleAndKind + +private typealias ViewModel = VectorSettingsPushRuleNotificationViewModel +private typealias ViewState = VectorSettingsPushRuleNotificationViewState + +class VectorSettingsPushRuleNotificationViewModel @AssistedInject constructor( + @Assisted initialState: ViewState, + private val activeSessionHolder: ActiveSessionHolder, +) : VectorViewModel<VectorSettingsPushRuleNotificationViewState, + VectorSettingsPushRuleNotificationViewAction, + VectorSettingsPushRuleNotificationViewEvent>(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory<ViewModel, ViewState> { + override fun create(initialState: ViewState): ViewModel + } + + companion object : MavericksViewModelFactory<ViewModel, ViewState> by hiltMavericksViewModelFactory() + + override fun handle(action: VectorSettingsPushRuleNotificationViewAction) { + when (action) { + is VectorSettingsPushRuleNotificationViewAction.UpdatePushRule -> handleUpdatePushRule(action.pushRuleAndKind, action.checked) + } + } + + fun getPushRuleAndKind(ruleId: String): PushRuleAndKind? { + return activeSessionHolder.getSafeActiveSession()?.pushRuleService()?.getPushRules()?.findDefaultRule(ruleId) + } + + fun isPushRuleChecked(ruleId: String): Boolean { + val rulesGroup = listOf(ruleId) + RuleIds.getSyncedRules(ruleId) + return rulesGroup.mapNotNull { getPushRuleAndKind(it) }.any { it.pushRule.notificationIndex != NotificationIndex.OFF } + } + + private fun handleUpdatePushRule(pushRuleAndKind: PushRuleAndKind, checked: Boolean) { + val ruleId = pushRuleAndKind.pushRule.ruleId + val kind = pushRuleAndKind.kind + val newIndex = if (checked) NotificationIndex.NOISY else NotificationIndex.OFF + val standardAction = getStandardAction(ruleId, newIndex) ?: return + val enabled = standardAction != StandardActions.Disabled + val newActions = standardAction.actions + setState { copy(isLoading = true) } + + viewModelScope.launch { + val rulesToUpdate = listOf(ruleId) + RuleIds.getSyncedRules(ruleId) + val results = rulesToUpdate.map { ruleId -> + runCatching { + updatePushRule(kind, ruleId, enabled, newActions) + } + } + setState { copy(isLoading = false) } + val failure = results.firstNotNullOfOrNull { result -> + // If the failure is a rule not found error, do not consider it + result.exceptionOrNull()?.takeUnless { it is ServerError && it.error.code == MatrixError.M_NOT_FOUND } + } + val newChecked = if (checked) { + // If any rule is checked, the global rule is checked + results.any { it.isSuccess } + } else { + // If any rule has not been unchecked, the global rule remains checked + failure != null + } + if (results.any { it.isSuccess }) { + _viewEvents.post(PushRuleUpdated(ruleId, newChecked, failure)) + } else { + _viewEvents.post(Failure(failure)) + } + } + } + + private suspend fun updatePushRule(kind: RuleKind, ruleId: String, enable: Boolean, newActions: List<Action>?) { + activeSessionHolder.getSafeActiveSession()?.pushRuleService()?.updatePushRuleActions( + kind = kind, + ruleId = ruleId, + enable = enable, + actions = newActions + ) + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewState.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewState.kt new file mode 100644 index 0000000000..477727aee6 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewState.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 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.settings.notifications + +import com.airbnb.mvrx.MavericksState + +data class VectorSettingsPushRuleNotificationViewState( + val isLoading: Boolean = false, +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/advanced/VectorSettingsAdvancedNotificationPreferenceFragment.kt similarity index 89% rename from vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt rename to vector/src/main/java/im/vector/app/features/settings/notifications/advanced/VectorSettingsAdvancedNotificationPreferenceFragment.kt index 183d997ffb..44d7330d39 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/advanced/VectorSettingsAdvancedNotificationPreferenceFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2023 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.app.features.settings.notifications +package im.vector.app.features.settings.notifications.advanced import androidx.lifecycle.lifecycleScope import androidx.preference.Preference @@ -23,10 +23,15 @@ import im.vector.app.core.preference.PushRulePreference import im.vector.app.core.preference.VectorPreference import im.vector.app.core.utils.toast import im.vector.app.features.settings.VectorSettingsBaseFragment +import im.vector.app.features.settings.notifications.NotificationIndex +import im.vector.app.features.settings.notifications.StandardActions +import im.vector.app.features.settings.notifications.getStandardAction +import im.vector.app.features.settings.notifications.notificationIndex import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.pushrules.RuleIds import org.matrix.android.sdk.api.session.pushrules.rest.PushRuleAndKind +// TODO This fragment seems not used anymore, we can probably delete it @AndroidEntryPoint class VectorSettingsAdvancedNotificationPreferenceFragment : VectorSettingsBaseFragment() { @@ -39,7 +44,7 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : for (preferenceKey in prefKeyToPushRuleId.keys) { val preference = findPreference<VectorPreference>(preferenceKey) if (preference is PushRulePreference) { - val ruleAndKind: PushRuleAndKind? = session.pushRuleService().getPushRules().findDefaultRule(prefKeyToPushRuleId[preferenceKey]) + val ruleAndKind: PushRuleAndKind? = prefKeyToPushRuleId[preferenceKey]?.let { session.pushRuleService().getPushRules().findDefaultRule(it) } if (ruleAndKind == null) { // The rule is not defined, hide the preference diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsDefaultNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/defaults/VectorSettingsDefaultNotificationFragment.kt similarity index 85% rename from vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsDefaultNotificationPreferenceFragment.kt rename to vector/src/main/java/im/vector/app/features/settings/notifications/defaults/VectorSettingsDefaultNotificationFragment.kt index 33ed7730eb..009213ceff 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsDefaultNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/defaults/VectorSettingsDefaultNotificationFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2023 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. @@ -14,16 +14,17 @@ * limitations under the License. */ -package im.vector.app.features.settings.notifications +package im.vector.app.features.settings.notifications.defaults import android.os.Bundle import im.vector.app.R import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.features.analytics.plan.MobileScreen +import im.vector.app.features.settings.notifications.VectorSettingsPushRuleNotificationFragment import org.matrix.android.sdk.api.session.pushrules.RuleIds -class VectorSettingsDefaultNotificationPreferenceFragment : - VectorSettingsPushRuleNotificationPreferenceFragment() { +class VectorSettingsDefaultNotificationFragment : + VectorSettingsPushRuleNotificationFragment() { override var titleRes: Int = R.string.settings_notification_default diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsKeywordAndMentionsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/keywordandmentions/VectorSettingsKeywordAndMentionsNotificationFragment.kt similarity index 93% rename from vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsKeywordAndMentionsNotificationPreferenceFragment.kt rename to vector/src/main/java/im/vector/app/features/settings/notifications/keywordandmentions/VectorSettingsKeywordAndMentionsNotificationFragment.kt index d0a1bff50c..c408b8ea91 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsKeywordAndMentionsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/keywordandmentions/VectorSettingsKeywordAndMentionsNotificationFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2023 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.settings.notifications +package im.vector.app.features.settings.notifications.keywordandmentions import android.os.Bundle import android.view.View @@ -26,6 +26,10 @@ import im.vector.app.core.preference.VectorCheckboxPreference import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.features.analytics.plan.MobileScreen +import im.vector.app.features.settings.notifications.NotificationIndex +import im.vector.app.features.settings.notifications.StandardActions +import im.vector.app.features.settings.notifications.VectorSettingsPushRuleNotificationFragment +import im.vector.app.features.settings.notifications.getStandardAction import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -34,8 +38,8 @@ import org.matrix.android.sdk.api.session.pushrules.RuleKind import org.matrix.android.sdk.api.session.pushrules.rest.PushRule import org.matrix.android.sdk.api.session.pushrules.toJson -class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment : - VectorSettingsPushRuleNotificationPreferenceFragment() { +class VectorSettingsKeywordAndMentionsNotificationFragment : + VectorSettingsPushRuleNotificationFragment() { override var titleRes: Int = R.string.settings_notification_mentions_and_keywords diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsOtherNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/other/VectorSettingsOtherNotificationFragment.kt similarity index 83% rename from vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsOtherNotificationPreferenceFragment.kt rename to vector/src/main/java/im/vector/app/features/settings/notifications/other/VectorSettingsOtherNotificationFragment.kt index 3638fb8526..5f8ad08a80 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsOtherNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/other/VectorSettingsOtherNotificationFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2023 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. @@ -14,14 +14,15 @@ * limitations under the License. */ -package im.vector.app.features.settings.notifications +package im.vector.app.features.settings.notifications.other import im.vector.app.R import im.vector.app.core.preference.VectorPreferenceCategory +import im.vector.app.features.settings.notifications.VectorSettingsPushRuleNotificationFragment import org.matrix.android.sdk.api.session.pushrules.RuleIds -class VectorSettingsOtherNotificationPreferenceFragment : - VectorSettingsPushRuleNotificationPreferenceFragment() { +class VectorSettingsOtherNotificationFragment : + VectorSettingsPushRuleNotificationFragment() { override var titleRes: Int = R.string.settings_notification_other diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/troubleshoot/VectorSettingsNotificationsTroubleshootFragment.kt similarity index 99% rename from vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt rename to vector/src/main/java/im/vector/app/features/settings/notifications/troubleshoot/VectorSettingsNotificationsTroubleshootFragment.kt index c5d15e54e5..b814c981c9 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/troubleshoot/VectorSettingsNotificationsTroubleshootFragment.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.app.features.settings.notifications +package im.vector.app.features.settings.notifications.troubleshoot import android.app.Activity import android.content.BroadcastReceiver diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/usecase/DisableNotificationsForCurrentSessionUseCase.kt similarity index 92% rename from vector/src/main/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCase.kt rename to vector/src/main/java/im/vector/app/features/settings/notifications/usecase/DisableNotificationsForCurrentSessionUseCase.kt index 0c50a296f3..9ce0307820 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/usecase/DisableNotificationsForCurrentSessionUseCase.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright (c) 2023 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.settings.notifications +package im.vector.app.features.settings.notifications.usecase import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/usecase/EnableNotificationsForCurrentSessionUseCase.kt similarity index 95% rename from vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt rename to vector/src/main/java/im/vector/app/features/settings/notifications/usecase/EnableNotificationsForCurrentSessionUseCase.kt index daf3890e33..2bb350b6ac 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/usecase/EnableNotificationsForCurrentSessionUseCase.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright (c) 2023 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.settings.notifications +package im.vector.app.features.settings.notifications.usecase import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase import im.vector.app.core.pushers.PushersManager diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/ToggleNotificationsForCurrentSessionUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/usecase/ToggleNotificationsForCurrentSessionUseCase.kt similarity index 97% rename from vector/src/main/java/im/vector/app/features/settings/notifications/ToggleNotificationsForCurrentSessionUseCase.kt rename to vector/src/main/java/im/vector/app/features/settings/notifications/usecase/ToggleNotificationsForCurrentSessionUseCase.kt index 3dc73f0a31..c717ab7657 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/ToggleNotificationsForCurrentSessionUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/usecase/ToggleNotificationsForCurrentSessionUseCase.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.settings.notifications +package im.vector.app.features.settings.notifications.usecase import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.pushers.UnifiedPushHelper diff --git a/vector/src/main/res/xml/vector_settings_notifications.xml b/vector/src/main/res/xml/vector_settings_notifications.xml index f4d7ff8cd5..87f344fc54 100644 --- a/vector/src/main/res/xml/vector_settings_notifications.xml +++ b/vector/src/main/res/xml/vector_settings_notifications.xml @@ -25,21 +25,21 @@ android:key="SETTINGS_NOTIFICATION_DEFAULT_PREFERENCE_KEY" android:persistent="false" android:title="@string/settings_notification_default" - app:fragment="im.vector.app.features.settings.notifications.VectorSettingsDefaultNotificationPreferenceFragment" /> + app:fragment="im.vector.app.features.settings.notifications.defaults.VectorSettingsDefaultNotificationFragment" /> <im.vector.app.core.preference.VectorPreference android:dependency="SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY" android:key="SETTINGS_NOTIFICATION_KEYWORD_AND_MENTIONS_PREFERENCE_KEY" android:persistent="false" android:title="@string/settings_notification_mentions_and_keywords" - app:fragment="im.vector.app.features.settings.notifications.VectorSettingsKeywordAndMentionsNotificationPreferenceFragment" /> + app:fragment="im.vector.app.features.settings.notifications.keywordandmentions.VectorSettingsKeywordAndMentionsNotificationFragment" /> <im.vector.app.core.preference.VectorPreference android:dependency="SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY" android:key="SETTINGS_NOTIFICATION_OTHER_PREFERENCE_KEY" android:persistent="false" android:title="@string/settings_notification_other" - app:fragment="im.vector.app.features.settings.notifications.VectorSettingsOtherNotificationPreferenceFragment" /> + app:fragment="im.vector.app.features.settings.notifications.other.VectorSettingsOtherNotificationFragment" /> </im.vector.app.core.preference.VectorPreferenceCategory> @@ -119,7 +119,7 @@ <im.vector.app.core.preference.VectorPreference android:key="SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY" android:title="@string/settings_notification_troubleshoot" - app:fragment="im.vector.app.features.settings.notifications.VectorSettingsNotificationsTroubleshootFragment" /> + app:fragment="im.vector.app.features.settings.notifications.troubleshoot.VectorSettingsNotificationsTroubleshootFragment" /> </im.vector.app.core.preference.VectorPreferenceCategory> diff --git a/vector/src/main/res/xml/vector_settings_root.xml b/vector/src/main/res/xml/vector_settings_root.xml index ffa8688cbd..59548d10cb 100644 --- a/vector/src/main/res/xml/vector_settings_root.xml +++ b/vector/src/main/res/xml/vector_settings_root.xml @@ -11,7 +11,7 @@ <im.vector.app.core.preference.VectorPreference android:icon="@drawable/ic_settings_root_notification" android:title="@string/settings_notifications" - app:fragment="im.vector.app.features.settings.notifications.VectorSettingsNotificationPreferenceFragment" + app:fragment="im.vector.app.features.settings.notifications.VectorSettingsNotificationFragment" app:isPreferenceVisible="@bool/settings_root_notification_visible" /> <im.vector.app.core.preference.VectorPreference diff --git a/vector/src/test/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCaseTest.kt index 669b20fc1a..ceb6fd9217 100644 --- a/vector/src/test/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCaseTest.kt @@ -17,6 +17,8 @@ package im.vector.app.features.settings.notifications import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase +import im.vector.app.features.settings.notifications.usecase.DisableNotificationsForCurrentSessionUseCase +import im.vector.app.features.settings.notifications.usecase.ToggleNotificationsForCurrentSessionUseCase import im.vector.app.test.fakes.FakePushersManager import io.mockk.coJustRun import io.mockk.coVerify diff --git a/vector/src/test/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCaseTest.kt index d58ba7645c..fa5d0a8bd1 100644 --- a/vector/src/test/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCaseTest.kt @@ -18,6 +18,8 @@ package im.vector.app.features.settings.notifications import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase import im.vector.app.core.pushers.RegisterUnifiedPushUseCase +import im.vector.app.features.settings.notifications.usecase.EnableNotificationsForCurrentSessionUseCase +import im.vector.app.features.settings.notifications.usecase.ToggleNotificationsForCurrentSessionUseCase import im.vector.app.test.fakes.FakePushersManager import io.mockk.coJustRun import io.mockk.coVerify diff --git a/vector/src/test/java/im/vector/app/features/settings/notifications/ToggleNotificationsForCurrentSessionUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/notifications/ToggleNotificationsForCurrentSessionUseCaseTest.kt index f49aafab8a..83a4265ace 100644 --- a/vector/src/test/java/im/vector/app/features/settings/notifications/ToggleNotificationsForCurrentSessionUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/notifications/ToggleNotificationsForCurrentSessionUseCaseTest.kt @@ -19,6 +19,7 @@ package im.vector.app.features.settings.notifications import im.vector.app.features.settings.devices.v2.notification.CheckIfCanToggleNotificationsViaPusherUseCase import im.vector.app.features.settings.devices.v2.notification.DeleteNotificationSettingsAccountDataUseCase import im.vector.app.features.settings.devices.v2.notification.SetNotificationSettingsAccountDataUseCase +import im.vector.app.features.settings.notifications.usecase.ToggleNotificationsForCurrentSessionUseCase import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeUnifiedPushHelper import im.vector.app.test.fixtures.PusherFixture diff --git a/vector/src/test/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationViewModelTest.kt similarity index 85% rename from vector/src/test/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModelTest.kt rename to vector/src/test/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationViewModelTest.kt index ae36ee7600..ad7adc6b69 100644 --- a/vector/src/test/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationViewModelTest.kt @@ -22,6 +22,9 @@ import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase import im.vector.app.core.pushers.RegisterUnifiedPushUseCase import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase import im.vector.app.features.settings.VectorPreferences.Companion.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY +import im.vector.app.features.settings.notifications.usecase.DisableNotificationsForCurrentSessionUseCase +import im.vector.app.features.settings.notifications.usecase.EnableNotificationsForCurrentSessionUseCase +import im.vector.app.features.settings.notifications.usecase.ToggleNotificationsForCurrentSessionUseCase import im.vector.app.test.fakes.FakePushersManager import im.vector.app.test.fakes.FakeVectorPreferences import im.vector.app.test.test @@ -35,7 +38,7 @@ import io.mockk.mockk import org.junit.Rule import org.junit.Test -class VectorSettingsNotificationPreferenceViewModelTest { +class VectorSettingsNotificationViewModelTest { @get:Rule val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher) @@ -49,7 +52,7 @@ class VectorSettingsNotificationPreferenceViewModelTest { private val fakeEnsureFcmTokenIsRetrievedUseCase = mockk<EnsureFcmTokenIsRetrievedUseCase>() private val fakeToggleNotificationsForCurrentSessionUseCase = mockk<ToggleNotificationsForCurrentSessionUseCase>() - private fun createViewModel() = VectorSettingsNotificationPreferenceViewModel( + private fun createViewModel() = VectorSettingsNotificationViewModel( initialState = VectorDummyViewState(), pushersManager = fakePushersManager.instance, vectorPreferences = fakeVectorPreferences.instance, @@ -65,7 +68,7 @@ class VectorSettingsNotificationPreferenceViewModelTest { fun `given view model init when notifications are enabled in preferences then view event is posted`() { // Given fakeVectorPreferences.givenAreNotificationsEnabledForDevice(true) - val expectedEvent = VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceEnabled + val expectedEvent = VectorSettingsNotificationViewEvent.NotificationsForDeviceEnabled val viewModel = createViewModel() // When @@ -82,7 +85,7 @@ class VectorSettingsNotificationPreferenceViewModelTest { fun `given view model init when notifications are disabled in preferences then view event is posted`() { // Given fakeVectorPreferences.givenAreNotificationsEnabledForDevice(false) - val expectedEvent = VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceDisabled + val expectedEvent = VectorSettingsNotificationViewEvent.NotificationsForDeviceDisabled val viewModel = createViewModel() // When @@ -99,9 +102,9 @@ class VectorSettingsNotificationPreferenceViewModelTest { fun `given DisableNotificationsForDevice action when handling action then disable use case is called`() { // Given val viewModel = createViewModel() - val action = VectorSettingsNotificationPreferenceViewAction.DisableNotificationsForDevice + val action = VectorSettingsNotificationViewAction.DisableNotificationsForDevice coJustRun { fakeDisableNotificationsForCurrentSessionUseCase.execute() } - val expectedEvent = VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceDisabled + val expectedEvent = VectorSettingsNotificationViewEvent.NotificationsForDeviceDisabled // When val viewModelTest = viewModel.test() @@ -121,10 +124,10 @@ class VectorSettingsNotificationPreferenceViewModelTest { // Given val viewModel = createViewModel() val aDistributor = "aDistributor" - val action = VectorSettingsNotificationPreferenceViewAction.EnableNotificationsForDevice(aDistributor) + val action = VectorSettingsNotificationViewAction.EnableNotificationsForDevice(aDistributor) coEvery { fakeEnableNotificationsForCurrentSessionUseCase.execute(any()) } returns EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.Success - val expectedEvent = VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceEnabled + val expectedEvent = VectorSettingsNotificationViewEvent.NotificationsForDeviceEnabled // When val viewModelTest = viewModel.test() @@ -144,10 +147,10 @@ class VectorSettingsNotificationPreferenceViewModelTest { // Given val viewModel = createViewModel() val aDistributor = "aDistributor" - val action = VectorSettingsNotificationPreferenceViewAction.EnableNotificationsForDevice(aDistributor) + val action = VectorSettingsNotificationViewAction.EnableNotificationsForDevice(aDistributor) coEvery { fakeEnableNotificationsForCurrentSessionUseCase.execute(any()) } returns EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.NeedToAskUserForDistributor - val expectedEvent = VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor + val expectedEvent = VectorSettingsNotificationViewEvent.AskUserForPushDistributor // When val viewModelTest = viewModel.test() @@ -167,14 +170,14 @@ class VectorSettingsNotificationPreferenceViewModelTest { // Given val viewModel = createViewModel() val aDistributor = "aDistributor" - val action = VectorSettingsNotificationPreferenceViewAction.RegisterPushDistributor(aDistributor) + val action = VectorSettingsNotificationViewAction.RegisterPushDistributor(aDistributor) coEvery { fakeRegisterUnifiedPushUseCase.execute(any()) } returns RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success coJustRun { fakeUnregisterUnifiedPushUseCase.execute(any()) } val areNotificationsEnabled = true fakeVectorPreferences.givenAreNotificationsEnabledForDevice(areNotificationsEnabled) coJustRun { fakeToggleNotificationsForCurrentSessionUseCase.execute(any()) } justRun { fakeEnsureFcmTokenIsRetrievedUseCase.execute(any(), any()) } - val expectedEvent = VectorSettingsNotificationPreferenceViewEvent.NotificationMethodChanged + val expectedEvent = VectorSettingsNotificationViewEvent.NotificationMethodChanged // When val viewModelTest = viewModel.test() @@ -197,10 +200,10 @@ class VectorSettingsNotificationPreferenceViewModelTest { // Given val viewModel = createViewModel() val aDistributor = "aDistributor" - val action = VectorSettingsNotificationPreferenceViewAction.RegisterPushDistributor(aDistributor) + val action = VectorSettingsNotificationViewAction.RegisterPushDistributor(aDistributor) coEvery { fakeRegisterUnifiedPushUseCase.execute(any()) } returns RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor coJustRun { fakeUnregisterUnifiedPushUseCase.execute(any()) } - val expectedEvent = VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor + val expectedEvent = VectorSettingsNotificationViewEvent.AskUserForPushDistributor // When val viewModelTest = viewModel.test() diff --git a/vector/src/test/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewModelTest.kt new file mode 100644 index 0000000000..04a22bc21f --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationViewModelTest.kt @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2023 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.settings.notifications + +import com.airbnb.mvrx.test.MavericksTestRule +import im.vector.app.test.fakes.FakeActiveSessionHolder +import im.vector.app.test.test +import im.vector.app.test.testDispatcher +import io.mockk.coVerifyOrder +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBe +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.matrix.android.sdk.api.session.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.rest.PushRuleAndKind + +internal class VectorSettingsPushRuleNotificationViewModelTest { + + @get:Rule + val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher) + + private val fakeActiveSessionHolder = FakeActiveSessionHolder() + private val fakePushRuleService = fakeActiveSessionHolder.fakeSession.fakePushRuleService + + private val initialState = VectorSettingsPushRuleNotificationViewState() + private fun createViewModel() = VectorSettingsPushRuleNotificationViewModel( + initialState = initialState, + activeSessionHolder = fakeActiveSessionHolder.instance, + ) + + @Before + fun setup() { + mockkStatic("im.vector.app.features.settings.notifications.NotificationIndexKt") + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given a ruleId, when the rule is checked or unchecked with no error, then the expected view event is posted`() = runTest { + // Given + val viewModel = createViewModel() + + val firstRuleId = RuleIds.RULE_ID_ONE_TO_ONE_ROOM + val secondRuleId = RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS + fakePushRuleService.givenUpdatePushRuleActionsSucceed() + + // When + val viewModelTest = viewModel.test() + viewModel.handle(VectorSettingsPushRuleNotificationViewAction.UpdatePushRule(givenARuleId(firstRuleId), true)) + viewModel.handle(VectorSettingsPushRuleNotificationViewAction.UpdatePushRule(givenARuleId(secondRuleId), false)) + + // Then + coVerifyOrder { + // first rule id + fakePushRuleService.updatePushRuleActions(any(), firstRuleId, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_START_ONE_TO_ONE, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_START_ONE_TO_ONE_UNSTABLE, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_END_ONE_TO_ONE, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_END_ONE_TO_ONE_UNSTABLE, any(), any()) + + // second rule id + fakePushRuleService.updatePushRuleActions(any(), secondRuleId, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_START, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_START_UNSTABLE, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_END, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_END_UNSTABLE, any(), any()) + } + + viewModelTest + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) }, + { copy(isLoading = true) }, + { copy(isLoading = false) }, + ) + .assertEvents( + VectorSettingsPushRuleNotificationViewEvent.PushRuleUpdated(RuleIds.RULE_ID_ONE_TO_ONE_ROOM, true), + VectorSettingsPushRuleNotificationViewEvent.PushRuleUpdated(RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS, false), + ) + .finish() + } + + @Test + fun `given a ruleId, when the rule is checked with an error, then expected view event is posted`() = runTest { + // Given + val viewModel = createViewModel() + val failure = mockk<Throwable>() + + val firstRuleId = RuleIds.RULE_ID_ONE_TO_ONE_ROOM + fakePushRuleService.givenUpdatePushRuleActionsSucceed() + fakePushRuleService.givenUpdatePushRuleActionsFail(RuleIds.RULE_ID_POLL_START_ONE_TO_ONE_UNSTABLE, failure) + + val secondRuleId = RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS + fakePushRuleService.givenUpdatePushRuleActionsFail(secondRuleId, failure) + fakePushRuleService.givenUpdatePushRuleActionsFail(RuleIds.RULE_ID_POLL_START, failure) + fakePushRuleService.givenUpdatePushRuleActionsFail(RuleIds.RULE_ID_POLL_START_UNSTABLE, failure) + fakePushRuleService.givenUpdatePushRuleActionsFail(RuleIds.RULE_ID_POLL_END, failure) + fakePushRuleService.givenUpdatePushRuleActionsFail(RuleIds.RULE_ID_POLL_END_UNSTABLE, failure) + + // When + val viewModelTest = viewModel.test() + // One rule failed to update + viewModel.handle(VectorSettingsPushRuleNotificationViewAction.UpdatePushRule(givenARuleId(firstRuleId), true)) + // All the rules failed to update + viewModel.handle(VectorSettingsPushRuleNotificationViewAction.UpdatePushRule(givenARuleId(secondRuleId), true)) + + // Then + coVerifyOrder { + // first rule id + fakePushRuleService.updatePushRuleActions(any(), firstRuleId, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_START_ONE_TO_ONE, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_START_ONE_TO_ONE_UNSTABLE, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_END_ONE_TO_ONE, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_END_ONE_TO_ONE_UNSTABLE, any(), any()) + + // second rule id + fakePushRuleService.updatePushRuleActions(any(), secondRuleId, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_START, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_START_UNSTABLE, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_END, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_END_UNSTABLE, any(), any()) + } + + viewModelTest + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) }, + { copy(isLoading = true) }, + { copy(isLoading = false) }, + ) + .assertEvents( + VectorSettingsPushRuleNotificationViewEvent.PushRuleUpdated(RuleIds.RULE_ID_ONE_TO_ONE_ROOM, true, failure), + VectorSettingsPushRuleNotificationViewEvent.Failure(failure), + ) + .finish() + } + + @Test + fun `given a ruleId, when the rule is unchecked with an error, then the expected view event is posted`() = runTest { + // Given + val viewModel = createViewModel() + val failure = mockk<Throwable>() + + val firstRuleId = RuleIds.RULE_ID_ONE_TO_ONE_ROOM + fakePushRuleService.givenUpdatePushRuleActionsSucceed() + fakePushRuleService.givenUpdatePushRuleActionsFail(RuleIds.RULE_ID_POLL_START_ONE_TO_ONE_UNSTABLE, failure) + + val secondRuleId = RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS + fakePushRuleService.givenUpdatePushRuleActionsFail(secondRuleId, failure) + fakePushRuleService.givenUpdatePushRuleActionsFail(RuleIds.RULE_ID_POLL_START, failure) + fakePushRuleService.givenUpdatePushRuleActionsFail(RuleIds.RULE_ID_POLL_START_UNSTABLE, failure) + fakePushRuleService.givenUpdatePushRuleActionsFail(RuleIds.RULE_ID_POLL_END, failure) + fakePushRuleService.givenUpdatePushRuleActionsFail(RuleIds.RULE_ID_POLL_END_UNSTABLE, failure) + + // When + val viewModelTest = viewModel.test() + // One rule failed to update + viewModel.handle(VectorSettingsPushRuleNotificationViewAction.UpdatePushRule(givenARuleId(firstRuleId), false)) + // All the rules failed to update + viewModel.handle(VectorSettingsPushRuleNotificationViewAction.UpdatePushRule(givenARuleId(secondRuleId), false)) + + // Then + coVerifyOrder { + // first rule id + fakePushRuleService.updatePushRuleActions(any(), firstRuleId, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_START_ONE_TO_ONE, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_START_ONE_TO_ONE_UNSTABLE, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_END_ONE_TO_ONE, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_END_ONE_TO_ONE_UNSTABLE, any(), any()) + + // second rule id + fakePushRuleService.updatePushRuleActions(any(), secondRuleId, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_START, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_START_UNSTABLE, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_END, any(), any()) + fakePushRuleService.updatePushRuleActions(any(), RuleIds.RULE_ID_POLL_END_UNSTABLE, any(), any()) + } + + viewModelTest + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) }, + { copy(isLoading = true) }, + { copy(isLoading = false) }, + ) + .assertEvents( + // The global rule remains checked if all the rules are not unchecked + VectorSettingsPushRuleNotificationViewEvent.PushRuleUpdated(RuleIds.RULE_ID_ONE_TO_ONE_ROOM, true, failure), + VectorSettingsPushRuleNotificationViewEvent.Failure(failure), + ) + .finish() + } + + @Test + fun `given a rule id, when requesting the check state, returns the expected value according to the related rules`() { + // Given + val viewModel = createViewModel() + val firstRuleId = RuleIds.RULE_ID_ONE_TO_ONE_ROOM + givenARuleId(firstRuleId, NotificationIndex.OFF) + givenARuleId(RuleIds.RULE_ID_POLL_START_ONE_TO_ONE, NotificationIndex.OFF) + givenARuleId(RuleIds.RULE_ID_POLL_START_ONE_TO_ONE_UNSTABLE, NotificationIndex.SILENT) + givenARuleId(RuleIds.RULE_ID_POLL_END_ONE_TO_ONE, NotificationIndex.NOISY) + givenARuleId(RuleIds.RULE_ID_POLL_END_ONE_TO_ONE_UNSTABLE, NotificationIndex.OFF) + + val secondRuleId = RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS + givenARuleId(secondRuleId, NotificationIndex.OFF) + givenARuleId(RuleIds.RULE_ID_POLL_START, NotificationIndex.OFF) + givenARuleId(RuleIds.RULE_ID_POLL_START_UNSTABLE, NotificationIndex.OFF) + givenARuleId(RuleIds.RULE_ID_POLL_END, NotificationIndex.OFF) + givenARuleId(RuleIds.RULE_ID_POLL_END_UNSTABLE, NotificationIndex.OFF) + + // When + val firstResult = viewModel.isPushRuleChecked(firstRuleId) + val secondResult = viewModel.isPushRuleChecked(secondRuleId) + + // Then + firstResult shouldBe true + secondResult shouldBe false + } + + private fun givenARuleId(ruleId: String, notificationIndex: NotificationIndex = NotificationIndex.NOISY): PushRuleAndKind { + val ruleAndKind = mockk<PushRuleAndKind> { + every { pushRule.ruleId } returns ruleId + every { pushRule.notificationIndex } returns notificationIndex + every { kind } returns mockk() + } + + every { fakePushRuleService.getPushRules().findDefaultRule(ruleId) } returns ruleAndKind + + return ruleAndKind + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakePushRuleService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakePushRuleService.kt new file mode 100644 index 0000000000..4560f58978 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakePushRuleService.kt @@ -0,0 +1,33 @@ +/* + * 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.test.fakes + +import io.mockk.coEvery +import io.mockk.coJustRun +import io.mockk.mockk +import org.matrix.android.sdk.api.session.pushrules.PushRuleService + +class FakePushRuleService : PushRuleService by mockk(relaxed = true) { + + fun givenUpdatePushRuleActionsSucceed(ruleId: String? = null) { + coJustRun { updatePushRuleActions(any(), ruleId ?: any(), any(), any()) } + } + + fun givenUpdatePushRuleActionsFail(ruleId: String? = null, failure: Throwable = mockk()) { + coEvery { updatePushRuleActions(any(), ruleId ?: any(), any(), any()) }.throws(failure) + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt index a05dce9c54..1b6d3e2729 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt @@ -41,10 +41,11 @@ class FakeSession( val fakeHomeServerCapabilitiesService: FakeHomeServerCapabilitiesService = FakeHomeServerCapabilitiesService(), val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService(), val fakeRoomService: FakeRoomService = FakeRoomService(), + val fakePushRuleService: FakePushRuleService = FakePushRuleService(), val fakePushersService: FakePushersService = FakePushersService(), val fakeUserService: FakeUserService = FakeUserService(), private val fakeEventService: FakeEventService = FakeEventService(), - val fakeSessionAccountDataService: FakeSessionAccountDataService = FakeSessionAccountDataService() + val fakeSessionAccountDataService: FakeSessionAccountDataService = FakeSessionAccountDataService(), ) : Session by mockk(relaxed = true) { init { @@ -61,6 +62,7 @@ class FakeSession( override fun sharedSecretStorageService() = fakeSharedSecretStorageService override fun roomService() = fakeRoomService override fun eventService() = fakeEventService + override fun pushRuleService() = fakePushRuleService override fun pushersService() = fakePushersService override fun accountDataService() = fakeSessionAccountDataService override fun userService() = fakeUserService