Adds Push Notification toggle to Device Manager (#7261)
* 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 * Fixes post merge errors * Fixes legal copies * Removes unused imports * Fixes lint errors * Fixes test errors * Fixes error * Fixes error * Fixes error * Fixes error * Fixes error
This commit is contained in:
parent
a096ff03c8
commit
2fe636e93b
|
@ -0,0 +1 @@
|
|||
Adds pusher toggle setting to device manager v2
|
|
@ -3304,6 +3304,8 @@
|
|||
<string name="device_manager_session_overview_signout">Sign out of this session</string>
|
||||
<string name="device_manager_session_details_title">Session details</string>
|
||||
<string name="device_manager_session_details_description">Application, device, and activity information.</string>
|
||||
<string name="device_manager_push_notifications_title">Push notifications</string>
|
||||
<string name="device_manager_push_notifications_description">Receive push notifications on this session.</string>
|
||||
<string name="device_manager_session_details_session_name">Session name</string>
|
||||
<string name="device_manager_session_details_session_id">Session ID</string>
|
||||
<string name="device_manager_session_details_session_last_activity">Last activity</string>
|
||||
|
|
|
@ -6,4 +6,10 @@
|
|||
<attr name="sessionOverviewEntryDescription" format="string" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="SessionOverviewEntrySwitchView">
|
||||
<attr name="sessionOverviewEntrySwitchTitle" format="string" />
|
||||
<attr name="sessionOverviewEntrySwitchDescription" format="string" />
|
||||
<attr name="sessionOverviewEntrySwitchEnabled" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -67,6 +67,14 @@ interface PushersService {
|
|||
append: Boolean = true
|
||||
)
|
||||
|
||||
/**
|
||||
* Enables or disables a registered pusher.
|
||||
*
|
||||
* @param pusher The pusher being toggled
|
||||
* @param enable Whether the pusher should be enabled or disabled
|
||||
*/
|
||||
suspend fun togglePusher(pusher: Pusher, enable: Boolean)
|
||||
|
||||
/**
|
||||
* Directly ask the push gateway to send a push to this device.
|
||||
* If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
|
||||
|
|
|
@ -42,6 +42,7 @@ internal class DefaultPushersService @Inject constructor(
|
|||
private val getPusherTask: GetPushersTask,
|
||||
private val pushGatewayNotifyTask: PushGatewayNotifyTask,
|
||||
private val addPusherTask: AddPusherTask,
|
||||
private val togglePusherTask: TogglePusherTask,
|
||||
private val removePusherTask: RemovePusherTask,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : PushersService {
|
||||
|
@ -108,6 +109,24 @@ internal class DefaultPushersService @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun togglePusher(pusher: Pusher, enable: Boolean) {
|
||||
togglePusherTask.execute(TogglePusherTask.Params(pusher.toJsonPusher(), enable))
|
||||
}
|
||||
|
||||
private fun Pusher.toJsonPusher() = JsonPusher(
|
||||
pushKey = pushKey,
|
||||
kind = kind,
|
||||
appId = appId,
|
||||
appDisplayName = appDisplayName,
|
||||
deviceDisplayName = deviceDisplayName,
|
||||
profileTag = profileTag,
|
||||
lang = lang,
|
||||
data = JsonPusherData(data.url, data.format),
|
||||
append = false,
|
||||
enabled = enabled,
|
||||
deviceId = deviceId,
|
||||
)
|
||||
|
||||
private fun enqueueAddPusher(pusher: JsonPusher): UUID {
|
||||
val params = AddPusherWorker.Params(sessionId, pusher)
|
||||
val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddPusherWorker>()
|
||||
|
|
|
@ -68,6 +68,9 @@ internal abstract class PushersModule {
|
|||
@Binds
|
||||
abstract fun bindAddPusherTask(task: DefaultAddPusherTask): AddPusherTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindTogglePusherTask(task: DefaultTogglePusherTask): TogglePusherTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindRemovePusherTask(task: DefaultRemovePusherTask): RemovePusherTask
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.session.pushers
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.matrix.android.sdk.internal.database.model.PusherEntity
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.RequestExecutor
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface TogglePusherTask : Task<TogglePusherTask.Params, Unit> {
|
||||
data class Params(val pusher: JsonPusher, val enable: Boolean)
|
||||
}
|
||||
|
||||
internal class DefaultTogglePusherTask @Inject constructor(
|
||||
private val pushersAPI: PushersAPI,
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val requestExecutor: RequestExecutor,
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : TogglePusherTask {
|
||||
|
||||
override suspend fun execute(params: TogglePusherTask.Params) {
|
||||
val pusher = params.pusher.copy(enabled = params.enable)
|
||||
|
||||
requestExecutor.executeRequest(globalErrorReceiver) {
|
||||
pushersAPI.setPusher(pusher)
|
||||
}
|
||||
|
||||
monarchy.awaitTransaction { realm ->
|
||||
val entity = PusherEntity.where(realm, params.pusher.pushKey).findFirst()
|
||||
entity?.apply { enabled = params.enable }?.let { realm.insertOrUpdate(it) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright 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.session.pushers
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.test.fakes.FakeAddPusherTask
|
||||
import org.matrix.android.sdk.test.fakes.FakeGetPushersTask
|
||||
import org.matrix.android.sdk.test.fakes.FakeMonarchy
|
||||
import org.matrix.android.sdk.test.fakes.FakeRemovePusherTask
|
||||
import org.matrix.android.sdk.test.fakes.FakeTaskExecutor
|
||||
import org.matrix.android.sdk.test.fakes.FakeTogglePusherTask
|
||||
import org.matrix.android.sdk.test.fakes.FakeWorkManagerProvider
|
||||
import org.matrix.android.sdk.test.fakes.internal.FakePushGatewayNotifyTask
|
||||
import org.matrix.android.sdk.test.fixtures.PusherFixture
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class DefaultPushersServiceTest {
|
||||
|
||||
private val workManagerProvider = FakeWorkManagerProvider()
|
||||
private val monarchy = FakeMonarchy()
|
||||
private val sessionId = ""
|
||||
private val getPushersTask = FakeGetPushersTask()
|
||||
private val pushGatewayNotifyTask = FakePushGatewayNotifyTask()
|
||||
private val addPusherTask = FakeAddPusherTask()
|
||||
private val togglePusherTask = FakeTogglePusherTask()
|
||||
private val removePusherTask = FakeRemovePusherTask()
|
||||
private val taskExecutor = FakeTaskExecutor()
|
||||
|
||||
private val pushersService = DefaultPushersService(
|
||||
workManagerProvider.instance,
|
||||
monarchy.instance,
|
||||
sessionId,
|
||||
getPushersTask,
|
||||
pushGatewayNotifyTask,
|
||||
addPusherTask,
|
||||
togglePusherTask,
|
||||
removePusherTask,
|
||||
taskExecutor.instance,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `when togglePusher, then execute task`() = runTest {
|
||||
val pusher = PusherFixture.aPusher()
|
||||
val enable = true
|
||||
|
||||
pushersService.togglePusher(pusher, enable)
|
||||
|
||||
togglePusherTask.verifyExecution(pusher, enable)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 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.session.pushers
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.internal.database.model.PusherEntity
|
||||
import org.matrix.android.sdk.internal.database.model.PusherEntityFields
|
||||
import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver
|
||||
import org.matrix.android.sdk.test.fakes.FakeMonarchy
|
||||
import org.matrix.android.sdk.test.fakes.FakePushersAPI
|
||||
import org.matrix.android.sdk.test.fakes.FakeRequestExecutor
|
||||
import org.matrix.android.sdk.test.fakes.givenEqualTo
|
||||
import org.matrix.android.sdk.test.fakes.givenFindFirst
|
||||
import org.matrix.android.sdk.test.fixtures.JsonPusherFixture.aJsonPusher
|
||||
import org.matrix.android.sdk.test.fixtures.PusherEntityFixture.aPusherEntity
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class DefaultTogglePusherTaskTest {
|
||||
|
||||
private val pushersAPI = FakePushersAPI()
|
||||
private val monarchy = FakeMonarchy()
|
||||
private val requestExecutor = FakeRequestExecutor()
|
||||
private val globalErrorReceiver = FakeGlobalErrorReceiver()
|
||||
|
||||
private val togglePusherTask = DefaultTogglePusherTask(pushersAPI, monarchy.instance, requestExecutor, globalErrorReceiver)
|
||||
|
||||
@Test
|
||||
fun `execution toggles enable on both local and remote`() = runTest {
|
||||
val jsonPusher = aJsonPusher(enabled = false)
|
||||
val params = TogglePusherTask.Params(aJsonPusher(), true)
|
||||
|
||||
val pusherEntity = aPusherEntity(enabled = false)
|
||||
monarchy.givenWhere<PusherEntity>()
|
||||
.givenEqualTo(PusherEntityFields.PUSH_KEY, jsonPusher.pushKey)
|
||||
.givenFindFirst(pusherEntity)
|
||||
|
||||
togglePusherTask.execute(params)
|
||||
|
||||
val expectedPayload = jsonPusher.copy(enabled = true)
|
||||
pushersAPI.verifySetPusher(expectedPayload)
|
||||
monarchy.verifyInsertOrUpdate<PusherEntity> {
|
||||
withArg { actual ->
|
||||
actual.enabled shouldBeEqualTo true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 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.test.fakes
|
||||
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.internal.session.pushers.AddPusherTask
|
||||
|
||||
class FakeAddPusherTask : AddPusherTask by mockk()
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 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.test.fakes
|
||||
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.internal.session.pushers.GetPushersTask
|
||||
|
||||
class FakeGetPushersTask : GetPushersTask by mockk()
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 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.test.fakes
|
||||
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.internal.session.pushers.RemovePusherTask
|
||||
|
||||
class FakeRemovePusherTask : RemovePusherTask by mockk()
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 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.test.fakes
|
||||
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
|
||||
internal class FakeTaskExecutor {
|
||||
|
||||
val instance: TaskExecutor = mockk()
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 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.test.fakes
|
||||
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.matrix.android.sdk.api.session.pushers.Pusher
|
||||
import org.matrix.android.sdk.internal.session.pushers.TogglePusherTask
|
||||
|
||||
class FakeTogglePusherTask : TogglePusherTask by mockk(relaxed = true) {
|
||||
|
||||
fun verifyExecution(pusher: Pusher, enable: Boolean) {
|
||||
val slot = slot<TogglePusherTask.Params>()
|
||||
coVerify { execute(capture(slot)) }
|
||||
val params = slot.captured
|
||||
params.pusher.pushKey shouldBeEqualTo pusher.pushKey
|
||||
params.enable shouldBeEqualTo enable
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 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.test.fakes.internal
|
||||
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.internal.session.pushers.gateway.PushGatewayNotifyTask
|
||||
|
||||
class FakePushGatewayNotifyTask : PushGatewayNotifyTask by mockk()
|
50
matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherFixture.kt
vendored
Normal file
50
matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherFixture.kt
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 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.test.fixtures
|
||||
|
||||
import org.matrix.android.sdk.api.session.pushers.Pusher
|
||||
import org.matrix.android.sdk.api.session.pushers.PusherData
|
||||
import org.matrix.android.sdk.api.session.pushers.PusherState
|
||||
|
||||
object PusherFixture {
|
||||
|
||||
fun aPusher(
|
||||
pushKey: String = "",
|
||||
kind: String = "",
|
||||
appId: String = "",
|
||||
appDisplayName: String? = "",
|
||||
deviceDisplayName: String? = "",
|
||||
profileTag: String? = null,
|
||||
lang: String? = "",
|
||||
data: PusherData = PusherData("f.o/_matrix/push/v1/notify", ""),
|
||||
enabled: Boolean = true,
|
||||
deviceId: String? = "",
|
||||
state: PusherState = PusherState.REGISTERED,
|
||||
) = Pusher(
|
||||
pushKey,
|
||||
kind,
|
||||
appId,
|
||||
appDisplayName,
|
||||
deviceDisplayName,
|
||||
profileTag,
|
||||
lang,
|
||||
data,
|
||||
enabled,
|
||||
deviceId,
|
||||
state,
|
||||
)
|
||||
}
|
|
@ -296,6 +296,7 @@ dependencies {
|
|||
// Plant Timber tree for test
|
||||
testImplementation libs.tests.timberJunitRule
|
||||
testImplementation libs.airbnb.mavericksTesting
|
||||
testImplementation libs.androidx.coreTesting
|
||||
testImplementation(libs.jetbrains.coroutinesTest) {
|
||||
exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
|
||||
}
|
||||
|
|
|
@ -19,9 +19,14 @@ package im.vector.app.features.settings.devices.v2.overview
|
|||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class SessionOverviewAction : VectorViewModelAction {
|
||||
|
||||
object VerifySession : SessionOverviewAction()
|
||||
object SignoutOtherSession : SessionOverviewAction()
|
||||
object SsoAuthDone : SessionOverviewAction()
|
||||
data class PasswordAuthDone(val password: String) : SessionOverviewAction()
|
||||
object ReAuthCancelled : SessionOverviewAction()
|
||||
data class TogglePushNotifications(
|
||||
val deviceId: String,
|
||||
val enabled: Boolean,
|
||||
) : SessionOverviewAction()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.overview
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.CompoundButton
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.res.use
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.setAttributeBackground
|
||||
import im.vector.app.databinding.ViewSessionOverviewEntrySwitchBinding
|
||||
|
||||
class SessionOverviewEntrySwitchView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val binding = ViewSessionOverviewEntrySwitchBinding.inflate(
|
||||
LayoutInflater.from(context),
|
||||
this
|
||||
)
|
||||
|
||||
init {
|
||||
initBackground()
|
||||
context.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.SessionOverviewEntrySwitchView,
|
||||
0,
|
||||
0
|
||||
).use {
|
||||
setTitle(it)
|
||||
setDescription(it)
|
||||
setSwitchedEnabled(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initBackground() {
|
||||
binding.root.setAttributeBackground(android.R.attr.selectableItemBackground)
|
||||
}
|
||||
|
||||
private fun setTitle(typedArray: TypedArray) {
|
||||
val title = typedArray.getString(R.styleable.SessionOverviewEntrySwitchView_sessionOverviewEntrySwitchTitle)
|
||||
binding.sessionsOverviewEntryTitle.text = title
|
||||
}
|
||||
|
||||
private fun setDescription(typedArray: TypedArray) {
|
||||
val description = typedArray.getString(R.styleable.SessionOverviewEntrySwitchView_sessionOverviewEntrySwitchDescription)
|
||||
binding.sessionsOverviewEntryDescription.text = description
|
||||
}
|
||||
|
||||
private fun setSwitchedEnabled(typedArray: TypedArray) {
|
||||
val enabled = typedArray.getBoolean(R.styleable.SessionOverviewEntrySwitchView_sessionOverviewEntrySwitchEnabled, true)
|
||||
binding.sessionsOverviewEntrySwitch.isChecked = enabled
|
||||
}
|
||||
|
||||
fun setChecked(checked: Boolean) {
|
||||
binding.sessionsOverviewEntrySwitch.isChecked = checked
|
||||
}
|
||||
|
||||
fun setOnCheckedChangeListener(listener: CompoundButton.OnCheckedChangeListener?) {
|
||||
binding.sessionsOverviewEntrySwitch.setOnCheckedChangeListener(listener)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
binding.sessionsOverviewEntrySwitch.setOnCheckedChangeListener(null)
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
|
@ -41,12 +42,14 @@ import im.vector.app.core.resources.StringProvider
|
|||
import im.vector.app.databinding.FragmentSessionOverviewBinding
|
||||
import im.vector.app.features.auth.ReAuthActivity
|
||||
import im.vector.app.features.crypto.recover.SetupMode
|
||||
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
|
||||
import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet
|
||||
import im.vector.app.features.workers.signout.SignOutUiWorker
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||
import org.matrix.android.sdk.api.session.pushers.Pusher
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -174,9 +177,14 @@ class SessionOverviewFragment :
|
|||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
updateToolbar(state)
|
||||
updateEntryDetails(state.deviceId)
|
||||
updateSessionInfo(state)
|
||||
updateLoading(state.isLoading)
|
||||
updatePushNotificationToggle(state.deviceId, state.pushers.invoke().orEmpty())
|
||||
if (state.deviceInfo is Success) {
|
||||
renderSessionInfo(state.isCurrentSessionTrusted, state.deviceInfo.invoke())
|
||||
} else {
|
||||
hideSessionInfo()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateToolbar(viewState: SessionOverviewViewState) {
|
||||
|
@ -189,12 +197,6 @@ class SessionOverviewFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateEntryDetails(deviceId: String) {
|
||||
views.sessionOverviewEntryDetails.setOnClickListener {
|
||||
viewNavigator.goToSessionDetails(requireContext(), deviceId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSessionInfo(viewState: SessionOverviewViewState) {
|
||||
if (viewState.deviceInfo is Success) {
|
||||
views.sessionOverviewInfo.isVisible = true
|
||||
|
@ -217,6 +219,35 @@ class SessionOverviewFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun updatePushNotificationToggle(deviceId: String, pushers: List<Pusher>) {
|
||||
views.sessionOverviewPushNotifications.apply {
|
||||
if (pushers.isEmpty()) {
|
||||
isVisible = false
|
||||
} else {
|
||||
val allPushersAreEnabled = pushers.all { it.enabled }
|
||||
setOnCheckedChangeListener(null)
|
||||
setChecked(allPushersAreEnabled)
|
||||
post {
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
viewModel.handle(SessionOverviewAction.TogglePushNotifications(deviceId, isChecked))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderSessionInfo(isCurrentSession: Boolean, deviceFullInfo: DeviceFullInfo) {
|
||||
views.sessionOverviewInfo.isVisible = true
|
||||
val viewState = SessionInfoViewState(
|
||||
isCurrentSession = isCurrentSession,
|
||||
deviceFullInfo = deviceFullInfo,
|
||||
isDetailsButtonVisible = false,
|
||||
isLearnMoreLinkVisible = true,
|
||||
isLastSeenDetailsVisible = true,
|
||||
)
|
||||
views.sessionOverviewInfo.render(viewState, dateFormatter, drawableProvider, colorProvider, stringProvider)
|
||||
}
|
||||
|
||||
private fun updateLoading(isLoading: Boolean) {
|
||||
if (isLoading) {
|
||||
showLoading(null)
|
||||
|
@ -275,4 +306,8 @@ class SessionOverviewFragment :
|
|||
)
|
||||
SessionLearnMoreBottomSheet.show(childFragmentManager, args)
|
||||
}
|
||||
|
||||
private fun hideSessionInfo() {
|
||||
views.sessionOverviewInfo.isGone = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,14 +43,17 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
|||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
import timber.log.Timber
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import kotlin.coroutines.Continuation
|
||||
|
||||
class SessionOverviewViewModel @AssistedInject constructor(
|
||||
@Assisted val initialState: SessionOverviewViewState,
|
||||
private val session: Session,
|
||||
private val stringProvider: StringProvider,
|
||||
private val getDeviceFullInfoUseCase: GetDeviceFullInfoUseCase,
|
||||
private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase,
|
||||
|
@ -73,6 +76,7 @@ class SessionOverviewViewModel @AssistedInject constructor(
|
|||
init {
|
||||
observeSessionInfo(initialState.deviceId)
|
||||
observeCurrentSessionInfo()
|
||||
observePushers(initialState.deviceId)
|
||||
}
|
||||
|
||||
private fun observeSessionInfo(deviceId: String) {
|
||||
|
@ -94,6 +98,13 @@ class SessionOverviewViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun observePushers(deviceId: String) {
|
||||
session.flow()
|
||||
.livePushers()
|
||||
.map { it.filter { pusher -> pusher.deviceId == deviceId } }
|
||||
.execute { copy(pushers = it) }
|
||||
}
|
||||
|
||||
override fun handle(action: SessionOverviewAction) {
|
||||
when (action) {
|
||||
is SessionOverviewAction.VerifySession -> handleVerifySessionAction()
|
||||
|
@ -101,6 +112,7 @@ class SessionOverviewViewModel @AssistedInject constructor(
|
|||
SessionOverviewAction.SsoAuthDone -> handleSsoAuthDone()
|
||||
is SessionOverviewAction.PasswordAuthDone -> handlePasswordAuthDone(action)
|
||||
SessionOverviewAction.ReAuthCancelled -> handleReAuthCancelled()
|
||||
is SessionOverviewAction.TogglePushNotifications -> handleTogglePusherAction(action)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,4 +210,13 @@ class SessionOverviewViewModel @AssistedInject constructor(
|
|||
private fun handleReAuthCancelled() {
|
||||
pendingAuthHandler.reAuthCancelled()
|
||||
}
|
||||
|
||||
private fun handleTogglePusherAction(action: SessionOverviewAction.TogglePushNotifications) {
|
||||
viewModelScope.launch {
|
||||
val devicePushers = awaitState().pushers.invoke()?.filter { it.deviceId == action.deviceId }
|
||||
devicePushers?.forEach { pusher ->
|
||||
session.pushersService().togglePusher(pusher, action.enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,12 +20,14 @@ import com.airbnb.mvrx.Async
|
|||
import com.airbnb.mvrx.MavericksState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||
import org.matrix.android.sdk.api.session.pushers.Pusher
|
||||
|
||||
data class SessionOverviewViewState(
|
||||
val deviceId: String,
|
||||
val isCurrentSessionTrusted: Boolean = false,
|
||||
val deviceInfo: Async<DeviceFullInfo> = Uninitialized,
|
||||
val isLoading: Boolean = false,
|
||||
val pushers: Async<List<Pusher>> = Uninitialized,
|
||||
) : MavericksState {
|
||||
constructor(args: SessionOverviewArgs) : this(
|
||||
deviceId = args.deviceId
|
||||
|
|
|
@ -31,6 +31,16 @@
|
|||
app:sessionOverviewEntryDescription="@string/device_manager_session_details_description"
|
||||
app:sessionOverviewEntryTitle="@string/device_manager_session_details_title" />
|
||||
|
||||
<im.vector.app.features.settings.devices.v2.overview.SessionOverviewEntrySwitchView
|
||||
android:id="@+id/sessionOverviewPushNotifications"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/sessionOverviewEntryDetails"
|
||||
app:sessionOverviewEntrySwitchDescription="@string/device_manager_push_notifications_description"
|
||||
app:sessionOverviewEntrySwitchTitle="@string/device_manager_push_notifications_title" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/sessionOverviewSignout"
|
||||
style="@style/Widget.Vector.Button.Text.Destructive"
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sessionsOverviewEntryTitle"
|
||||
style="@style/TextAppearance.Vector.Subtitle.DevicesManagement"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="20dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Push notifications" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sessionsOverviewEntryDescription"
|
||||
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
app:layout_constraintEnd_toEndOf="@id/sessionsOverviewEntryTitle"
|
||||
app:layout_constraintStart_toStartOf="@id/sessionsOverviewEntryTitle"
|
||||
app:layout_constraintTop_toBottomOf="@id/sessionsOverviewEntryTitle"
|
||||
tools:text="Receive push notifications on this session." />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/sessionsOverviewEntrySwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/sessionsOverviewEntryDivider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="@drawable/divider_horizontal"
|
||||
app:layout_constraintEnd_toEndOf="@id/sessionsOverviewEntryTitle"
|
||||
app:layout_constraintStart_toStartOf="@id/sessionsOverviewEntryTitle"
|
||||
app:layout_constraintTop_toBottomOf="@id/sessionsOverviewEntryDescription" />
|
||||
|
||||
</merge>
|
|
@ -17,9 +17,10 @@
|
|||
package im.vector.app.features.settings.devices.v2.overview
|
||||
|
||||
import android.os.SystemClock
|
||||
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
|
||||
|
@ -28,8 +29,10 @@ import im.vector.app.features.settings.devices.v2.signout.SignoutSessionUseCase
|
|||
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
|
||||
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||
import im.vector.app.test.fakes.FakePendingAuthHandler
|
||||
import im.vector.app.test.fakes.FakeSession
|
||||
import im.vector.app.test.fakes.FakeStringProvider
|
||||
import im.vector.app.test.fakes.FakeVerificationService
|
||||
import im.vector.app.test.fixtures.PusherFixture.aPusher
|
||||
import im.vector.app.test.test
|
||||
import im.vector.app.test.testDispatcher
|
||||
import io.mockk.coEvery
|
||||
|
@ -52,10 +55,8 @@ 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"
|
||||
|
@ -69,12 +70,16 @@ class SessionOverviewViewModelTest {
|
|||
@get:Rule
|
||||
val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher)
|
||||
|
||||
@get:Rule
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
private val args = SessionOverviewArgs(
|
||||
deviceId = A_SESSION_ID_1
|
||||
)
|
||||
private val fakeSession = FakeSession()
|
||||
private val getDeviceFullInfoUseCase = mockk<GetDeviceFullInfoUseCase>(relaxed = true)
|
||||
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||
private val fakeStringProvider = FakeStringProvider()
|
||||
private val getDeviceFullInfoUseCase = mockk<GetDeviceFullInfoUseCase>()
|
||||
private val checkIfCurrentSessionCanBeVerifiedUseCase = mockk<CheckIfCurrentSessionCanBeVerifiedUseCase>()
|
||||
private val signoutSessionUseCase = mockk<SignoutSessionUseCase>()
|
||||
private val interceptSignoutFlowResponseUseCase = mockk<InterceptSignoutFlowResponseUseCase>()
|
||||
|
@ -83,6 +88,7 @@ class SessionOverviewViewModelTest {
|
|||
|
||||
private fun createViewModel() = SessionOverviewViewModel(
|
||||
initialState = SessionOverviewViewState(args),
|
||||
session = fakeSession,
|
||||
stringProvider = fakeStringProvider.instance,
|
||||
getDeviceFullInfoUseCase = getDeviceFullInfoUseCase,
|
||||
checkIfCurrentSessionCanBeVerifiedUseCase = checkIfCurrentSessionCanBeVerifiedUseCase,
|
||||
|
@ -108,8 +114,7 @@ class SessionOverviewViewModelTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `given the viewModel has been initialized then viewState is updated with session info and current session verification status`() {
|
||||
// Given
|
||||
fun `given the viewModel has been initialized then viewState is updated with session info`() {
|
||||
val deviceFullInfo = mockk<DeviceFullInfo>()
|
||||
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo)
|
||||
givenCurrentSessionIsTrusted()
|
||||
|
@ -117,12 +122,11 @@ class SessionOverviewViewModelTest {
|
|||
deviceId = A_SESSION_ID_1,
|
||||
deviceInfo = Success(deviceFullInfo),
|
||||
isCurrentSessionTrusted = true,
|
||||
pushers = Loading(),
|
||||
)
|
||||
|
||||
// When
|
||||
val viewModel = createViewModel()
|
||||
|
||||
// Then
|
||||
viewModel.test()
|
||||
.assertLatestState { state -> state == expectedState }
|
||||
.finish()
|
||||
|
@ -199,110 +203,6 @@ 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,
|
||||
)
|
||||
|
||||
// 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,
|
||||
)
|
||||
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,
|
||||
)
|
||||
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
|
||||
|
@ -447,4 +347,30 @@ class SessionOverviewViewModelTest {
|
|||
every { deviceFullInfo.roomEncryptionTrustLevel } returns RoomEncryptionTrustLevel.Trusted
|
||||
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_2) } returns flowOf(deviceFullInfo)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when viewModel init, then observe pushers and emit to state`() {
|
||||
val pushers = listOf(aPusher(deviceId = A_SESSION_ID_1))
|
||||
fakeSession.pushersService().givenPushersLive(pushers)
|
||||
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.test()
|
||||
.assertLatestState { state -> state.pushers.invoke() == pushers }
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when handle TogglePushNotifications, then toggle enabled for device pushers`() {
|
||||
val pushers = listOf(
|
||||
aPusher(deviceId = A_SESSION_ID_1, enabled = false),
|
||||
aPusher(deviceId = "another id", enabled = false)
|
||||
)
|
||||
fakeSession.pushersService().givenPushersLive(pushers)
|
||||
|
||||
val viewModel = createViewModel()
|
||||
viewModel.handle(SessionOverviewAction.TogglePushNotifications(A_SESSION_ID_1, true))
|
||||
|
||||
fakeSession.pushersService().verifyOnlyTogglePusherCalled(pushers.first(), true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,30 @@
|
|||
|
||||
package im.vector.app.test.fakes
|
||||
|
||||
import androidx.lifecycle.liveData
|
||||
import io.mockk.Ordering
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.verify
|
||||
import org.matrix.android.sdk.api.session.pushers.HttpPusher
|
||||
import org.matrix.android.sdk.api.session.pushers.Pusher
|
||||
import org.matrix.android.sdk.api.session.pushers.PushersService
|
||||
|
||||
class FakePushersService : PushersService by mockk(relaxed = true) {
|
||||
|
||||
fun givenPushersLive(pushers: List<Pusher>) {
|
||||
every { getPushersLive() } returns liveData { emit(pushers) }
|
||||
}
|
||||
|
||||
fun verifyOnlyTogglePusherCalled(pusher: Pusher, enable: Boolean) {
|
||||
coVerify(ordering = Ordering.ALL) {
|
||||
getPushersLive() // verifies only getPushersLive and the following togglePusher was called
|
||||
togglePusher(pusher, enable)
|
||||
}
|
||||
}
|
||||
|
||||
fun verifyEnqueueAddHttpPusher(): HttpPusher {
|
||||
val httpPusherSlot = slot<HttpPusher>()
|
||||
verify { enqueueAddHttpPusher(capture(httpPusherSlot)) }
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.fixtures
|
||||
|
||||
import org.matrix.android.sdk.api.session.pushers.Pusher
|
||||
import org.matrix.android.sdk.api.session.pushers.PusherData
|
||||
import org.matrix.android.sdk.api.session.pushers.PusherState
|
||||
|
||||
object PusherFixture {
|
||||
|
||||
fun aPusher(
|
||||
pushKey: String = "",
|
||||
kind: String = "",
|
||||
appId: String = "",
|
||||
appDisplayName: String? = "",
|
||||
deviceDisplayName: String? = "",
|
||||
profileTag: String? = null,
|
||||
lang: String? = "",
|
||||
data: PusherData = PusherData("f.o/_matrix/push/v1/notify", ""),
|
||||
enabled: Boolean = true,
|
||||
deviceId: String? = "",
|
||||
state: PusherState = PusherState.REGISTERED,
|
||||
) = Pusher(
|
||||
pushKey,
|
||||
kind,
|
||||
appId,
|
||||
appDisplayName,
|
||||
deviceDisplayName,
|
||||
profileTag,
|
||||
lang,
|
||||
data,
|
||||
enabled,
|
||||
deviceId,
|
||||
state,
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue