Listen for pusher or account data changes to update the local setting

This commit is contained in:
Maxime NATUREL 2022-11-03 16:33:16 +01:00
parent 67d2a6faab
commit 24a5cfa9e5
15 changed files with 206 additions and 46 deletions

View file

@ -16,6 +16,9 @@
package org.matrix.android.sdk.api.session.homeserver package org.matrix.android.sdk.api.session.homeserver
import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.util.Optional
/** /**
* This interface defines a method to retrieve the homeserver capabilities. * This interface defines a method to retrieve the homeserver capabilities.
*/ */
@ -30,4 +33,9 @@ interface HomeServerCapabilitiesService {
* Get the HomeServer capabilities. * Get the HomeServer capabilities.
*/ */
fun getHomeServerCapabilities(): HomeServerCapabilities fun getHomeServerCapabilities(): HomeServerCapabilities
/**
* Get a LiveData on the HomeServer capabilities.
*/
fun getHomeServerCapabilitiesLive(): LiveData<Optional<HomeServerCapabilities>>
} }

View file

@ -16,8 +16,10 @@
package org.matrix.android.sdk.internal.session.homeserver package org.matrix.android.sdk.internal.session.homeserver
import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.util.Optional
import javax.inject.Inject import javax.inject.Inject
internal class DefaultHomeServerCapabilitiesService @Inject constructor( internal class DefaultHomeServerCapabilitiesService @Inject constructor(
@ -33,4 +35,8 @@ internal class DefaultHomeServerCapabilitiesService @Inject constructor(
return homeServerCapabilitiesDataSource.getHomeServerCapabilities() return homeServerCapabilitiesDataSource.getHomeServerCapabilities()
?: HomeServerCapabilities() ?: HomeServerCapabilities()
} }
override fun getHomeServerCapabilitiesLive(): LiveData<Optional<HomeServerCapabilities>> {
return homeServerCapabilitiesDataSource.getHomeServerCapabilitiesLive()
}
} }

View file

@ -16,9 +16,14 @@
package org.matrix.android.sdk.internal.session.homeserver package org.matrix.android.sdk.internal.session.homeserver
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.where
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
import org.matrix.android.sdk.internal.database.query.get import org.matrix.android.sdk.internal.database.query.get
@ -26,7 +31,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
import javax.inject.Inject import javax.inject.Inject
internal class HomeServerCapabilitiesDataSource @Inject constructor( internal class HomeServerCapabilitiesDataSource @Inject constructor(
@SessionDatabase private val monarchy: Monarchy @SessionDatabase private val monarchy: Monarchy,
) { ) {
fun getHomeServerCapabilities(): HomeServerCapabilities? { fun getHomeServerCapabilities(): HomeServerCapabilities? {
return Realm.getInstance(monarchy.realmConfiguration).use { realm -> return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
@ -35,4 +40,14 @@ internal class HomeServerCapabilitiesDataSource @Inject constructor(
} }
} }
} }
fun getHomeServerCapabilitiesLive(): LiveData<Optional<HomeServerCapabilities>> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm: Realm -> realm.where<HomeServerCapabilitiesEntity>() },
{ HomeServerCapabilitiesMapper.map(it) }
)
return Transformations.map(liveData) {
it.firstOrNull().toOptional()
}
}
} }

View file

@ -19,6 +19,7 @@ package im.vector.app.core.di
import android.content.Context import android.content.Context
import im.vector.app.ActiveSessionDataSource import im.vector.app.ActiveSessionDataSource
import im.vector.app.core.extensions.startSyncing import im.vector.app.core.extensions.startSyncing
import im.vector.app.core.notification.EnableNotificationsSettingUpdater
import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.services.GuardServiceStarter import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.core.session.ConfigureAndStartSessionUseCase import im.vector.app.core.session.ConfigureAndStartSessionUseCase

View file

@ -0,0 +1,41 @@
/*
* 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.core.notification
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class EnableNotificationsSettingUpdater @Inject constructor(
private val updateEnableNotificationsSettingOnChangeUseCase: UpdateEnableNotificationsSettingOnChangeUseCase,
) {
private var job: Job? = null
fun onSessionsStarted(session: Session) {
job?.cancel()
job = session.coroutineScope.launch {
updateEnableNotificationsSettingOnChangeUseCase.execute(session)
.launchIn(this)
}
}
}

View file

@ -0,0 +1,53 @@
/*
* 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.core.notification
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.devices.v2.notification.GetNotificationsStatusUseCase
import im.vector.app.features.settings.devices.v2.notification.NotificationsStatus
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.session.Session
import timber.log.Timber
import javax.inject.Inject
/**
* Listen for changes in either Pusher or Account data to update the local enable notifications
* setting for the current device.
*/
class UpdateEnableNotificationsSettingOnChangeUseCase @Inject constructor(
private val vectorPreferences: VectorPreferences,
private val getNotificationsStatusUseCase: GetNotificationsStatusUseCase,
) {
// TODO add unit tests
fun execute(session: Session): Flow<NotificationsStatus> {
val deviceId = session.sessionParams.deviceId ?: return emptyFlow()
return getNotificationsStatusUseCase.execute(session, deviceId)
.onEach(::updatePreference)
}
private fun updatePreference(notificationStatus: NotificationsStatus) {
Timber.d("updatePreference with status=$notificationStatus")
when (notificationStatus) {
NotificationsStatus.ENABLED -> vectorPreferences.setNotificationEnabledForDevice(true)
NotificationsStatus.DISABLED -> vectorPreferences.setNotificationEnabledForDevice(false)
else -> Unit
}
}
}

View file

@ -19,6 +19,7 @@ package im.vector.app.core.session
import android.content.Context import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import im.vector.app.core.extensions.startSyncing import im.vector.app.core.extensions.startSyncing
import im.vector.app.core.notification.EnableNotificationsSettingUpdater
import im.vector.app.core.session.clientinfo.UpdateMatrixClientInfoUseCase import im.vector.app.core.session.clientinfo.UpdateMatrixClientInfoUseCase
import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
@ -32,8 +33,10 @@ class ConfigureAndStartSessionUseCase @Inject constructor(
private val webRtcCallManager: WebRtcCallManager, private val webRtcCallManager: WebRtcCallManager,
private val updateMatrixClientInfoUseCase: UpdateMatrixClientInfoUseCase, private val updateMatrixClientInfoUseCase: UpdateMatrixClientInfoUseCase,
private val vectorPreferences: VectorPreferences, private val vectorPreferences: VectorPreferences,
private val enableNotificationsSettingUpdater: EnableNotificationsSettingUpdater,
) { ) {
// TODO update unit tests
suspend fun execute(session: Session, startSyncing: Boolean = true) { suspend fun execute(session: Session, startSyncing: Boolean = true) {
Timber.i("Configure and start session for ${session.myUserId}. startSyncing: $startSyncing") Timber.i("Configure and start session for ${session.myUserId}. startSyncing: $startSyncing")
session.open() session.open()
@ -46,5 +49,6 @@ class ConfigureAndStartSessionUseCase @Inject constructor(
if (vectorPreferences.isClientInfoRecordingEnabled()) { if (vectorPreferences.isClientInfoRecordingEnabled()) {
updateMatrixClientInfoUseCase.execute(session) updateMatrixClientInfoUseCase.execute(session)
} }
enableNotificationsSettingUpdater.onSessionsStarted(session)
} }
} }

View file

@ -0,0 +1,36 @@
/*
* 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.features.settings.devices.v2.notification
import androidx.lifecycle.asFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.flow.unwrap
import javax.inject.Inject
class CanTogglePushNotificationsViaPusherUseCase @Inject constructor() {
fun execute(session: Session): Flow<Boolean> {
return session
.homeServerCapabilitiesService()
.getHomeServerCapabilitiesLive()
.asFlow()
.unwrap()
.map { it.canRemotelyTogglePushNotificationsOfDevices }
}
}

View file

@ -16,18 +16,15 @@
package im.vector.app.features.settings.devices.v2.notification package im.vector.app.features.settings.devices.v2.notification
import im.vector.app.core.di.ActiveSessionHolder import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
import javax.inject.Inject import javax.inject.Inject
class CheckIfCanTogglePushNotificationsViaAccountDataUseCase @Inject constructor( class CheckIfCanTogglePushNotificationsViaAccountDataUseCase @Inject constructor() {
private val activeSessionHolder: ActiveSessionHolder,
) {
fun execute(deviceId: String): Boolean { fun execute(session: Session, deviceId: String): Boolean {
return activeSessionHolder return session
.getSafeActiveSession() .accountDataService()
?.accountDataService() .getUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId) != null
?.getUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId) != null
} }
} }

View file

@ -16,20 +16,15 @@
package im.vector.app.features.settings.devices.v2.notification package im.vector.app.features.settings.devices.v2.notification
import im.vector.app.core.di.ActiveSessionHolder import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.extensions.orFalse
import javax.inject.Inject import javax.inject.Inject
class CheckIfCanTogglePushNotificationsViaPusherUseCase @Inject constructor( class CheckIfCanTogglePushNotificationsViaPusherUseCase @Inject constructor() {
private val activeSessionHolder: ActiveSessionHolder,
) {
fun execute(): Boolean { fun execute(session: Session): Boolean {
return activeSessionHolder return session
.getSafeActiveSession() .homeServerCapabilitiesService()
?.homeServerCapabilitiesService() .getHomeServerCapabilities()
?.getHomeServerCapabilities() .canRemotelyTogglePushNotificationsOfDevices
?.canRemotelyTogglePushNotificationsOfDevices
.orFalse()
} }
} }

View file

@ -16,12 +16,13 @@
package im.vector.app.features.settings.devices.v2.notification package im.vector.app.features.settings.devices.v2.notification
import im.vector.app.core.di.ActiveSessionHolder
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
@ -29,16 +30,13 @@ import org.matrix.android.sdk.flow.unwrap
import javax.inject.Inject import javax.inject.Inject
class GetNotificationsStatusUseCase @Inject constructor( class GetNotificationsStatusUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder, private val canTogglePushNotificationsViaPusherUseCase: CanTogglePushNotificationsViaPusherUseCase,
private val checkIfCanTogglePushNotificationsViaPusherUseCase: CheckIfCanTogglePushNotificationsViaPusherUseCase,
private val checkIfCanTogglePushNotificationsViaAccountDataUseCase: CheckIfCanTogglePushNotificationsViaAccountDataUseCase, private val checkIfCanTogglePushNotificationsViaAccountDataUseCase: CheckIfCanTogglePushNotificationsViaAccountDataUseCase,
) { ) {
fun execute(deviceId: String): Flow<NotificationsStatus> { fun execute(session: Session, deviceId: String): Flow<NotificationsStatus> {
val session = activeSessionHolder.getSafeActiveSession()
return when { return when {
session == null -> flowOf(NotificationsStatus.NOT_SUPPORTED) checkIfCanTogglePushNotificationsViaAccountDataUseCase.execute(session, deviceId) -> {
checkIfCanTogglePushNotificationsViaAccountDataUseCase.execute(deviceId) -> {
session.flow() session.flow()
.liveUserAccountData(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId) .liveUserAccountData(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId)
.unwrap() .unwrap()
@ -46,15 +44,19 @@ class GetNotificationsStatusUseCase @Inject constructor(
.map { if (it == true) NotificationsStatus.ENABLED else NotificationsStatus.DISABLED } .map { if (it == true) NotificationsStatus.ENABLED else NotificationsStatus.DISABLED }
.distinctUntilChanged() .distinctUntilChanged()
} }
checkIfCanTogglePushNotificationsViaPusherUseCase.execute() -> { else -> canTogglePushNotificationsViaPusherUseCase.execute(session)
.flatMapLatest { canToggle ->
if (canToggle) {
session.flow() session.flow()
.livePushers() .livePushers()
.map { it.filter { pusher -> pusher.deviceId == deviceId } } .map { it.filter { pusher -> pusher.deviceId == deviceId } }
.map { it.takeIf { it.isNotEmpty() }?.any { pusher -> pusher.enabled } } .map { it.takeIf { it.isNotEmpty() }?.any { pusher -> pusher.enabled } }
.map { if (it == true) NotificationsStatus.ENABLED else NotificationsStatus.DISABLED } .map { if (it == true) NotificationsStatus.ENABLED else NotificationsStatus.DISABLED }
.distinctUntilChanged() .distinctUntilChanged()
} else {
flowOf(NotificationsStatus.NOT_SUPPORTED)
}
} }
else -> flowOf(NotificationsStatus.NOT_SUPPORTED)
} }
} }
} }

View file

@ -31,14 +31,14 @@ class TogglePushNotificationUseCase @Inject constructor(
suspend fun execute(deviceId: String, enabled: Boolean) { suspend fun execute(deviceId: String, enabled: Boolean) {
val session = activeSessionHolder.getSafeActiveSession() ?: return val session = activeSessionHolder.getSafeActiveSession() ?: return
if (checkIfCanTogglePushNotificationsViaPusherUseCase.execute()) { if (checkIfCanTogglePushNotificationsViaPusherUseCase.execute(session)) {
val devicePusher = session.pushersService().getPushers().firstOrNull { it.deviceId == deviceId } val devicePusher = session.pushersService().getPushers().firstOrNull { it.deviceId == deviceId }
devicePusher?.let { pusher -> devicePusher?.let { pusher ->
session.pushersService().togglePusher(pusher, enabled) session.pushersService().togglePusher(pusher, enabled)
} }
} }
if (checkIfCanTogglePushNotificationsViaAccountDataUseCase.execute(deviceId)) { if (checkIfCanTogglePushNotificationsViaAccountDataUseCase.execute(session, deviceId)) {
val newNotificationSettingsContent = LocalNotificationSettingsContent(isSilenced = !enabled) val newNotificationSettingsContent = LocalNotificationSettingsContent(isSilenced = !enabled)
session.accountDataService().updateUserAccountData( session.accountDataService().updateUserAccountData(
UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId, UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId,

View file

@ -96,10 +96,12 @@ class SessionOverviewViewModel @AssistedInject constructor(
} }
private fun observeNotificationsStatus(deviceId: String) { private fun observeNotificationsStatus(deviceId: String) {
getNotificationsStatusUseCase.execute(deviceId) activeSessionHolder.getSafeActiveSession()?.let { session ->
getNotificationsStatusUseCase.execute(session, deviceId)
.onEach { setState { copy(notificationsStatus = it) } } .onEach { setState { copy(notificationsStatus = it) } }
.launchIn(viewModelScope) .launchIn(viewModelScope)
} }
}
override fun handle(action: SessionOverviewAction) { override fun handle(action: SessionOverviewAction) {
when (action) { when (action) {

View file

@ -35,7 +35,7 @@ class DisableNotificationsForCurrentSessionUseCase @Inject constructor(
suspend fun execute() { suspend fun execute() {
val session = activeSessionHolder.getSafeActiveSession() ?: return val session = activeSessionHolder.getSafeActiveSession() ?: return
val deviceId = session.sessionParams.deviceId ?: return val deviceId = session.sessionParams.deviceId ?: return
if (checkIfCanTogglePushNotificationsViaPusherUseCase.execute()) { if (checkIfCanTogglePushNotificationsViaPusherUseCase.execute(session)) {
togglePushNotificationUseCase.execute(deviceId, enabled = false) togglePushNotificationUseCase.execute(deviceId, enabled = false)
} else { } else {
unifiedPushHelper.unregister(pushersManager) unifiedPushHelper.unregister(pushersManager)

View file

@ -44,8 +44,8 @@ class EnableNotificationsForCurrentSessionUseCase @Inject constructor(
registerPusher(fragmentActivity) registerPusher(fragmentActivity)
} }
if (checkIfCanTogglePushNotificationsViaPusherUseCase.execute()) {
val session = activeSessionHolder.getSafeActiveSession() ?: return val session = activeSessionHolder.getSafeActiveSession() ?: return
if (checkIfCanTogglePushNotificationsViaPusherUseCase.execute(session)) {
val deviceId = session.sessionParams.deviceId ?: return val deviceId = session.sessionParams.deviceId ?: return
togglePushNotificationUseCase.execute(deviceId, enabled = true) togglePushNotificationUseCase.execute(deviceId, enabled = true)
} }