diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt index 0f297d14dd..5226d55b6b 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt @@ -29,6 +29,7 @@ import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.theme.ElementTheme import timber.log.Timber @ContributesNode(SessionScope::class) @@ -67,9 +68,17 @@ class PreferencesRootNode @AssistedInject constructor( plugins().forEach { it.onOpenAbout() } } - private fun onManageAccountClicked(activity: Activity, accountManagementUrl: String?) { - accountManagementUrl?.let { - activity.openUrlInChromeCustomTab(null, false, it) + private fun onManageAccountClicked( + activity: Activity, + url: String?, + isDark: Boolean, + ) { + url?.let { + activity.openUrlInChromeCustomTab( + null, + darkTheme = isDark, + url = it + ) } } @@ -81,6 +90,7 @@ class PreferencesRootNode @AssistedInject constructor( override fun View(modifier: Modifier) { val state = presenter.present() val activity = LocalContext.current as Activity + val isDark = ElementTheme.isLightTheme.not() PreferencesRootView( state = state, modifier = modifier, @@ -91,7 +101,7 @@ class PreferencesRootNode @AssistedInject constructor( onVerifyClicked = this::onVerifyClicked, onOpenDeveloperSettings = this::onOpenDeveloperSettings, onSuccessLogout = { onSuccessLogout(activity, it) }, - onManageAccountClicked = { onManageAccountClicked(activity, state.accountManagementUrl) }, + onManageAccountClicked = { onManageAccountClicked(activity, it, isDark) }, onOpenNotificationSettings = this::onOpenNotificationSettings ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt index 37ea66b31f..dac7ae3204 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt @@ -32,6 +32,7 @@ import io.element.android.libraries.designsystem.utils.collectSnackbarMessageAsS import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.getCurrentUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService @@ -74,9 +75,12 @@ class PreferencesRootPresenter @Inject constructor( val accountManagementUrl: MutableState = remember { mutableStateOf(null) } + val devicesManagementUrl: MutableState = remember { + mutableStateOf(null) + } LaunchedEffect(Unit) { - initAccountManagementUrl(accountManagementUrl) + initAccountManagementUrl(accountManagementUrl, devicesManagementUrl) } val logoutState = logoutPresenter.present() @@ -87,6 +91,7 @@ class PreferencesRootPresenter @Inject constructor( version = versionFormatter.get(), showCompleteVerification = showCompleteVerification, accountManagementUrl = accountManagementUrl.value, + devicesManagementUrl = devicesManagementUrl.value, showAnalyticsSettings = hasAnalyticsProviders, showDeveloperSettings = showDeveloperSettings, showNotificationSettings = showNotificationSettings.value, @@ -98,7 +103,11 @@ class PreferencesRootPresenter @Inject constructor( matrixUser.value = matrixClient.getCurrentUser() } - private fun CoroutineScope.initAccountManagementUrl(accountManagementUrl: MutableState) = launch { - accountManagementUrl.value = matrixClient.getAccountManagementUrl().getOrNull() + private fun CoroutineScope.initAccountManagementUrl( + accountManagementUrl: MutableState, + devicesManagementUrl: MutableState, + ) = launch { + accountManagementUrl.value = matrixClient.getAccountManagementUrl(AccountManagementAction.Profile).getOrNull() + devicesManagementUrl.value = matrixClient.getAccountManagementUrl(AccountManagementAction.SessionsList).getOrNull() } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt index 967450031a..accede5d6d 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt @@ -26,6 +26,7 @@ data class PreferencesRootState( val version: String, val showCompleteVerification: Boolean, val accountManagementUrl: String?, + val devicesManagementUrl: String?, val showAnalyticsSettings: Boolean, val showDeveloperSettings: Boolean, val showNotificationSettings: Boolean, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt index 8dd6b807f8..860c738687 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt @@ -26,6 +26,7 @@ fun aPreferencesRootState() = PreferencesRootState( version = "Version 1.1 (1)", showCompleteVerification = true, accountManagementUrl = "aUrl", + devicesManagementUrl = "anOtherUrl", showAnalyticsSettings = true, showDeveloperSettings = true, showNotificationSettings = true, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index a2449424e6..3d901c4617 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -23,8 +23,8 @@ import androidx.compose.material.icons.outlined.BugReport import androidx.compose.material.icons.outlined.DeveloperMode import androidx.compose.material.icons.outlined.Help import androidx.compose.material.icons.outlined.InsertChart -import androidx.compose.material.icons.outlined.ManageAccounts import androidx.compose.material.icons.outlined.Notifications +import androidx.compose.material.icons.outlined.OpenInNew import androidx.compose.material.icons.outlined.VerifiedUser import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -53,7 +53,7 @@ fun PreferencesRootView( state: PreferencesRootState, onBackPressed: () -> Unit, onVerifyClicked: () -> Unit, - onManageAccountClicked: () -> Unit, + onManageAccountClicked: (url: String) -> Unit, onOpenAnalytics: () -> Unit, onOpenRageShake: () -> Unit, onOpenAbout: () -> Unit, @@ -82,10 +82,11 @@ fun PreferencesRootView( } if (state.accountManagementUrl != null) { PreferenceText( - title = stringResource(id = CommonStrings.screen_settings_oidc_account), - icon = Icons.Outlined.ManageAccounts, - onClick = onManageAccountClicked, + title = stringResource(id = CommonStrings.action_manage_account), + icon = Icons.Outlined.OpenInNew, + onClick = { onManageAccountClicked(state.accountManagementUrl) }, ) + HorizontalDivider() } if (state.showAnalyticsSettings) { PreferenceText( @@ -94,7 +95,7 @@ fun PreferencesRootView( onClick = onOpenAnalytics, ) } - if(state.showNotificationSettings) { + if (state.showNotificationSettings) { PreferenceText( title = stringResource(id = CommonStrings.screen_notification_settings_title), icon = Icons.Outlined.Notifications, @@ -111,10 +112,19 @@ fun PreferencesRootView( icon = Icons.Outlined.Help, onClick = onOpenAbout, ) + HorizontalDivider() + if (state.devicesManagementUrl != null) { + PreferenceText( + title = stringResource(id = CommonStrings.action_manage_devices), + icon = Icons.Outlined.OpenInNew, + onClick = { onManageAccountClicked(state.devicesManagementUrl) }, + ) + HorizontalDivider() + } if (state.showDeveloperSettings) { DeveloperPreferencesView(onOpenDeveloperSettings) + HorizontalDivider() } - HorizontalDivider() LogoutPreferenceView( state = state.logoutState, onSuccessLogout = onSuccessLogout, diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt index f246e8c852..2237f717bd 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt @@ -73,6 +73,7 @@ class PreferencesRootPresenterTest { assertThat(loadedState.showDeveloperSettings).isEqualTo(true) assertThat(loadedState.showAnalyticsSettings).isEqualTo(false) assertThat(loadedState.accountManagementUrl).isNull() + assertThat(loadedState.devicesManagementUrl).isNull() } } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 63093064b6..611cb4303c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService +import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver @@ -66,7 +67,7 @@ interface MatrixClient : Closeable { suspend fun logout(): String? suspend fun loadUserDisplayName(): Result suspend fun loadUserAvatarURLString(): Result - suspend fun getAccountManagementUrl(): Result + suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result fun roomMembershipObserver(): RoomMembershipObserver diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/oidc/AccountManagementAction.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/oidc/AccountManagementAction.kt new file mode 100644 index 0000000000..ceb5f4fb71 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/oidc/AccountManagementAction.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023 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 io.element.android.libraries.matrix.api.oidc + +sealed interface AccountManagementAction { + data object Profile : AccountManagementAction + data object SessionsList : AccountManagementAction + data class SessionView(val deviceId: String) : AccountManagementAction + data class SessionEnd(val deviceId: String) : AccountManagementAction +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 090a0cf613..c4a3ab3ef2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.createroom.RoomVisibility import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService +import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver @@ -44,6 +45,7 @@ import io.element.android.libraries.matrix.impl.mapper.toSessionData import io.element.android.libraries.matrix.impl.media.RustMediaLoader import io.element.android.libraries.matrix.impl.notification.RustNotificationService import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService +import io.element.android.libraries.matrix.impl.oidc.toRustAction import io.element.android.libraries.matrix.impl.pushers.RustPushersService import io.element.android.libraries.matrix.impl.room.RoomContentForwarder import io.element.android.libraries.matrix.impl.room.RustMatrixRoom @@ -326,9 +328,10 @@ class RustMatrixClient constructor( return result } - override suspend fun getAccountManagementUrl(): Result = withContext(sessionDispatcher) { + override suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result = withContext(sessionDispatcher) { + val rustAction = action?.toRustAction() runCatching { - client.accountUrl() + client.accountUrl(rustAction) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementAction.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementAction.kt new file mode 100644 index 0000000000..2644ac0321 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementAction.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 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 io.element.android.libraries.matrix.impl.oidc + +import io.element.android.libraries.matrix.api.oidc.AccountManagementAction +import org.matrix.rustcomponents.sdk.AccountManagementAction as RustAccountManagementAction + +fun AccountManagementAction.toRustAction(): RustAccountManagementAction { + return when (this) { + AccountManagementAction.Profile -> RustAccountManagementAction.Profile + is AccountManagementAction.SessionEnd -> RustAccountManagementAction.SessionEnd(deviceId) + is AccountManagementAction.SessionView -> RustAccountManagementAction.SessionView(deviceId) + AccountManagementAction.SessionsList -> RustAccountManagementAction.SessionsList + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 9ef50ecda5..660c1e268a 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService +import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver @@ -129,7 +130,7 @@ class FakeMatrixClient( return userAvatarURLString } - override suspend fun getAccountManagementUrl(): Result { + override suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result { return accountManagementUrlString } override suspend fun uploadMedia(