diff --git a/changelog.d/7261.wip b/changelog.d/7261.wip
new file mode 100644
index 0000000000..f7063fcc1b
--- /dev/null
+++ b/changelog.d/7261.wip
@@ -0,0 +1 @@
+Adds pusher toggle setting to device manager v2
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 63a469a8da..63c1f8a8bb 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3304,6 +3304,8 @@
Sign out of this sessionSession detailsApplication, device, and activity information.
+ Push notifications
+ Receive push notifications on this session.Session nameSession IDLast activity
diff --git a/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml b/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml
index d3884f247d..6428cd6eac 100644
--- a/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml
+++ b/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml
@@ -6,4 +6,10 @@
+
+
+
+
+
+
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
index d7958ea3cd..6a27f7af61 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
@@ -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.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt
index 082b5b63eb..e89cfa508c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt
@@ -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()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
index 4528c95e69..37c1c0c3ad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
@@ -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
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/TogglePusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/TogglePusherTask.kt
new file mode 100644
index 0000000000..87836e1c76
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/TogglePusherTask.kt
@@ -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 {
+ 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) }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersServiceTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersServiceTest.kt
new file mode 100644
index 0000000000..a00ac3a17d
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersServiceTest.kt
@@ -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)
+ }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultTogglePusherTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultTogglePusherTaskTest.kt
new file mode 100644
index 0000000000..3c54f6f1e1
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultTogglePusherTaskTest.kt
@@ -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()
+ .givenEqualTo(PusherEntityFields.PUSH_KEY, jsonPusher.pushKey)
+ .givenFindFirst(pusherEntity)
+
+ togglePusherTask.execute(params)
+
+ val expectedPayload = jsonPusher.copy(enabled = true)
+ pushersAPI.verifySetPusher(expectedPayload)
+ monarchy.verifyInsertOrUpdate {
+ withArg { actual ->
+ actual.enabled shouldBeEqualTo true
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeAddPusherTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeAddPusherTask.kt
new file mode 100644
index 0000000000..16cdd7a626
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeAddPusherTask.kt
@@ -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()
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetPushersTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetPushersTask.kt
new file mode 100644
index 0000000000..d5a41bb0e0
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetPushersTask.kt
@@ -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()
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRemovePusherTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRemovePusherTask.kt
new file mode 100644
index 0000000000..55a7607a03
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRemovePusherTask.kt
@@ -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()
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTaskExecutor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTaskExecutor.kt
new file mode 100644
index 0000000000..543dda8a4f
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTaskExecutor.kt
@@ -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()
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTogglePusherTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTogglePusherTask.kt
new file mode 100644
index 0000000000..b1e059a40e
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTogglePusherTask.kt
@@ -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()
+ coVerify { execute(capture(slot)) }
+ val params = slot.captured
+ params.pusher.pushKey shouldBeEqualTo pusher.pushKey
+ params.enable shouldBeEqualTo enable
+ }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePushGatewayNotifyTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePushGatewayNotifyTask.kt
new file mode 100644
index 0000000000..46a106dcb2
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePushGatewayNotifyTask.kt
@@ -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()
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherFixture.kt
new file mode 100644
index 0000000000..0ac7885062
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherFixture.kt
@@ -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,
+ )
+}
diff --git a/vector/build.gradle b/vector/build.gradle
index 76f32a34db..37a98d8242 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -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"
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt
index 42e79ac89f..9a92d5b629 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt
@@ -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()
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewEntrySwitchView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewEntrySwitchView.kt
new file mode 100644
index 0000000000..bbefd31dfe
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewEntrySwitchView.kt
@@ -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)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
index 58b0a13706..7510880087 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
@@ -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) {
+ 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
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
index bd5c7725eb..a7b0435e29 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
@@ -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)
+ }
+ }
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
index 07423888b5..c2d4a858b3 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
@@ -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 = Uninitialized,
val isLoading: Boolean = false,
+ val pushers: Async> = Uninitialized,
) : MavericksState {
constructor(args: SessionOverviewArgs) : this(
deviceId = args.deviceId
diff --git a/vector/src/main/res/layout/fragment_session_overview.xml b/vector/src/main/res/layout/fragment_session_overview.xml
index 0a9dd61fe0..80ad744d01 100644
--- a/vector/src/main/res/layout/fragment_session_overview.xml
+++ b/vector/src/main/res/layout/fragment_session_overview.xml
@@ -31,6 +31,16 @@
app:sessionOverviewEntryDescription="@string/device_manager_session_details_description"
app:sessionOverviewEntryTitle="@string/device_manager_session_details_title" />
+
+