mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 06:55:41 +00:00
Add a Push rules entry to the developer options
Add a "Push rules" item in the Notifications section of the developer options, above "Push history". It displays the raw push rules of the account, which were only available as a rageshake attachment so far. The content is read using NotificationSettingsService.getRawPushRules(), then rendered by the new FileViewerEntryPoint, so that the screen looks like the media viewer rendering a file, with Share and Download actions on a push_rules<userId>.json file. The user id contains a colon, which is not a valid file name character on all the file systems, so it is replaced by an underscore. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,7 @@ dependencies {
|
|||||||
implementation(projects.libraries.testtags)
|
implementation(projects.libraries.testtags)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(projects.libraries.matrixui)
|
implementation(projects.libraries.matrixui)
|
||||||
|
implementation(projects.libraries.mediaviewer.api)
|
||||||
implementation(projects.libraries.mediapickers.api)
|
implementation(projects.libraries.mediapickers.api)
|
||||||
implementation(projects.libraries.mediaupload.api)
|
implementation(projects.libraries.mediaupload.api)
|
||||||
implementation(projects.libraries.permissions.api)
|
implementation(projects.libraries.permissions.api)
|
||||||
@@ -108,6 +109,7 @@ dependencies {
|
|||||||
testImplementation(projects.libraries.push.test)
|
testImplementation(projects.libraries.push.test)
|
||||||
testImplementation(projects.libraries.pushstore.test)
|
testImplementation(projects.libraries.pushstore.test)
|
||||||
testImplementation(projects.libraries.roomselect.test)
|
testImplementation(projects.libraries.roomselect.test)
|
||||||
|
testImplementation(projects.libraries.mediaviewer.test)
|
||||||
testImplementation(projects.libraries.troubleshoot.test)
|
testImplementation(projects.libraries.troubleshoot.test)
|
||||||
testImplementation(projects.features.deactivation.test)
|
testImplementation(projects.features.deactivation.test)
|
||||||
testImplementation(projects.features.enterprise.test)
|
testImplementation(projects.features.enterprise.test)
|
||||||
|
|||||||
+30
@@ -40,10 +40,12 @@ import io.element.android.libraries.architecture.BaseFlowNode
|
|||||||
import io.element.android.libraries.architecture.appyx.canPop
|
import io.element.android.libraries.architecture.appyx.canPop
|
||||||
import io.element.android.libraries.architecture.callback
|
import io.element.android.libraries.architecture.callback
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
|
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
import io.element.android.libraries.mediaviewer.api.FileViewerEntryPoint
|
||||||
import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint
|
import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint
|
||||||
import io.element.android.libraries.troubleshoot.api.PushHistoryEntryPoint
|
import io.element.android.libraries.troubleshoot.api.PushHistoryEntryPoint
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
@@ -56,6 +58,7 @@ class PreferencesFlowNode(
|
|||||||
private val lockScreenEntryPoint: LockScreenEntryPoint,
|
private val lockScreenEntryPoint: LockScreenEntryPoint,
|
||||||
private val notificationTroubleShootEntryPoint: NotificationTroubleShootEntryPoint,
|
private val notificationTroubleShootEntryPoint: NotificationTroubleShootEntryPoint,
|
||||||
private val pushHistoryEntryPoint: PushHistoryEntryPoint,
|
private val pushHistoryEntryPoint: PushHistoryEntryPoint,
|
||||||
|
private val fileViewerEntryPoint: FileViewerEntryPoint,
|
||||||
private val logoutEntryPoint: LogoutEntryPoint,
|
private val logoutEntryPoint: LogoutEntryPoint,
|
||||||
private val openSourceLicensesEntryPoint: OpenSourceLicensesEntryPoint,
|
private val openSourceLicensesEntryPoint: OpenSourceLicensesEntryPoint,
|
||||||
private val accountDeactivationEntryPoint: AccountDeactivationEntryPoint,
|
private val accountDeactivationEntryPoint: AccountDeactivationEntryPoint,
|
||||||
@@ -95,6 +98,9 @@ class PreferencesFlowNode(
|
|||||||
@Parcelize
|
@Parcelize
|
||||||
data object PushHistory : NavTarget
|
data object PushHistory : NavTarget
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class PushRules(val filename: String, val content: String) : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object LockScreenSettings : NavTarget
|
data object LockScreenSettings : NavTarget
|
||||||
|
|
||||||
@@ -187,6 +193,10 @@ class PreferencesFlowNode(
|
|||||||
}
|
}
|
||||||
NavTarget.DeveloperSettings -> {
|
NavTarget.DeveloperSettings -> {
|
||||||
val developerSettingsCallback = object : DeveloperSettingsNode.Callback {
|
val developerSettingsCallback = object : DeveloperSettingsNode.Callback {
|
||||||
|
override fun navigateToPushRules(filename: String, content: String) {
|
||||||
|
backstack.push(NavTarget.PushRules(filename = filename, content = content))
|
||||||
|
}
|
||||||
|
|
||||||
override fun navigateToPushHistory() {
|
override fun navigateToPushHistory() {
|
||||||
backstack.push(NavTarget.PushHistory)
|
backstack.push(NavTarget.PushHistory)
|
||||||
}
|
}
|
||||||
@@ -270,6 +280,26 @@ class PreferencesFlowNode(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is NavTarget.PushRules -> {
|
||||||
|
fileViewerEntryPoint.createNode(
|
||||||
|
parentNode = this,
|
||||||
|
buildContext = buildContext,
|
||||||
|
params = FileViewerEntryPoint.Params(
|
||||||
|
filename = navTarget.filename,
|
||||||
|
mimeType = MimeTypes.Json,
|
||||||
|
content = navTarget.content,
|
||||||
|
),
|
||||||
|
callback = object : FileViewerEntryPoint.Callback {
|
||||||
|
override fun onDone() {
|
||||||
|
if (backstack.canPop()) {
|
||||||
|
backstack.pop()
|
||||||
|
} else {
|
||||||
|
navigateUp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
is NavTarget.EditDefaultNotificationSetting -> {
|
is NavTarget.EditDefaultNotificationSetting -> {
|
||||||
val callback = object : EditDefaultNotificationSettingNode.Callback {
|
val callback = object : EditDefaultNotificationSettingNode.Callback {
|
||||||
override fun navigateToRoomNotificationSettings(roomId: RoomId) {
|
override fun navigateToRoomNotificationSettings(roomId: RoomId) {
|
||||||
|
|||||||
+2
@@ -17,4 +17,6 @@ sealed interface DeveloperSettingsEvent {
|
|||||||
data object VacuumStores : DeveloperSettingsEvent
|
data object VacuumStores : DeveloperSettingsEvent
|
||||||
data class MarkAllRoomsAsRead(val needsConfirmation: Boolean) : DeveloperSettingsEvent
|
data class MarkAllRoomsAsRead(val needsConfirmation: Boolean) : DeveloperSettingsEvent
|
||||||
data object DismissMarkAllRoomsAsReadConfirmation : DeveloperSettingsEvent
|
data object DismissMarkAllRoomsAsReadConfirmation : DeveloperSettingsEvent
|
||||||
|
data object OpenPushRules : DeveloperSettingsEvent
|
||||||
|
data object DismissPushRulesError : DeveloperSettingsEvent
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.preferences.impl.developer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigation performed by the developer settings screen, when data has to be loaded first.
|
||||||
|
*/
|
||||||
|
fun interface DeveloperSettingsNavigator {
|
||||||
|
fun openPushRules(filename: String, content: String)
|
||||||
|
}
|
||||||
+9
-2
@@ -28,15 +28,22 @@ import io.element.android.libraries.di.SessionScope
|
|||||||
class DeveloperSettingsNode(
|
class DeveloperSettingsNode(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val presenter: DeveloperSettingsPresenter,
|
presenterFactory: DeveloperSettingsPresenter.Factory,
|
||||||
private val extraDeveloperOptionsRenderer: ExtraDeveloperOptionsRenderer,
|
private val extraDeveloperOptionsRenderer: ExtraDeveloperOptionsRenderer,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins),
|
||||||
|
DeveloperSettingsNavigator {
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
|
fun navigateToPushRules(filename: String, content: String)
|
||||||
fun navigateToPushHistory()
|
fun navigateToPushHistory()
|
||||||
fun onDone()
|
fun onDone()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val callback: Callback = callback()
|
private val callback: Callback = callback()
|
||||||
|
private val presenter = presenterFactory.create(navigator = this)
|
||||||
|
|
||||||
|
override fun openPushRules(filename: String, content: String) {
|
||||||
|
callback.navigateToPushRules(filename = filename, content = content)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
|
|||||||
+41
-2
@@ -17,7 +17,9 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Assisted
|
||||||
|
import dev.zacsweers.metro.AssistedFactory
|
||||||
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.features.preferences.impl.developer.appsettings.AppDeveloperSettingsState
|
import io.element.android.features.preferences.impl.developer.appsettings.AppDeveloperSettingsState
|
||||||
import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
|
import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
|
||||||
@@ -34,13 +36,15 @@ import io.element.android.libraries.core.meta.BuildMeta
|
|||||||
import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase
|
import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase
|
||||||
import io.element.android.libraries.matrix.api.core.DeviceId
|
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||||
import kotlinx.collections.immutable.ImmutableMap
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
import kotlinx.collections.immutable.toImmutableMap
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Inject
|
@AssistedInject
|
||||||
class DeveloperSettingsPresenter(
|
class DeveloperSettingsPresenter(
|
||||||
|
@Assisted private val navigator: DeveloperSettingsNavigator,
|
||||||
private val appDeveloperSettingsPresenter: Presenter<AppDeveloperSettingsState>,
|
private val appDeveloperSettingsPresenter: Presenter<AppDeveloperSettingsState>,
|
||||||
private val sessionId: SessionId,
|
private val sessionId: SessionId,
|
||||||
private val deviceId: DeviceId,
|
private val deviceId: DeviceId,
|
||||||
@@ -52,7 +56,13 @@ class DeveloperSettingsPresenter(
|
|||||||
private val fileSizeFormatter: FileSizeFormatter,
|
private val fileSizeFormatter: FileSizeFormatter,
|
||||||
private val markAllRoomsAsRead: MarkAllRoomsAsRead,
|
private val markAllRoomsAsRead: MarkAllRoomsAsRead,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
|
private val notificationSettingsService: NotificationSettingsService,
|
||||||
) : Presenter<DeveloperSettingsState> {
|
) : Presenter<DeveloperSettingsState> {
|
||||||
|
@AssistedFactory
|
||||||
|
fun interface Factory {
|
||||||
|
fun create(navigator: DeveloperSettingsNavigator): DeveloperSettingsPresenter
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): DeveloperSettingsState {
|
override fun present(): DeveloperSettingsState {
|
||||||
val cacheSize = remember {
|
val cacheSize = remember {
|
||||||
@@ -67,6 +77,9 @@ class DeveloperSettingsPresenter(
|
|||||||
val markAllRoomsAsReadAction = remember {
|
val markAllRoomsAsReadAction = remember {
|
||||||
mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
|
mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
|
||||||
}
|
}
|
||||||
|
val pushRulesAction = remember {
|
||||||
|
mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
|
||||||
|
}
|
||||||
var showColorPicker by remember {
|
var showColorPicker by remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
@@ -109,6 +122,10 @@ class DeveloperSettingsPresenter(
|
|||||||
DeveloperSettingsEvent.DismissMarkAllRoomsAsReadConfirmation -> {
|
DeveloperSettingsEvent.DismissMarkAllRoomsAsReadConfirmation -> {
|
||||||
markAllRoomsAsReadAction.value = AsyncAction.Uninitialized
|
markAllRoomsAsReadAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
|
DeveloperSettingsEvent.OpenPushRules -> coroutineScope.openPushRules(pushRulesAction)
|
||||||
|
DeveloperSettingsEvent.DismissPushRulesError -> {
|
||||||
|
pushRulesAction.value = AsyncAction.Uninitialized
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +136,7 @@ class DeveloperSettingsPresenter(
|
|||||||
databaseSizes = databaseSizes.value,
|
databaseSizes = databaseSizes.value,
|
||||||
clearCacheAction = clearCacheAction.value,
|
clearCacheAction = clearCacheAction.value,
|
||||||
markAllRoomsAsReadAction = markAllRoomsAsReadAction.value,
|
markAllRoomsAsReadAction = markAllRoomsAsReadAction.value,
|
||||||
|
pushRulesAction = pushRulesAction.value,
|
||||||
isEnterpriseBuild = buildMeta.isEnterpriseBuild,
|
isEnterpriseBuild = buildMeta.isEnterpriseBuild,
|
||||||
showColorPicker = showColorPicker,
|
showColorPicker = showColorPicker,
|
||||||
deviceId = deviceId,
|
deviceId = deviceId,
|
||||||
@@ -164,4 +182,25 @@ class DeveloperSettingsPresenter(
|
|||||||
markAllRoomsAsRead().getOrThrow()
|
markAllRoomsAsRead().getOrThrow()
|
||||||
}.runCatchingUpdatingState(state = markAllRoomsAsReadAction)
|
}.runCatchingUpdatingState(state = markAllRoomsAsReadAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.openPushRules(pushRulesAction: MutableState<AsyncAction<Unit>>) = launch {
|
||||||
|
pushRulesAction.value = AsyncAction.Loading
|
||||||
|
notificationSettingsService.getRawPushRules()
|
||||||
|
.onSuccess { content ->
|
||||||
|
pushRulesAction.value = AsyncAction.Uninitialized
|
||||||
|
navigator.openPushRules(
|
||||||
|
filename = pushRulesFilename(),
|
||||||
|
content = content.orEmpty(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.onFailure {
|
||||||
|
pushRulesAction.value = AsyncAction.Failure(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user id contains a colon, which is not a valid character for a file name on all the file systems,
|
||||||
|
* so replace it by an underscore.
|
||||||
|
*/
|
||||||
|
private fun pushRulesFilename() = "push_rules${sessionId.value.replace(':', '_')}.json"
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -20,10 +20,13 @@ data class DeveloperSettingsState(
|
|||||||
val databaseSizes: AsyncData<ImmutableMap<String, String>>,
|
val databaseSizes: AsyncData<ImmutableMap<String, String>>,
|
||||||
val clearCacheAction: AsyncAction<Unit>,
|
val clearCacheAction: AsyncAction<Unit>,
|
||||||
val markAllRoomsAsReadAction: AsyncAction<Unit>,
|
val markAllRoomsAsReadAction: AsyncAction<Unit>,
|
||||||
|
val pushRulesAction: AsyncAction<Unit>,
|
||||||
val isEnterpriseBuild: Boolean,
|
val isEnterpriseBuild: Boolean,
|
||||||
val showColorPicker: Boolean,
|
val showColorPicker: Boolean,
|
||||||
val deviceId: DeviceId,
|
val deviceId: DeviceId,
|
||||||
val eventSink: (DeveloperSettingsEvent) -> Unit
|
val eventSink: (DeveloperSettingsEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val showLoader = clearCacheAction is AsyncAction.Loading || markAllRoomsAsReadAction is AsyncAction.Loading
|
val showLoader = clearCacheAction is AsyncAction.Loading ||
|
||||||
|
markAllRoomsAsReadAction is AsyncAction.Loading ||
|
||||||
|
pushRulesAction is AsyncAction.Loading
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -32,6 +32,9 @@ open class DeveloperSettingsStatePreviewParam : PreviewParameterProvider<Develop
|
|||||||
// at io.mhssn.colorpicker.pickers.ClassicColorPickerKt$ClassicColorPicker$1$1.invokeSuspend(ClassicColorPicker.kt:53)
|
// at io.mhssn.colorpicker.pickers.ClassicColorPickerKt$ClassicColorPicker$1$1.invokeSuspend(ClassicColorPicker.kt:53)
|
||||||
showColorPicker = false,
|
showColorPicker = false,
|
||||||
),
|
),
|
||||||
|
aDeveloperSettingsState(
|
||||||
|
pushRulesAction = AsyncAction.Failure(Exception("A failure"))
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +42,7 @@ fun aDeveloperSettingsState(
|
|||||||
appDeveloperSettingsState: AppDeveloperSettingsState = anAppDeveloperSettingsState(),
|
appDeveloperSettingsState: AppDeveloperSettingsState = anAppDeveloperSettingsState(),
|
||||||
clearCacheAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
clearCacheAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
markAllRoomsAsReadAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
markAllRoomsAsReadAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
|
pushRulesAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
isEnterpriseBuild: Boolean = false,
|
isEnterpriseBuild: Boolean = false,
|
||||||
showColorPicker: Boolean = false,
|
showColorPicker: Boolean = false,
|
||||||
deviceId: DeviceId = DeviceId("ILAKNDNASDLK"),
|
deviceId: DeviceId = DeviceId("ILAKNDNASDLK"),
|
||||||
@@ -49,6 +53,7 @@ fun aDeveloperSettingsState(
|
|||||||
databaseSizes = AsyncData.Success(persistentMapOf("state_store" to "1.2MB")),
|
databaseSizes = AsyncData.Success(persistentMapOf("state_store" to "1.2MB")),
|
||||||
clearCacheAction = clearCacheAction,
|
clearCacheAction = clearCacheAction,
|
||||||
markAllRoomsAsReadAction = markAllRoomsAsReadAction,
|
markAllRoomsAsReadAction = markAllRoomsAsReadAction,
|
||||||
|
pushRulesAction = pushRulesAction,
|
||||||
isEnterpriseBuild = isEnterpriseBuild,
|
isEnterpriseBuild = isEnterpriseBuild,
|
||||||
showColorPicker = showColorPicker,
|
showColorPicker = showColorPicker,
|
||||||
deviceId = deviceId,
|
deviceId = deviceId,
|
||||||
|
|||||||
+22
-2
@@ -23,8 +23,10 @@ import io.element.android.compound.theme.ElementTheme
|
|||||||
import io.element.android.features.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
import io.element.android.features.preferences.impl.developer.appsettings.AppDeveloperSettingsView
|
import io.element.android.features.preferences.impl.developer.appsettings.AppDeveloperSettingsView
|
||||||
import io.element.android.libraries.androidutils.system.copyToClipboard
|
import io.element.android.libraries.androidutils.system.copyToClipboard
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
|
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||||
@@ -60,6 +62,12 @@ fun DeveloperSettingsView(
|
|||||||
onDismiss = { state.eventSink(DeveloperSettingsEvent.DismissMarkAllRoomsAsReadConfirmation) },
|
onDismiss = { state.eventSink(DeveloperSettingsEvent.DismissMarkAllRoomsAsReadConfirmation) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
(state.pushRulesAction as? AsyncAction.Failure)?.let { failure ->
|
||||||
|
ErrorDialog(
|
||||||
|
content = failure.error.message ?: stringResource(CommonStrings.error_unknown),
|
||||||
|
onSubmit = { state.eventSink(DeveloperSettingsEvent.DismissPushRulesError) },
|
||||||
|
)
|
||||||
|
}
|
||||||
BackHandler(
|
BackHandler(
|
||||||
enabled = !state.showLoader,
|
enabled = !state.showLoader,
|
||||||
onBack = onBackClick,
|
onBack = onBackClick,
|
||||||
@@ -79,7 +87,10 @@ fun DeveloperSettingsView(
|
|||||||
onOpenShowkase = onOpenShowkase,
|
onOpenShowkase = onOpenShowkase,
|
||||||
)
|
)
|
||||||
SessionCategory(deviceId = state.deviceId)
|
SessionCategory(deviceId = state.deviceId)
|
||||||
NotificationCategory(onPushHistoryClick)
|
NotificationCategory(
|
||||||
|
onPushRulesClick = { state.eventSink(DeveloperSettingsEvent.OpenPushRules) },
|
||||||
|
onPushHistoryClick = onPushHistoryClick,
|
||||||
|
)
|
||||||
MarkAllRoomsAsReadCategory(state)
|
MarkAllRoomsAsReadCategory(state)
|
||||||
|
|
||||||
if (state.isEnterpriseBuild) {
|
if (state.isEnterpriseBuild) {
|
||||||
@@ -217,8 +228,17 @@ private fun MarkAllRoomsAsReadCategory(state: DeveloperSettingsState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NotificationCategory(onPushHistoryClick: () -> Unit) {
|
private fun NotificationCategory(
|
||||||
|
onPushRulesClick: () -> Unit,
|
||||||
|
onPushHistoryClick: () -> Unit,
|
||||||
|
) {
|
||||||
PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_title)) {
|
PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_title)) {
|
||||||
|
ListItem(
|
||||||
|
content = {
|
||||||
|
Text("Push rules")
|
||||||
|
},
|
||||||
|
onClick = onPushRulesClick,
|
||||||
|
)
|
||||||
ListItem(
|
ListItem(
|
||||||
content = {
|
content = {
|
||||||
Text(stringResource(R.string.troubleshoot_notifications_entry_point_push_history_title))
|
Text(stringResource(R.string.troubleshoot_notifications_entry_point_push_history_title))
|
||||||
|
|||||||
+2
@@ -19,6 +19,7 @@ import io.element.android.features.logout.test.FakeLogoutEntryPoint
|
|||||||
import io.element.android.features.preferences.api.PreferencesEntryPoint
|
import io.element.android.features.preferences.api.PreferencesEntryPoint
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.mediaviewer.test.FakeFileViewerEntryPoint
|
||||||
import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleShootEntryPoint
|
import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleShootEntryPoint
|
||||||
import io.element.android.libraries.troubleshoot.test.FakePushHistoryEntryPoint
|
import io.element.android.libraries.troubleshoot.test.FakePushHistoryEntryPoint
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
@@ -43,6 +44,7 @@ class DefaultPreferencesEntryPointTest {
|
|||||||
lockScreenEntryPoint = FakeLockScreenEntryPoint(),
|
lockScreenEntryPoint = FakeLockScreenEntryPoint(),
|
||||||
notificationTroubleShootEntryPoint = FakeNotificationTroubleShootEntryPoint(),
|
notificationTroubleShootEntryPoint = FakeNotificationTroubleShootEntryPoint(),
|
||||||
pushHistoryEntryPoint = FakePushHistoryEntryPoint(),
|
pushHistoryEntryPoint = FakePushHistoryEntryPoint(),
|
||||||
|
fileViewerEntryPoint = FakeFileViewerEntryPoint(),
|
||||||
logoutEntryPoint = FakeLogoutEntryPoint(),
|
logoutEntryPoint = FakeLogoutEntryPoint(),
|
||||||
openSourceLicensesEntryPoint = FakeOpenSourceLicensesEntryPoint(),
|
openSourceLicensesEntryPoint = FakeOpenSourceLicensesEntryPoint(),
|
||||||
accountDeactivationEntryPoint = FakeAccountDeactivationEntryPoint(),
|
accountDeactivationEntryPoint = FakeAccountDeactivationEntryPoint(),
|
||||||
|
|||||||
+55
@@ -28,10 +28,13 @@ import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase
|
|||||||
import io.element.android.libraries.matrix.api.analytics.SdkStoreSizes
|
import io.element.android.libraries.matrix.api.analytics.SdkStoreSizes
|
||||||
import io.element.android.libraries.matrix.api.core.DeviceId
|
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||||
import io.element.android.libraries.matrix.test.A_DEVICE_ID
|
import io.element.android.libraries.matrix.test.A_DEVICE_ID
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
|
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
@@ -41,6 +44,9 @@ import kotlinx.coroutines.test.runTest
|
|||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
private const val A_PUSH_RULES_CONTENT = "{\"global\":{}}"
|
||||||
|
private val AN_EXCEPTION = Exception("A failure")
|
||||||
|
|
||||||
class DeveloperSettingsPresenterTest {
|
class DeveloperSettingsPresenterTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
@@ -170,7 +176,53 @@ class DeveloperSettingsPresenterTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - OpenPushRules event navigates to the push rules content`() = runTest {
|
||||||
|
val openPushRulesLambda = lambdaRecorder<String, String, Unit> { _, _ -> }
|
||||||
|
val presenter = createDeveloperSettingsPresenter(
|
||||||
|
navigator = DeveloperSettingsNavigator(openPushRulesLambda),
|
||||||
|
notificationSettingsService = FakeNotificationSettingsService(
|
||||||
|
getRawPushRulesResult = { Result.success(A_PUSH_RULES_CONTENT) },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val state = awaitItem()
|
||||||
|
assertThat(state.pushRulesAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
|
state.eventSink(DeveloperSettingsEvent.OpenPushRules)
|
||||||
|
skipItems(1)
|
||||||
|
awaitItem().also {
|
||||||
|
assertThat(it.pushRulesAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
|
}
|
||||||
|
openPushRulesLambda.assertions().isCalledOnce().with(
|
||||||
|
value("push_rules@alice_server.org.json"),
|
||||||
|
value(A_PUSH_RULES_CONTENT),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - OpenPushRules event failure can be dismissed`() = runTest {
|
||||||
|
val presenter = createDeveloperSettingsPresenter(
|
||||||
|
notificationSettingsService = FakeNotificationSettingsService(
|
||||||
|
getRawPushRulesResult = { Result.failure(AN_EXCEPTION) },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val state = awaitItem()
|
||||||
|
state.eventSink(DeveloperSettingsEvent.OpenPushRules)
|
||||||
|
skipItems(1)
|
||||||
|
awaitItem().also {
|
||||||
|
assertThat(it.pushRulesAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION))
|
||||||
|
it.eventSink(DeveloperSettingsEvent.DismissPushRulesError)
|
||||||
|
}
|
||||||
|
awaitItem().also {
|
||||||
|
assertThat(it.pushRulesAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createDeveloperSettingsPresenter(
|
private fun createDeveloperSettingsPresenter(
|
||||||
|
navigator: DeveloperSettingsNavigator = DeveloperSettingsNavigator { _, _ -> lambdaError() },
|
||||||
sessionId: SessionId = A_SESSION_ID,
|
sessionId: SessionId = A_SESSION_ID,
|
||||||
deviceId: DeviceId = A_DEVICE_ID,
|
deviceId: DeviceId = A_DEVICE_ID,
|
||||||
cacheSizeUseCase: FakeComputeCacheSizeUseCase = FakeComputeCacheSizeUseCase(),
|
cacheSizeUseCase: FakeComputeCacheSizeUseCase = FakeComputeCacheSizeUseCase(),
|
||||||
@@ -180,8 +232,10 @@ class DeveloperSettingsPresenterTest {
|
|||||||
databaseSizesUseCase: GetDatabaseSizesUseCase = GetDatabaseSizesUseCase { Result.success(SdkStoreSizes(null, null, null, null)) },
|
databaseSizesUseCase: GetDatabaseSizesUseCase = GetDatabaseSizesUseCase { Result.success(SdkStoreSizes(null, null, null, null)) },
|
||||||
markAllRoomsAsRead: FakeMarkAllRoomsAsRead = FakeMarkAllRoomsAsRead(),
|
markAllRoomsAsRead: FakeMarkAllRoomsAsRead = FakeMarkAllRoomsAsRead(),
|
||||||
buildMeta: BuildMeta = aBuildMeta(),
|
buildMeta: BuildMeta = aBuildMeta(),
|
||||||
|
notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
|
||||||
): DeveloperSettingsPresenter {
|
): DeveloperSettingsPresenter {
|
||||||
return DeveloperSettingsPresenter(
|
return DeveloperSettingsPresenter(
|
||||||
|
navigator = navigator,
|
||||||
appDeveloperSettingsPresenter = { anAppDeveloperSettingsState() },
|
appDeveloperSettingsPresenter = { anAppDeveloperSettingsState() },
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
deviceId = deviceId,
|
deviceId = deviceId,
|
||||||
@@ -193,6 +247,7 @@ class DeveloperSettingsPresenterTest {
|
|||||||
fileSizeFormatter = FakeFileSizeFormatter(),
|
fileSizeFormatter = FakeFileSizeFormatter(),
|
||||||
markAllRoomsAsRead = markAllRoomsAsRead,
|
markAllRoomsAsRead = markAllRoomsAsRead,
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
|
notificationSettingsService = notificationSettingsService,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
@@ -57,6 +57,19 @@ class DeveloperSettingsViewTest : RobolectricTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Config(qualifiers = "h2400dp")
|
||||||
|
@Test
|
||||||
|
fun `clicking on push rules emits the expected event`() = runAndroidComposeUiTest {
|
||||||
|
val eventsRecorder = EventsRecorder<DeveloperSettingsEvent>()
|
||||||
|
setDeveloperSettingsView(
|
||||||
|
state = aDeveloperSettingsState(
|
||||||
|
eventSink = eventsRecorder
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onNodeWithText("Push rules").performScrollTo().performClick()
|
||||||
|
eventsRecorder.assertSingle(DeveloperSettingsEvent.OpenPushRules)
|
||||||
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h2000dp")
|
@Config(qualifiers = "h2000dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on open showkase invokes the expected callback`() = runAndroidComposeUiTest {
|
fun `clicking on open showkase invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
|
|||||||
Reference in New Issue
Block a user