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 session Session details Application, device, and activity information. + Push notifications + Receive push notifications on this session. Session name Session ID Last 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" /> + +