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
import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.util.Optional
/**
* This interface defines a method to retrieve the homeserver capabilities.
*/
@ -30,4 +33,9 @@ interface HomeServerCapabilitiesService {
* Get the HomeServer capabilities.
*/
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
import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.util.Optional
import javax.inject.Inject
internal class DefaultHomeServerCapabilitiesService @Inject constructor(
@ -33,4 +35,8 @@ internal class DefaultHomeServerCapabilitiesService @Inject constructor(
return homeServerCapabilitiesDataSource.getHomeServerCapabilities()
?: HomeServerCapabilities()
}
override fun getHomeServerCapabilitiesLive(): LiveData<Optional<HomeServerCapabilities>> {
return homeServerCapabilitiesDataSource.getHomeServerCapabilitiesLive()
}
}

View file

@ -16,9 +16,14 @@
package org.matrix.android.sdk.internal.session.homeserver
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import io.realm.kotlin.where
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.model.HomeServerCapabilitiesEntity
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
internal class HomeServerCapabilitiesDataSource @Inject constructor(
@SessionDatabase private val monarchy: Monarchy
@SessionDatabase private val monarchy: Monarchy,
) {
fun getHomeServerCapabilities(): HomeServerCapabilities? {
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 im.vector.app.ActiveSessionDataSource
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.services.GuardServiceStarter
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 dagger.hilt.android.qualifiers.ApplicationContext
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.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.settings.VectorPreferences
@ -32,8 +33,10 @@ class ConfigureAndStartSessionUseCase @Inject constructor(
private val webRtcCallManager: WebRtcCallManager,
private val updateMatrixClientInfoUseCase: UpdateMatrixClientInfoUseCase,
private val vectorPreferences: VectorPreferences,
private val enableNotificationsSettingUpdater: EnableNotificationsSettingUpdater,
) {
// TODO update unit tests
suspend fun execute(session: Session, startSyncing: Boolean = true) {
Timber.i("Configure and start session for ${session.myUserId}. startSyncing: $startSyncing")
session.open()
@ -46,5 +49,6 @@ class ConfigureAndStartSessionUseCase @Inject constructor(
if (vectorPreferences.isClientInfoRecordingEnabled()) {
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
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 javax.inject.Inject
class CheckIfCanTogglePushNotificationsViaAccountDataUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder,
) {
class CheckIfCanTogglePushNotificationsViaAccountDataUseCase @Inject constructor() {
fun execute(deviceId: String): Boolean {
return activeSessionHolder
.getSafeActiveSession()
?.accountDataService()
?.getUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId) != null
fun execute(session: Session, deviceId: String): Boolean {
return session
.accountDataService()
.getUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId) != null
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -44,8 +44,8 @@ class EnableNotificationsForCurrentSessionUseCase @Inject constructor(
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
togglePushNotificationUseCase.execute(deviceId, enabled = true)
}