Merge pull request #8114 from vector-im/feature/fre/poll_new_push_rules

[Poll] Synchronize polls push rules with message push rules (PSG-954)
This commit is contained in:
Florian Renaud 2023-02-17 10:03:53 +01:00 committed by GitHub
commit f887acd854
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 741 additions and 184 deletions

1
changelog.d/8007.feature Normal file
View File

@ -0,0 +1 @@
[Poll] Synchronize polls push rules with message push rules

View File

@ -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()
}
}
}

View File

@ -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
}
/**

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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))
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
)
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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