mirror of
https://github.com/element-hq/element-android
synced 2024-11-23 18:05:36 +03:00
Makes "Enable Notifications for this session" respond to enabled value in pusher (#7281)
* Adds push notifications switch * Adds functionality to Push notification toggle * Adds DefaultPushersServiceTest for togglePusher * Adds DefaultTogglePusherTaskTest * Adds SessionOverviewViewModelTest for toggling pusher * Hides pusher toggle if there are no pushers of the device * Adds changelog file * Edits changelog file * Fixes copyrights * Unregisters checkedChangelistener in onDetachedFromWindow for switch view * Links notification settings toggle to pusher service * Adds changelog file * Adds error handling to VectorSettingsNotificationPreferenceFragment * Removes comment in FakePushersService * Fixes post merge errors * Fixes imports and improves string name * Fixes legal copies * Fixes kdoc punctuation * Fixes string error * Removes unused imports * Fixes lint errors * Fixes test errors * Fixes test errors * Fixes error * Fixes error * Fixes error * Fixes error * Fixes error * Adds lost tests * Adds PusherEntity migration * Fixes session overview layout overlap * Fixes switch being enabled by default * Binds entire view to toggle switch
This commit is contained in:
parent
cf9f30d95e
commit
9857fa6ca4
12 changed files with 303 additions and 2 deletions
1
changelog.d/7281.wip
Normal file
1
changelog.d/7281.wip
Normal file
|
@ -0,0 +1 @@
|
|||
Links "Enable Notifications for this session" setting to enabled value in pusher
|
|
@ -1666,6 +1666,7 @@
|
|||
<string name="create_new_room">Create New Room</string>
|
||||
<string name="create_new_space">Create New Space</string>
|
||||
<string name="error_no_network">No network. Please check your Internet connection.</string>
|
||||
<string name="error_check_network">Something went wrong. Please check your network connection and try again.</string>
|
||||
<string name="change_room_directory_network">"Change network"</string>
|
||||
<string name="please_wait">"Please wait…"</string>
|
||||
<string name="updating_your_data">Updating your data…</string>
|
||||
|
|
|
@ -54,6 +54,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034
|
|||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo036
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo037
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo038
|
||||
import org.matrix.android.sdk.internal.util.Normalizer
|
||||
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
||||
import javax.inject.Inject
|
||||
|
@ -62,7 +63,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||
private val normalizer: Normalizer
|
||||
) : MatrixRealmMigration(
|
||||
dbName = "Session",
|
||||
schemaVersion = 37L,
|
||||
schemaVersion = 38L,
|
||||
) {
|
||||
/**
|
||||
* Forces all RealmSessionStoreMigration instances to be equal.
|
||||
|
@ -109,5 +110,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||
if (oldVersion < 35) MigrateSessionTo035(realm).perform()
|
||||
if (oldVersion < 36) MigrateSessionTo036(realm).perform()
|
||||
if (oldVersion < 37) MigrateSessionTo037(realm).perform()
|
||||
if (oldVersion < 38) MigrateSessionTo038(realm).perform()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.PusherEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
internal class MigrateSessionTo038(realm: DynamicRealm) : RealmMigrator(realm, 38) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("PusherEntity")
|
||||
?.addField(PusherEntityFields.ENABLED, Boolean::class.java)
|
||||
?.addField(PusherEntityFields.DEVICE_ID, String::class.java)
|
||||
?.transform { obj -> obj.set(PusherEntityFields.ENABLED, true) }
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ import im.vector.app.core.resources.AppNameProvider
|
|||
import im.vector.app.core.resources.LocaleProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import org.matrix.android.sdk.api.session.pushers.HttpPusher
|
||||
import org.matrix.android.sdk.api.session.pushers.Pusher
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.abs
|
||||
|
@ -90,6 +91,18 @@ class PushersManager @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
fun getPusherForCurrentSession(): Pusher? {
|
||||
val session = activeSessionHolder.getSafeActiveSession() ?: return null
|
||||
val deviceId = session.sessionParams.deviceId
|
||||
return session.pushersService().getPushers().firstOrNull { it.deviceId == deviceId }
|
||||
}
|
||||
|
||||
suspend fun togglePusherForCurrentSession(enable: Boolean) {
|
||||
val session = activeSessionHolder.getSafeActiveSession() ?: return
|
||||
val pusher = getPusherForCurrentSession() ?: return
|
||||
session.pushersService().togglePusher(pusher, enable)
|
||||
}
|
||||
|
||||
suspend fun unregisterEmailPusher(email: String) {
|
||||
val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
|
||||
currentSession.pushersService().removeEmailPusher(email)
|
||||
|
|
|
@ -49,6 +49,7 @@ class SessionOverviewEntrySwitchView @JvmOverloads constructor(
|
|||
setTitle(it)
|
||||
setDescription(it)
|
||||
setSwitchedEnabled(it)
|
||||
setClickListener()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,10 +68,16 @@ class SessionOverviewEntrySwitchView @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
private fun setSwitchedEnabled(typedArray: TypedArray) {
|
||||
val enabled = typedArray.getBoolean(R.styleable.SessionOverviewEntrySwitchView_sessionOverviewEntrySwitchEnabled, true)
|
||||
val enabled = typedArray.getBoolean(R.styleable.SessionOverviewEntrySwitchView_sessionOverviewEntrySwitchEnabled, false)
|
||||
binding.sessionsOverviewEntrySwitch.isChecked = enabled
|
||||
}
|
||||
|
||||
private fun setClickListener() {
|
||||
binding.root.setOnClickListener {
|
||||
setChecked(!binding.sessionsOverviewEntrySwitch.isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
fun setChecked(checked: Boolean) {
|
||||
binding.sessionsOverviewEntrySwitch.isChecked = checked
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ import im.vector.app.features.settings.VectorPreferences
|
|||
import im.vector.app.features.settings.VectorSettingsBaseFragment
|
||||
import im.vector.app.features.settings.VectorSettingsFragmentInteractionListener
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
@ -104,6 +105,10 @@ class VectorSettingsNotificationPreferenceFragment :
|
|||
}
|
||||
|
||||
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY)?.let {
|
||||
pushersManager.getPusherForCurrentSession()?.let { pusher ->
|
||||
it.isChecked = pusher.enabled
|
||||
}
|
||||
|
||||
it.setTransactionalSwitchChangeListener(lifecycleScope) { isChecked ->
|
||||
if (isChecked) {
|
||||
unifiedPushHelper.register(requireActivity()) {
|
||||
|
@ -117,6 +122,16 @@ class VectorSettingsNotificationPreferenceFragment :
|
|||
}
|
||||
findPreference<VectorPreference>(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY)
|
||||
?.summary = unifiedPushHelper.getCurrentDistributorName()
|
||||
lifecycleScope.launch {
|
||||
val result = runCatching {
|
||||
pushersManager.togglePusherForCurrentSession(true)
|
||||
}
|
||||
|
||||
result.exceptionOrNull()?.let { _ ->
|
||||
Toast.makeText(context, R.string.error_check_network, Toast.LENGTH_SHORT).show()
|
||||
it.isChecked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unifiedPushHelper.unregister(pushersManager)
|
||||
|
|
|
@ -24,8 +24,12 @@ import im.vector.app.test.fakes.FakeLocaleProvider
|
|||
import im.vector.app.test.fakes.FakePushersService
|
||||
import im.vector.app.test.fakes.FakeSession
|
||||
import im.vector.app.test.fakes.FakeStringProvider
|
||||
import im.vector.app.test.fixtures.CredentialsFixture
|
||||
import im.vector.app.test.fixtures.CryptoDeviceInfoFixture.aCryptoDeviceInfo
|
||||
import im.vector.app.test.fixtures.PusherFixture
|
||||
import im.vector.app.test.fixtures.SessionParamsFixture
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo
|
||||
|
@ -82,4 +86,34 @@ class PushersManagerTest {
|
|||
val httpPusher = pushersService.verifyEnqueueAddHttpPusher()
|
||||
httpPusher shouldBeEqualTo expectedHttpPusher
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when getPusherForCurrentSession, then return pusher`() {
|
||||
val deviceId = "device_id"
|
||||
val sessionParams = SessionParamsFixture.aSessionParams(
|
||||
credentials = CredentialsFixture.aCredentials(deviceId = deviceId)
|
||||
)
|
||||
session.givenSessionParams(sessionParams)
|
||||
val expectedPusher = PusherFixture.aPusher(deviceId = deviceId)
|
||||
pushersService.givenGetPushers(listOf(expectedPusher))
|
||||
|
||||
val pusher = pushersManager.getPusherForCurrentSession()
|
||||
|
||||
pusher shouldBeEqualTo expectedPusher
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when togglePusherForCurrentSession, then do service toggle pusher`() = runTest {
|
||||
val deviceId = "device_id"
|
||||
val sessionParams = SessionParamsFixture.aSessionParams(
|
||||
credentials = CredentialsFixture.aCredentials(deviceId = deviceId)
|
||||
)
|
||||
session.givenSessionParams(sessionParams)
|
||||
val pusher = PusherFixture.aPusher(deviceId = deviceId)
|
||||
pushersService.givenGetPushers(listOf(pusher))
|
||||
|
||||
pushersManager.togglePusherForCurrentSession(true)
|
||||
|
||||
pushersService.verifyOnlyGetPushersAndTogglePusherCalled(pusher, true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
|||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.test.MavericksTestRule
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||
import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase
|
||||
import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase
|
||||
|
@ -55,8 +56,10 @@ import org.junit.Test
|
|||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import kotlin.coroutines.Continuation
|
||||
|
||||
private const val A_SESSION_ID_1 = "session-id-1"
|
||||
|
@ -203,6 +206,113 @@ class SessionOverviewViewModelTest {
|
|||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given another session and no reAuth is needed when handling signout action then signout process is performed`() {
|
||||
// Given
|
||||
val deviceFullInfo = mockk<DeviceFullInfo>()
|
||||
every { deviceFullInfo.isCurrentDevice } returns false
|
||||
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo)
|
||||
givenSignoutSuccess(A_SESSION_ID_1)
|
||||
every { refreshDevicesUseCase.execute() } just runs
|
||||
val signoutAction = SessionOverviewAction.SignoutOtherSession
|
||||
givenCurrentSessionIsTrusted()
|
||||
val expectedViewState = SessionOverviewViewState(
|
||||
deviceId = A_SESSION_ID_1,
|
||||
isCurrentSessionTrusted = true,
|
||||
deviceInfo = Success(deviceFullInfo),
|
||||
isLoading = false,
|
||||
pushers = Loading(),
|
||||
)
|
||||
|
||||
// When
|
||||
val viewModel = createViewModel()
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(signoutAction)
|
||||
|
||||
// Then
|
||||
viewModelTest
|
||||
.assertStatesChanges(
|
||||
expectedViewState,
|
||||
{ copy(isLoading = true) },
|
||||
{ copy(isLoading = false) }
|
||||
)
|
||||
.assertEvent { it is SessionOverviewViewEvent.SignoutSuccess }
|
||||
.finish()
|
||||
verify {
|
||||
refreshDevicesUseCase.execute()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given another session and server error during signout when handling signout action then signout process is performed`() {
|
||||
// Given
|
||||
val deviceFullInfo = mockk<DeviceFullInfo>()
|
||||
every { deviceFullInfo.isCurrentDevice } returns false
|
||||
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo)
|
||||
val serverError = Failure.OtherServerError(errorBody = "", httpCode = HttpsURLConnection.HTTP_UNAUTHORIZED)
|
||||
givenSignoutError(A_SESSION_ID_1, serverError)
|
||||
val signoutAction = SessionOverviewAction.SignoutOtherSession
|
||||
givenCurrentSessionIsTrusted()
|
||||
val expectedViewState = SessionOverviewViewState(
|
||||
deviceId = A_SESSION_ID_1,
|
||||
isCurrentSessionTrusted = true,
|
||||
deviceInfo = Success(deviceFullInfo),
|
||||
isLoading = false,
|
||||
pushers = Loading(),
|
||||
)
|
||||
fakeStringProvider.given(R.string.authentication_error, AUTH_ERROR_MESSAGE)
|
||||
|
||||
// When
|
||||
val viewModel = createViewModel()
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(signoutAction)
|
||||
|
||||
// Then
|
||||
viewModelTest
|
||||
.assertStatesChanges(
|
||||
expectedViewState,
|
||||
{ copy(isLoading = true) },
|
||||
{ copy(isLoading = false) }
|
||||
)
|
||||
.assertEvent { it is SessionOverviewViewEvent.SignoutError && it.error.message == AUTH_ERROR_MESSAGE }
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given another session and unexpected error during signout when handling signout action then signout process is performed`() {
|
||||
// Given
|
||||
val deviceFullInfo = mockk<DeviceFullInfo>()
|
||||
every { deviceFullInfo.isCurrentDevice } returns false
|
||||
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo)
|
||||
val error = Exception()
|
||||
givenSignoutError(A_SESSION_ID_1, error)
|
||||
val signoutAction = SessionOverviewAction.SignoutOtherSession
|
||||
givenCurrentSessionIsTrusted()
|
||||
val expectedViewState = SessionOverviewViewState(
|
||||
deviceId = A_SESSION_ID_1,
|
||||
isCurrentSessionTrusted = true,
|
||||
deviceInfo = Success(deviceFullInfo),
|
||||
isLoading = false,
|
||||
pushers = Loading(),
|
||||
)
|
||||
fakeStringProvider.given(R.string.matrix_error, AN_ERROR_MESSAGE)
|
||||
|
||||
// When
|
||||
val viewModel = createViewModel()
|
||||
val viewModelTest = viewModel.test()
|
||||
viewModel.handle(signoutAction)
|
||||
|
||||
// Then
|
||||
viewModelTest
|
||||
.assertStatesChanges(
|
||||
expectedViewState,
|
||||
{ copy(isLoading = true) },
|
||||
{ copy(isLoading = false) }
|
||||
)
|
||||
.assertEvent { it is SessionOverviewViewEvent.SignoutError && it.error.message == AN_ERROR_MESSAGE }
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given another session and reAuth is needed during signout when handling signout action then requestReAuth is sent and pending auth is stored`() {
|
||||
// Given
|
||||
|
|
|
@ -29,10 +29,21 @@ import org.matrix.android.sdk.api.session.pushers.PushersService
|
|||
|
||||
class FakePushersService : PushersService by mockk(relaxed = true) {
|
||||
|
||||
fun givenGetPushers(pushers: List<Pusher>) {
|
||||
every { getPushers() } returns pushers
|
||||
}
|
||||
|
||||
fun givenPushersLive(pushers: List<Pusher>) {
|
||||
every { getPushersLive() } returns liveData { emit(pushers) }
|
||||
}
|
||||
|
||||
fun verifyOnlyGetPushersAndTogglePusherCalled(pusher: Pusher, enable: Boolean) {
|
||||
coVerify(ordering = Ordering.ALL) {
|
||||
getPushers()
|
||||
togglePusher(pusher, enable)
|
||||
}
|
||||
}
|
||||
|
||||
fun verifyOnlyTogglePusherCalled(pusher: Pusher, enable: Boolean) {
|
||||
coVerify(ordering = Ordering.ALL) {
|
||||
getPushersLive() // verifies only getPushersLive and the following togglePusher was called
|
||||
|
|
38
vector/src/test/java/im/vector/app/test/fixtures/CredentialsFixture.kt
vendored
Normal file
38
vector/src/test/java/im/vector/app/test/fixtures/CredentialsFixture.kt
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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.fixtures
|
||||
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.data.DiscoveryInformation
|
||||
|
||||
object CredentialsFixture {
|
||||
fun aCredentials(
|
||||
userId: String = "",
|
||||
accessToken: String = "",
|
||||
refreshToken: String? = null,
|
||||
homeServer: String? = null,
|
||||
deviceId: String? = null,
|
||||
discoveryInformation: DiscoveryInformation? = null,
|
||||
) = Credentials(
|
||||
userId,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
homeServer,
|
||||
deviceId,
|
||||
discoveryInformation,
|
||||
)
|
||||
}
|
38
vector/src/test/java/im/vector/app/test/fixtures/SessionParamsFixture.kt
vendored
Normal file
38
vector/src/test/java/im/vector/app/test/fixtures/SessionParamsFixture.kt
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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.fixtures
|
||||
|
||||
import im.vector.app.test.fixtures.CredentialsFixture.aCredentials
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.api.auth.LoginType
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||
|
||||
object SessionParamsFixture {
|
||||
fun aSessionParams(
|
||||
credentials: Credentials = aCredentials(),
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig = mockk(relaxed = true),
|
||||
isTokenValid: Boolean = false,
|
||||
loginType: LoginType = LoginType.UNKNOWN,
|
||||
) = SessionParams(
|
||||
credentials,
|
||||
homeServerConnectionConfig,
|
||||
isTokenValid,
|
||||
loginType,
|
||||
)
|
||||
}
|
Loading…
Reference in a new issue