mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 16:10:00 +00:00
Allow adding extra elements to the developer settings from external modules (#7230)
* Add `ExtraDeveloperOptionsRenderer` to display extra UI components in the developer settings screen. It defaults to not displaying anything, using `NoopExtraDeveloperOptionsRenderer` * Add `EnterpriseService.overriddenElementWellKnown` method to fetch the current overridden element well known value, if any * Modify the well known retrievers to use the overridden value if it's there. Also extract the JSON parsing to its own `ElementWellKnownParser` component. * Bump enterprise repo reference.
This commit is contained in:
committed by
GitHub
parent
10161a686f
commit
925376b6af
+1
-1
Submodule enterprise updated: cca96c4fc2...60de13ffd7
@@ -17,4 +17,5 @@ dependencies {
|
|||||||
implementation(projects.libraries.compound)
|
implementation(projects.libraries.compound)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
implementation(projects.libraries.wellknown.api)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -11,6 +11,7 @@ package io.element.android.features.enterprise.api
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import io.element.android.compound.colors.SemanticColorsLightDark
|
import io.element.android.compound.colors.SemanticColorsLightDark
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface EnterpriseService {
|
interface EnterpriseService {
|
||||||
@@ -41,6 +42,11 @@ interface EnterpriseService {
|
|||||||
*/
|
*/
|
||||||
fun getNoisyNotificationChannelId(sessionId: SessionId): String?
|
fun getNoisyNotificationChannelId(sessionId: SessionId): String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the overridden Element Well-Known data if it has been set, or null if not set.
|
||||||
|
*/
|
||||||
|
fun overriddenElementWellKnown(): ElementWellKnown?
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ANY_ACCOUNT_PROVIDER = "*"
|
const val ANY_ACCOUNT_PROVIDER = "*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ dependencies {
|
|||||||
api(projects.features.enterprise.api)
|
api(projects.features.enterprise.api)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
implementation(projects.libraries.wellknown.api)
|
||||||
|
|
||||||
testCommonDependencies(libs)
|
testCommonDependencies(libs)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
|||||||
+3
@@ -15,6 +15,7 @@ import io.element.android.compound.colors.SemanticColorsLightDark
|
|||||||
import io.element.android.features.enterprise.api.BugReportUrl
|
import io.element.android.features.enterprise.api.BugReportUrl
|
||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
|
||||||
@@ -45,4 +46,6 @@ class DefaultEnterpriseService : EnterpriseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getNoisyNotificationChannelId(sessionId: SessionId): String? = null
|
override fun getNoisyNotificationChannelId(sessionId: SessionId): String? = null
|
||||||
|
|
||||||
|
override fun overriddenElementWellKnown(): ElementWellKnown? = null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,5 +18,6 @@ dependencies {
|
|||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.compound)
|
implementation(projects.libraries.compound)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
implementation(projects.libraries.wellknown.api)
|
||||||
implementation(projects.tests.testutils)
|
implementation(projects.tests.testutils)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -13,6 +13,7 @@ import io.element.android.compound.colors.SemanticColorsLightDark
|
|||||||
import io.element.android.features.enterprise.api.BugReportUrl
|
import io.element.android.features.enterprise.api.BugReportUrl
|
||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
import io.element.android.tests.testutils.simulateLongTask
|
import io.element.android.tests.testutils.simulateLongTask
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -31,6 +32,7 @@ class FakeEnterpriseService(
|
|||||||
private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() },
|
private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() },
|
||||||
private val getNoisyNotificationChannelIdResult: (SessionId?) -> String? = { lambdaError() },
|
private val getNoisyNotificationChannelIdResult: (SessionId?) -> String? = { lambdaError() },
|
||||||
private val tweakMasUrlResult: (String, String) -> String = { _, _ -> lambdaError() },
|
private val tweakMasUrlResult: (String, String) -> String = { _, _ -> lambdaError() },
|
||||||
|
private val overrideWellKnownResult: () -> ElementWellKnown? = { lambdaError() },
|
||||||
) : EnterpriseService {
|
) : EnterpriseService {
|
||||||
private val brandColorState = MutableStateFlow(initialBrandColor)
|
private val brandColorState = MutableStateFlow(initialBrandColor)
|
||||||
private val semanticColorsState = MutableStateFlow(initialSemanticColors)
|
private val semanticColorsState = MutableStateFlow(initialSemanticColors)
|
||||||
@@ -79,4 +81,8 @@ class FakeEnterpriseService(
|
|||||||
override fun getNoisyNotificationChannelId(sessionId: SessionId): String? {
|
override fun getNoisyNotificationChannelId(sessionId: SessionId): String? {
|
||||||
return getNoisyNotificationChannelIdResult(sessionId)
|
return getNoisyNotificationChannelIdResult(sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun overriddenElementWellKnown(): ElementWellKnown? {
|
||||||
|
return overrideWellKnownResult()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-library")
|
id("io.element.android-compose-library")
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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.api
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component for rendering extra developer options in the preferences screen.
|
||||||
|
*/
|
||||||
|
interface ExtraDeveloperOptionsRenderer {
|
||||||
|
@Composable
|
||||||
|
fun Render(modifier: Modifier)
|
||||||
|
}
|
||||||
+3
@@ -18,6 +18,7 @@ import com.bumble.appyx.core.plugin.Plugin
|
|||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.annotations.ContributesNode
|
import io.element.android.annotations.ContributesNode
|
||||||
|
import io.element.android.features.preferences.api.ExtraDeveloperOptionsRenderer
|
||||||
import io.element.android.libraries.architecture.callback
|
import io.element.android.libraries.architecture.callback
|
||||||
import io.element.android.libraries.designsystem.showkase.getBrowserIntent
|
import io.element.android.libraries.designsystem.showkase.getBrowserIntent
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
@@ -28,6 +29,7 @@ class DeveloperSettingsNode(
|
|||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val presenter: DeveloperSettingsPresenter,
|
private val presenter: DeveloperSettingsPresenter,
|
||||||
|
private val extraDeveloperOptionsRenderer: ExtraDeveloperOptionsRenderer,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun navigateToPushHistory()
|
fun navigateToPushHistory()
|
||||||
@@ -51,6 +53,7 @@ class DeveloperSettingsNode(
|
|||||||
onOpenShowkase = ::openShowkase,
|
onOpenShowkase = ::openShowkase,
|
||||||
onPushHistoryClick = callback::navigateToPushHistory,
|
onPushHistoryClick = callback::navigateToPushHistory,
|
||||||
onBackClick = callback::onDone,
|
onBackClick = callback::onDone,
|
||||||
|
extraOptions = { extraDeveloperOptionsRenderer.Render(Modifier) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -43,6 +43,7 @@ fun DeveloperSettingsView(
|
|||||||
onPushHistoryClick: () -> Unit,
|
onPushHistoryClick: () -> Unit,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
extraOptions: @Composable () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
if (state.showLoader) {
|
if (state.showLoader) {
|
||||||
ProgressDialog()
|
ProgressDialog()
|
||||||
@@ -97,6 +98,9 @@ fun DeveloperSettingsView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extraOptions()
|
||||||
|
|
||||||
val cache = state.cacheSize
|
val cache = state.cacheSize
|
||||||
PreferenceCategory(title = "Cache") {
|
PreferenceCategory(title = "Cache") {
|
||||||
ListItem(
|
ListItem(
|
||||||
|
|||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import dev.zacsweers.metro.AppScope
|
||||||
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
|
import io.element.android.features.preferences.api.ExtraDeveloperOptionsRenderer
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class NoopExtraDeveloperOptionsRenderer : ExtraDeveloperOptionsRenderer {
|
||||||
|
@Composable
|
||||||
|
override fun Render(modifier: Modifier) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
@@ -18,6 +18,7 @@ import dev.zacsweers.metro.AppScope
|
|||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.annotations.ContributesNode
|
import io.element.android.annotations.ContributesNode
|
||||||
|
import io.element.android.features.preferences.api.ExtraDeveloperOptionsRenderer
|
||||||
import io.element.android.features.preferences.api.PreferencesEntryPoint
|
import io.element.android.features.preferences.api.PreferencesEntryPoint
|
||||||
import io.element.android.libraries.architecture.callback
|
import io.element.android.libraries.architecture.callback
|
||||||
import io.element.android.libraries.designsystem.showkase.getBrowserIntent
|
import io.element.android.libraries.designsystem.showkase.getBrowserIntent
|
||||||
@@ -28,6 +29,7 @@ class AppDeveloperSettingsNode(
|
|||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val presenter: AppDeveloperSettingsPresenter,
|
private val presenter: AppDeveloperSettingsPresenter,
|
||||||
|
private val extraDeveloperOptionsRenderer: ExtraDeveloperOptionsRenderer,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
private val callback: PreferencesEntryPoint.DeveloperSettingsCallback = callback()
|
private val callback: PreferencesEntryPoint.DeveloperSettingsCallback = callback()
|
||||||
|
|
||||||
@@ -45,6 +47,7 @@ class AppDeveloperSettingsNode(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onOpenShowkase = ::openShowkase,
|
onOpenShowkase = ::openShowkase,
|
||||||
onBackClick = callback::onDone,
|
onBackClick = callback::onDone,
|
||||||
|
extraOptions = { extraDeveloperOptionsRenderer.Render(Modifier) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -24,6 +24,7 @@ fun AppDeveloperSettingsPage(
|
|||||||
onOpenShowkase: () -> Unit,
|
onOpenShowkase: () -> Unit,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
extraOptions: @Composable () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
BackHandler(
|
BackHandler(
|
||||||
onBack = onBackClick,
|
onBack = onBackClick,
|
||||||
@@ -40,6 +41,7 @@ fun AppDeveloperSettingsPage(
|
|||||||
onOpenShowkase = onOpenShowkase,
|
onOpenShowkase = onOpenShowkase,
|
||||||
modifier = Modifier.padding(top = 8.dp)
|
modifier = Modifier.padding(top = 8.dp)
|
||||||
)
|
)
|
||||||
|
extraOptions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* 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.libraries.wellknown.api
|
||||||
|
|
||||||
|
fun interface ElementWellKnownParser {
|
||||||
|
fun parse(json: String): Result<ElementWellKnown>
|
||||||
|
}
|
||||||
@@ -34,6 +34,7 @@ dependencies {
|
|||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.network)
|
implementation(projects.libraries.network)
|
||||||
implementation(projects.libraries.cachestore.api)
|
implementation(projects.libraries.cachestore.api)
|
||||||
|
implementation(projects.features.enterprise.api)
|
||||||
implementation(projects.services.toolbox.api)
|
implementation(projects.services.toolbox.api)
|
||||||
|
|
||||||
testCommonDependencies(libs)
|
testCommonDependencies(libs)
|
||||||
@@ -41,5 +42,6 @@ dependencies {
|
|||||||
testImplementation(projects.libraries.cachestore.test)
|
testImplementation(projects.libraries.cachestore.test)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.wellknown.test)
|
testImplementation(projects.libraries.wellknown.test)
|
||||||
|
testImplementation(projects.features.enterprise.test)
|
||||||
testImplementation(projects.services.toolbox.test)
|
testImplementation(projects.services.toolbox.test)
|
||||||
}
|
}
|
||||||
|
|||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* 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.libraries.wellknown.impl
|
||||||
|
|
||||||
|
import dev.zacsweers.metro.AppScope
|
||||||
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
|
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||||
|
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||||
|
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||||
|
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultElementWellKnownParser(
|
||||||
|
private val jsonProvider: JsonProvider,
|
||||||
|
) : ElementWellKnownParser {
|
||||||
|
override fun parse(json: String): Result<ElementWellKnown> {
|
||||||
|
return runCatchingExceptions {
|
||||||
|
val decoder = jsonProvider()
|
||||||
|
decoder.decodeFromString<InternalElementWellKnown>(json).map()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-3
@@ -9,13 +9,14 @@
|
|||||||
package io.element.android.libraries.wellknown.impl
|
package io.element.android.libraries.wellknown.impl
|
||||||
|
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import io.element.android.libraries.androidutils.json.JsonProvider
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.libraries.core.extensions.mapCatchingExceptions
|
import io.element.android.libraries.core.extensions.mapCatchingExceptions
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||||
|
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
|
||||||
import io.element.android.libraries.wellknown.api.ElementWellknownStore
|
import io.element.android.libraries.wellknown.api.ElementWellknownStore
|
||||||
import io.element.android.libraries.wellknown.api.SessionWellknownRetriever
|
import io.element.android.libraries.wellknown.api.SessionWellknownRetriever
|
||||||
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
||||||
@@ -26,14 +27,20 @@ import timber.log.Timber
|
|||||||
@ContributesBinding(SessionScope::class)
|
@ContributesBinding(SessionScope::class)
|
||||||
class DefaultSessionWellknownRetriever(
|
class DefaultSessionWellknownRetriever(
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
private val json: JsonProvider,
|
|
||||||
@SessionCoroutineScope
|
@SessionCoroutineScope
|
||||||
private val sessionCoroutineScope: CoroutineScope,
|
private val sessionCoroutineScope: CoroutineScope,
|
||||||
private val elementWellknownStore: ElementWellknownStore,
|
private val elementWellknownStore: ElementWellknownStore,
|
||||||
|
private val elementWellKnownParser: ElementWellKnownParser,
|
||||||
|
private val enterpriseService: EnterpriseService,
|
||||||
) : SessionWellknownRetriever {
|
) : SessionWellknownRetriever {
|
||||||
private val domain by lazy { matrixClient.userIdServerName() }
|
private val domain by lazy { matrixClient.userIdServerName() }
|
||||||
|
|
||||||
override suspend fun getElementWellKnown(): WellknownRetrieverResult<ElementWellKnown> {
|
override suspend fun getElementWellKnown(): WellknownRetrieverResult<ElementWellKnown> {
|
||||||
|
val overriddenElementWellKnown = enterpriseService.overriddenElementWellKnown()
|
||||||
|
if (overriddenElementWellKnown != null) {
|
||||||
|
return WellknownRetrieverResult.Success(overriddenElementWellKnown)
|
||||||
|
}
|
||||||
|
|
||||||
val cacheData = elementWellknownStore.get(domain)
|
val cacheData = elementWellknownStore.get(domain)
|
||||||
return when (cacheData) {
|
return when (cacheData) {
|
||||||
is WellknownRetrieverResult.Success -> {
|
is WellknownRetrieverResult.Success -> {
|
||||||
@@ -69,7 +76,7 @@ class DefaultSessionWellknownRetriever(
|
|||||||
.getUrl(url)
|
.getUrl(url)
|
||||||
.mapCatchingExceptions {
|
.mapCatchingExceptions {
|
||||||
val data = String(it)
|
val data = String(it)
|
||||||
val parsed = json().decodeFromString<InternalElementWellKnown>(data).map()
|
val parsed = elementWellKnownParser.parse(data).getOrThrow()
|
||||||
// Also store in cache, if valid
|
// Also store in cache, if valid
|
||||||
elementWellknownStore.update(domain, data)
|
elementWellknownStore.update(domain, data)
|
||||||
.onFailure { exception ->
|
.onFailure { exception ->
|
||||||
|
|||||||
+3
-3
@@ -9,11 +9,11 @@ package io.element.android.libraries.wellknown.impl
|
|||||||
|
|
||||||
import dev.zacsweers.metro.AppScope
|
import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import io.element.android.libraries.androidutils.json.JsonProvider
|
|
||||||
import io.element.android.libraries.cachestore.api.CacheData
|
import io.element.android.libraries.cachestore.api.CacheData
|
||||||
import io.element.android.libraries.cachestore.api.CacheStore
|
import io.element.android.libraries.cachestore.api.CacheStore
|
||||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||||
|
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
|
||||||
import io.element.android.libraries.wellknown.api.ElementWellknownStore
|
import io.element.android.libraries.wellknown.api.ElementWellknownStore
|
||||||
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
||||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||||
@@ -21,14 +21,14 @@ import io.element.android.services.toolbox.api.systemclock.SystemClock
|
|||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultWellKnownStore(
|
class DefaultWellKnownStore(
|
||||||
private val cacheStore: CacheStore,
|
private val cacheStore: CacheStore,
|
||||||
private val json: JsonProvider,
|
private val elementWellKnownParser: ElementWellKnownParser,
|
||||||
private val systemClock: SystemClock,
|
private val systemClock: SystemClock,
|
||||||
) : ElementWellknownStore {
|
) : ElementWellknownStore {
|
||||||
override suspend fun get(domain: String): WellknownRetrieverResult<ElementWellKnown> {
|
override suspend fun get(domain: String): WellknownRetrieverResult<ElementWellKnown> {
|
||||||
return runCatchingExceptions {
|
return runCatchingExceptions {
|
||||||
val cachedData = cacheStore.getData(key(domain))
|
val cachedData = cacheStore.getData(key(domain))
|
||||||
if (cachedData != null) {
|
if (cachedData != null) {
|
||||||
val data = json().decodeFromString<InternalElementWellKnown>(cachedData.value).map()
|
val data = elementWellKnownParser.parse(cachedData.value).getOrThrow()
|
||||||
if (systemClock.epochMillis() > cachedData.updatedAt + CACHE_VALIDITY_MILLIS) {
|
if (systemClock.epochMillis() > cachedData.updatedAt + CACHE_VALIDITY_MILLIS) {
|
||||||
WellknownRetrieverResult.Outdated(data)
|
WellknownRetrieverResult.Outdated(data)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+7
@@ -10,6 +10,7 @@ package io.element.android.libraries.wellknown.impl
|
|||||||
|
|
||||||
import dev.zacsweers.metro.AppScope
|
import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.libraries.androidutils.json.JsonProvider
|
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||||
import io.element.android.libraries.core.uri.ensureProtocol
|
import io.element.android.libraries.core.uri.ensureProtocol
|
||||||
@@ -28,8 +29,14 @@ class DefaultWellknownRetriever(
|
|||||||
private val retrofitFactory: RetrofitFactory,
|
private val retrofitFactory: RetrofitFactory,
|
||||||
private val elementWellknownStore: ElementWellknownStore,
|
private val elementWellknownStore: ElementWellknownStore,
|
||||||
private val jsonProvider: JsonProvider,
|
private val jsonProvider: JsonProvider,
|
||||||
|
private val enterpriseService: EnterpriseService,
|
||||||
) : WellknownRetriever {
|
) : WellknownRetriever {
|
||||||
override suspend fun getElementWellKnown(baseUrl: String): WellknownRetrieverResult<ElementWellKnown> {
|
override suspend fun getElementWellKnown(baseUrl: String): WellknownRetrieverResult<ElementWellKnown> {
|
||||||
|
val overriddenElementWellKnown = enterpriseService.overriddenElementWellKnown()
|
||||||
|
if (overriddenElementWellKnown != null) {
|
||||||
|
return WellknownRetrieverResult.Success(overriddenElementWellKnown)
|
||||||
|
}
|
||||||
|
|
||||||
val domain = URL(baseUrl).host
|
val domain = URL(baseUrl).host
|
||||||
return buildWellknownApi(baseUrl)
|
return buildWellknownApi(baseUrl)
|
||||||
.map { wellknownApi ->
|
.map { wellknownApi ->
|
||||||
|
|||||||
+28
-4
@@ -11,15 +11,16 @@
|
|||||||
package io.element.android.libraries.wellknown.impl
|
package io.element.android.libraries.wellknown.impl
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
import io.element.android.features.wellknown.test.FakeElementWellknownStore
|
import io.element.android.features.wellknown.test.FakeElementWellknownStore
|
||||||
import io.element.android.features.wellknown.test.anElementWellKnown
|
import io.element.android.features.wellknown.test.anElementWellKnown
|
||||||
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||||
import io.element.android.libraries.androidutils.json.JsonProvider
|
|
||||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
|
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
|
||||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||||
|
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
|
||||||
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
@@ -276,7 +277,7 @@ class DefaultSessionWellknownRetrieverTest {
|
|||||||
Result.success("{}".toByteArray())
|
Result.success("{}".toByteArray())
|
||||||
},
|
},
|
||||||
cacheStore = cacheStore,
|
cacheStore = cacheStore,
|
||||||
jsonProvider = JsonProvider { error("Failed to parse JSON") }
|
elementWellKnownParser = { Result.failure(IllegalStateException("Failed to parse JSON")) }
|
||||||
)
|
)
|
||||||
assertThat(sut.getElementWellKnown()).isInstanceOf(WellknownRetrieverResult.Error::class.java)
|
assertThat(sut.getElementWellKnown()).isInstanceOf(WellknownRetrieverResult.Error::class.java)
|
||||||
// Ensure that the cache is deleted after the failure to parse it
|
// Ensure that the cache is deleted after the failure to parse it
|
||||||
@@ -320,20 +321,43 @@ class DefaultSessionWellknownRetrieverTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `get element wellknown was overridden`() = runTest {
|
||||||
|
val getLambda = lambdaRecorder<String, Result<ByteArray>> { Result.failure(IllegalStateException("BOOM")) }
|
||||||
|
val wellKnown = anElementWellKnown()
|
||||||
|
|
||||||
|
val sut = createDefaultSessionWellknownRetriever(
|
||||||
|
getUrlLambda = getLambda,
|
||||||
|
enterpriseService = FakeEnterpriseService(
|
||||||
|
overrideWellKnownResult = { wellKnown }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// The overridden value is returned
|
||||||
|
assertThat(sut.getElementWellKnown()).isEqualTo(
|
||||||
|
WellknownRetrieverResult.Success(wellKnown)
|
||||||
|
)
|
||||||
|
|
||||||
|
// And the endpoint is never hit
|
||||||
|
getLambda.assertions().isNeverCalled()
|
||||||
|
}
|
||||||
|
|
||||||
private fun parsedWellKnownContent() = DefaultJsonProvider().invoke().decodeFromString<InternalElementWellKnown>(WELLKNOWN_CONTENT).map()
|
private fun parsedWellKnownContent() = DefaultJsonProvider().invoke().decodeFromString<InternalElementWellKnown>(WELLKNOWN_CONTENT).map()
|
||||||
|
|
||||||
private fun TestScope.createDefaultSessionWellknownRetriever(
|
private fun TestScope.createDefaultSessionWellknownRetriever(
|
||||||
cacheStore: FakeElementWellknownStore = FakeElementWellknownStore(),
|
cacheStore: FakeElementWellknownStore = FakeElementWellknownStore(),
|
||||||
getUrlLambda: (String) -> Result<ByteArray>,
|
getUrlLambda: (String) -> Result<ByteArray>,
|
||||||
jsonProvider: JsonProvider = DefaultJsonProvider(),
|
elementWellKnownParser: ElementWellKnownParser = DefaultElementWellKnownParser(DefaultJsonProvider()),
|
||||||
|
enterpriseService: FakeEnterpriseService = FakeEnterpriseService(overrideWellKnownResult = { null }),
|
||||||
) = DefaultSessionWellknownRetriever(
|
) = DefaultSessionWellknownRetriever(
|
||||||
matrixClient = FakeMatrixClient(
|
matrixClient = FakeMatrixClient(
|
||||||
userIdServerNameLambda = { "user.domain.org" },
|
userIdServerNameLambda = { "user.domain.org" },
|
||||||
getUrlLambda = getUrlLambda,
|
getUrlLambda = getUrlLambda,
|
||||||
),
|
),
|
||||||
json = jsonProvider,
|
|
||||||
sessionCoroutineScope = backgroundScope,
|
sessionCoroutineScope = backgroundScope,
|
||||||
elementWellknownStore = cacheStore,
|
elementWellknownStore = cacheStore,
|
||||||
|
elementWellKnownParser = elementWellKnownParser,
|
||||||
|
enterpriseService = enterpriseService,
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
+3
-1
@@ -13,6 +13,7 @@ import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
|||||||
import io.element.android.libraries.cachestore.api.CacheData
|
import io.element.android.libraries.cachestore.api.CacheData
|
||||||
import io.element.android.libraries.sessionstorage.test.InMemoryCacheStore
|
import io.element.android.libraries.sessionstorage.test.InMemoryCacheStore
|
||||||
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
|
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
|
||||||
|
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
|
||||||
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
||||||
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
|
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
|
||||||
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||||
@@ -150,9 +151,10 @@ class DefaultWellKnownStoreTest {
|
|||||||
private fun createDefaultWellKnownStore(
|
private fun createDefaultWellKnownStore(
|
||||||
cacheStore: InMemoryCacheStore = InMemoryCacheStore(),
|
cacheStore: InMemoryCacheStore = InMemoryCacheStore(),
|
||||||
systemClock: FakeSystemClock = FakeSystemClock(),
|
systemClock: FakeSystemClock = FakeSystemClock(),
|
||||||
|
elementWellKnownParser: ElementWellKnownParser = DefaultElementWellKnownParser(jsonProvider),
|
||||||
) = DefaultWellKnownStore(
|
) = DefaultWellKnownStore(
|
||||||
cacheStore = cacheStore,
|
cacheStore = cacheStore,
|
||||||
json = jsonProvider,
|
|
||||||
systemClock = systemClock,
|
systemClock = systemClock,
|
||||||
|
elementWellKnownParser = elementWellKnownParser,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+25
@@ -11,6 +11,7 @@
|
|||||||
package io.element.android.libraries.wellknown.impl
|
package io.element.android.libraries.wellknown.impl
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
import io.element.android.features.wellknown.test.FakeElementWellknownStore
|
import io.element.android.features.wellknown.test.FakeElementWellknownStore
|
||||||
import io.element.android.features.wellknown.test.anElementWellKnown
|
import io.element.android.features.wellknown.test.anElementWellKnown
|
||||||
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||||
@@ -19,6 +20,7 @@ import io.element.android.libraries.network.RetrofitFactory
|
|||||||
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
|
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
|
||||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||||
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
@@ -236,6 +238,27 @@ class DefaultWellknownRetrieverTest {
|
|||||||
assertThat(cacheStore.get(WELLKNOWN_URL)).isEqualTo(WellknownRetrieverResult.NotFound)
|
assertThat(cacheStore.get(WELLKNOWN_URL)).isEqualTo(WellknownRetrieverResult.NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `get element wellknown was overridden`() = runTest {
|
||||||
|
val getLambda = lambdaRecorder<Request, Call> { mockCall() }
|
||||||
|
val wellKnown = anElementWellKnown()
|
||||||
|
|
||||||
|
val sut = createDefaultWellknownRetriever(
|
||||||
|
callFactory = getLambda,
|
||||||
|
enterpriseService = FakeEnterpriseService(
|
||||||
|
overrideWellKnownResult = { wellKnown }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// The overridden value is returned
|
||||||
|
assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isEqualTo(
|
||||||
|
WellknownRetrieverResult.Success(wellKnown)
|
||||||
|
)
|
||||||
|
|
||||||
|
// And the endpoint is never hit
|
||||||
|
getLambda.assertions().isNeverCalled()
|
||||||
|
}
|
||||||
|
|
||||||
private fun defaultResponse(
|
private fun defaultResponse(
|
||||||
body: String = WELLKNOWN_CONTENT,
|
body: String = WELLKNOWN_CONTENT,
|
||||||
status: Int = 200,
|
status: Int = 200,
|
||||||
@@ -251,6 +274,7 @@ class DefaultWellknownRetrieverTest {
|
|||||||
callFactory: Call.Factory = Call.Factory { _: Request -> mockCall(defaultResponse()) },
|
callFactory: Call.Factory = Call.Factory { _: Request -> mockCall(defaultResponse()) },
|
||||||
cacheStore: FakeElementWellknownStore = FakeElementWellknownStore(),
|
cacheStore: FakeElementWellknownStore = FakeElementWellknownStore(),
|
||||||
jsonProvider: JsonProvider = DefaultJsonProvider(),
|
jsonProvider: JsonProvider = DefaultJsonProvider(),
|
||||||
|
enterpriseService: FakeEnterpriseService = FakeEnterpriseService(overrideWellKnownResult = { null }),
|
||||||
) = DefaultWellknownRetriever(
|
) = DefaultWellknownRetriever(
|
||||||
retrofitFactory = RetrofitFactory(
|
retrofitFactory = RetrofitFactory(
|
||||||
callFactory = { callFactory },
|
callFactory = { callFactory },
|
||||||
@@ -258,6 +282,7 @@ class DefaultWellknownRetrieverTest {
|
|||||||
),
|
),
|
||||||
jsonProvider = jsonProvider,
|
jsonProvider = jsonProvider,
|
||||||
elementWellknownStore = cacheStore,
|
elementWellknownStore = cacheStore,
|
||||||
|
enterpriseService = enterpriseService,
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ class KonsistPreviewTest {
|
|||||||
"BackgroundVerticalGradientPreview",
|
"BackgroundVerticalGradientPreview",
|
||||||
"ColorAliasesPreview",
|
"ColorAliasesPreview",
|
||||||
"EmojiItemWithPopupPreview",
|
"EmojiItemWithPopupPreview",
|
||||||
|
"EnterpriseExtraDeveloperOptionsRendererBottomSheetPreview",
|
||||||
|
"EnterpriseExtraDeveloperOptionsRendererListItemPreview",
|
||||||
"FocusedEventPreview",
|
"FocusedEventPreview",
|
||||||
"GradientFloatingActionButtonCircleShapePreview",
|
"GradientFloatingActionButtonCircleShapePreview",
|
||||||
"HeaderFooterPageScrollablePreview",
|
"HeaderFooterPageScrollablePreview",
|
||||||
|
|||||||
Reference in New Issue
Block a user