From d7e4e00b5d62e528cd2fe2bd95deb89e271dbe72 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 8 Aug 2025 15:17:00 +0200 Subject: [PATCH] Let enterprise build be able to override (or disable) the bug report URL. --- .../element/android/x/ElementXApplication.kt | 6 +--- .../android/appconfig/RageshakeConfig.kt | 6 ---- .../android/appnav/LoggedInFlowNode.kt | 6 +++- enterprise | 2 +- .../features/enterprise/api/BugReportUrl.kt | 16 +++++++++ .../enterprise/api/EnterpriseService.kt | 3 ++ .../api/SessionEnterpriseService.kt | 2 ++ .../impl/DefaultEnterpriseService.kt | 4 +++ .../impl/DefaultSessionEnterpriseService.kt | 1 + .../enterprise/test/FakeEnterpriseService.kt | 9 +++-- .../test/FakeSessionEnterpriseService.kt | 3 ++ .../features/home/impl/HomePresenter.kt | 2 +- .../features/home/impl/HomePresenterTest.kt | 20 +++++++++-- .../screens/onboarding/OnBoardingPresenter.kt | 3 +- ...DefaultAccountProviderAccessControlTest.kt | 19 +++++----- .../changeserver/ChangeServerPresenterTest.kt | 3 +- .../onboarding/OnBoardingPresenterTest.kt | 7 ++-- .../impl/root/PreferencesRootPresenter.kt | 2 +- .../impl/root/PreferencesRootPresenterTest.kt | 7 ++-- .../api/RageshakeFeatureAvailability.kt | 4 ++- features/rageshake/impl/build.gradle.kts | 2 ++ .../DefaultRageshakeFeatureAvailability.kt | 14 +++++--- .../crash/DefaultCrashDetectionPresenter.kt | 22 ++++++++---- .../DefaultRageshakePreferencesPresenter.kt | 2 +- .../impl/reporter/BugReportAppNameProvider.kt | 22 ++++++++++++ .../impl/reporter/BugReporterUrlProvider.kt | 3 +- .../impl/reporter/DefaultBugReporter.kt | 8 ++++- .../reporter/DefaultBugReporterUrlProvider.kt | 23 ++++++++++-- .../crash/ui/CrashDetectionPresenterTest.kt | 3 +- .../RageshakeDetectionPresenterTest.kt | 11 +++--- .../RageshakePreferencesPresenterTest.kt | 9 ++--- .../impl/reporter/DefaultBugReporterTest.kt | 3 +- .../DefaultBugReporterUrlProviderTest.kt | 36 ++++++++++++++++--- .../wellknown/api/ElementWellKnown.kt | 5 +-- .../libraries/wellknown/api/WellKnown.kt | 6 ++-- .../impl/DefaultWellknownRetriever.kt | 1 - .../impl/InternalElementWellKnown.kt | 2 ++ .../libraries/wellknown/impl/Mapper.kt | 1 + .../test/FakeSessionWellknownRetriever.kt | 26 ++++++++++++++ .../features/wellknown/test/Fixtures.kt | 20 +++++++++++ 40 files changed, 266 insertions(+), 78 deletions(-) create mode 100644 features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/BugReportUrl.kt create mode 100644 features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReportAppNameProvider.kt create mode 100644 libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt create mode 100644 libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt diff --git a/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt b/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt index 30bef76339..a4bfe0c60d 100644 --- a/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt +++ b/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt @@ -9,8 +9,6 @@ package io.element.android.x import android.app.Application import androidx.startup.AppInitializer -import io.element.android.appconfig.RageshakeConfig -import io.element.android.appconfig.isEnabled import io.element.android.features.cachecleaner.api.CacheCleanerInitializer import io.element.android.libraries.di.DaggerComponentOwner import io.element.android.x.di.AppComponent @@ -25,9 +23,7 @@ class ElementXApplication : Application(), DaggerComponentOwner { override fun onCreate() { super.onCreate() AppInitializer.getInstance(this).apply { - if (RageshakeConfig.isEnabled) { - initializeComponent(CrashInitializer::class.java) - } + initializeComponent(CrashInitializer::class.java) initializeComponent(PlatformInitializer::class.java) initializeComponent(CacheCleanerInitializer::class.java) } diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt index 8c836bc8a2..1dd482cc40 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt @@ -25,9 +25,3 @@ object RageshakeConfig { */ const val MAX_LOG_UPLOAD_SIZE = 50 * 1024 * 1024L } - -/** - * Whether the rageshake feature is enabled. - */ -val RageshakeConfig.isEnabled: Boolean - get() = BUG_REPORT_URL.isNotEmpty() && BUG_REPORT_APP_NAME.isNotEmpty() diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 043dc75a92..25c89abce9 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -47,6 +47,7 @@ import io.element.android.appnav.room.RoomFlowNode import io.element.android.appnav.room.RoomNavigationTarget import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode import io.element.android.features.createroom.api.CreateRoomEntryPoint +import io.element.android.features.enterprise.api.SessionEnterpriseService import io.element.android.features.ftue.api.FtueEntryPoint import io.element.android.features.ftue.api.state.FtueService import io.element.android.features.ftue.api.state.FtueState @@ -125,6 +126,7 @@ class LoggedInFlowNode @AssistedInject constructor( private val logoutEntryPoint: LogoutEntryPoint, private val incomingVerificationEntryPoint: IncomingVerificationEntryPoint, private val mediaPreviewConfigMigration: MediaPreviewConfigMigration, + private val sessionEnterpriseService: SessionEnterpriseService, snackbarDispatcher: SnackbarDispatcher, ) : BaseFlowNode( backstack = BackStack( @@ -182,7 +184,9 @@ class LoggedInFlowNode @AssistedInject constructor( override fun onBuilt() { super.onBuilt() - + lifecycleScope.launch { + sessionEnterpriseService.init() + } lifecycle.subscribe( onCreate = { appNavigationStateService.onNavigateToSession(id, matrixClient.sessionId) diff --git a/enterprise b/enterprise index b7ababb953..f3546ac27a 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit b7ababb9537da8bec254b8ed00b5a4122e9f3e3b +Subproject commit f3546ac27aff0b9b0875bbf7fcc496a16ac22595 diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/BugReportUrl.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/BugReportUrl.kt new file mode 100644 index 0000000000..8c1b659ab6 --- /dev/null +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/BugReportUrl.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2025 New Vector 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.enterprise.api + +sealed interface BugReportUrl { + data object UseDefault : BugReportUrl + data object Disabled : BugReportUrl + data class Custom( + val url: String, + ) : BugReportUrl +} diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt index 4e2a3cc1f2..03ecda80c0 100644 --- a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt @@ -9,6 +9,7 @@ package io.element.android.features.enterprise.api import io.element.android.compound.tokens.generated.SemanticColors import io.element.android.libraries.matrix.api.core.SessionId +import kotlinx.coroutines.flow.Flow interface EnterpriseService { val isEnterpriseBuild: Boolean @@ -22,6 +23,8 @@ interface EnterpriseService { fun firebasePushGateway(): String? fun unifiedPushDefaultPushGateway(): String? + val bugReportUrlFlow: Flow + companion object { const val ANY_ACCOUNT_PROVIDER = "*" } diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt index c67b94476b..aff3d93e8e 100644 --- a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt @@ -9,4 +9,6 @@ package io.element.android.features.enterprise.api interface SessionEnterpriseService { suspend fun isElementCallAvailable(): Boolean + + suspend fun init() } diff --git a/features/enterprise/impl/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt b/features/enterprise/impl/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt index 7f4f4b387b..d5c23788d6 100644 --- a/features/enterprise/impl/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt +++ b/features/enterprise/impl/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt @@ -11,9 +11,11 @@ import com.squareup.anvil.annotations.ContributesBinding import io.element.android.compound.tokens.generated.SemanticColors import io.element.android.compound.tokens.generated.compoundColorsDark import io.element.android.compound.tokens.generated.compoundColorsLight +import io.element.android.features.enterprise.api.BugReportUrl import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.core.SessionId +import kotlinx.coroutines.flow.flowOf import javax.inject.Inject @ContributesBinding(AppScope::class) @@ -31,4 +33,6 @@ class DefaultEnterpriseService @Inject constructor() : EnterpriseService { override fun firebasePushGateway(): String? = null override fun unifiedPushDefaultPushGateway(): String? = null + + override val bugReportUrlFlow = flowOf(BugReportUrl.UseDefault) } diff --git a/features/enterprise/impl/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt b/features/enterprise/impl/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt index 2728bdad6a..47f52f6ac1 100644 --- a/features/enterprise/impl/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt +++ b/features/enterprise/impl/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt @@ -14,5 +14,6 @@ import javax.inject.Inject @ContributesBinding(SessionScope::class) class DefaultSessionEnterpriseService @Inject constructor() : SessionEnterpriseService { + override suspend fun init() = Unit override suspend fun isElementCallAvailable(): Boolean = true } diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt index 14195e1243..30ccc4c48d 100644 --- a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt +++ b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt @@ -8,10 +8,14 @@ package io.element.android.features.enterprise.test import io.element.android.compound.tokens.generated.SemanticColors +import io.element.android.features.enterprise.api.BugReportUrl import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.simulateLongTask +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow class FakeEnterpriseService( override val isEnterpriseBuild: Boolean = false, @@ -51,7 +55,6 @@ class FakeEnterpriseService( return unifiedPushDefaultPushGatewayResult() } - companion object { - const val A_FAKE_HOMESERVER = "a_fake_homeserver" - } + val bugReportUrlMutableFlow = MutableStateFlow(BugReportUrl.UseDefault) + override val bugReportUrlFlow: Flow = bugReportUrlMutableFlow.asStateFlow() } diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt index a1e811ed10..e3125a19e6 100644 --- a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt +++ b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt @@ -14,6 +14,9 @@ import io.element.android.tests.testutils.simulateLongTask class FakeSessionEnterpriseService( private val isElementCallAvailableResult: () -> Boolean = { lambdaError() }, ) : SessionEnterpriseService { + override suspend fun init() { + } + override suspend fun isElementCallAvailable(): Boolean = simulateLongTask { isElementCallAvailableResult() } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomePresenter.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomePresenter.kt index 894ed29e8d..c5eea40796 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomePresenter.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomePresenter.kt @@ -43,7 +43,7 @@ class HomePresenter @Inject constructor( override fun present(): HomeState { val matrixUser = client.userProfile.collectAsState() val isOnline by syncService.isOnline.collectAsState() - val canReportBug = remember { rageshakeFeatureAvailability.isAvailable() } + val canReportBug by remember { rageshakeFeatureAvailability.isAvailable() }.collectAsState(false) val roomListState = roomListPresenter.present() val isSpaceFeatureEnabled by remember { featureFlagService.isFeatureEnabledFlow(FeatureFlags.Space) diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/HomePresenterTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/HomePresenterTest.kt index 6913ea19bf..a7917af658 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/HomePresenterTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/HomePresenterTest.kt @@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.sync.FakeSyncService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.test +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -49,7 +50,7 @@ class HomePresenterTest { matrixClient.givenGetProfileResult(matrixClient.sessionId, Result.success(MatrixUser(matrixClient.sessionId, A_USER_NAME, AN_AVATAR_URL))) val presenter = createHomePresenter( client = matrixClient, - rageshakeFeatureAvailability = { false }, + rageshakeFeatureAvailability = { flowOf(false) }, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -66,6 +67,21 @@ class HomePresenterTest { } } + @Test + fun `present - can report bug`() = runTest { + val presenter = createHomePresenter( + rageshakeFeatureAvailability = { flowOf(true) }, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.canReportBug).isFalse() + val finalState = awaitItem() + assertThat(finalState.canReportBug).isTrue() + } + } + @Test fun `present - space feature enabled`() = runTest { val presenter = createHomePresenter( @@ -132,7 +148,7 @@ class HomePresenterTest { client: MatrixClient = FakeMatrixClient(), syncService: SyncService = FakeSyncService(), snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(), - rageshakeFeatureAvailability: RageshakeFeatureAvailability = RageshakeFeatureAvailability { true }, + rageshakeFeatureAvailability: RageshakeFeatureAvailability = RageshakeFeatureAvailability { flowOf(false) }, indicatorService: IndicatorService = FakeIndicatorService(), featureFlagService: FeatureFlagService = FakeFeatureFlagService() ) = HomePresenter( diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt index 90e4e99c37..ba60cfd222 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt @@ -8,6 +8,7 @@ package io.element.android.features.login.impl.screens.onboarding import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState @@ -82,7 +83,7 @@ class OnBoardingPresenter @AssistedInject constructor( value = linkAccountProvider == null && featureFlagService.isFeatureEnabled(FeatureFlags.QrCodeLogin) } - val canReportBug = remember { rageshakeFeatureAvailability.isAvailable() } + val canReportBug by remember { rageshakeFeatureAvailability.isAvailable() }.collectAsState(false) var showReportBug by rememberSaveable { mutableStateOf(false) } val loginMode by loginHelper.collectLoginMode() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt index b0ee59cea8..f559c57bf0 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt @@ -11,6 +11,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.login.impl.changeserver.AccountProviderAccessException import io.element.android.features.wellknown.test.FakeWellknownRetriever +import io.element.android.features.wellknown.test.anElementWellKnown import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2 import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_URL @@ -25,7 +26,7 @@ class DefaultAccountProviderAccessControlTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = false, isAllowedToConnectToHomeserver = true, - elementWellKnown = ElementWellKnown( + elementWellKnown = anElementWellKnown( enforceElementPro = true, ), ) @@ -38,7 +39,7 @@ class DefaultAccountProviderAccessControlTest { isEnterpriseBuild = false, // false here. isAllowedToConnectToHomeserver = false, - elementWellKnown = ElementWellKnown( + elementWellKnown = anElementWellKnown( enforceElementPro = true, ), ) @@ -50,7 +51,7 @@ class DefaultAccountProviderAccessControlTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = false, isAllowedToConnectToHomeserver = true, - elementWellKnown = ElementWellKnown( + elementWellKnown = anElementWellKnown( enforceElementPro = false, ), ) @@ -62,7 +63,7 @@ class DefaultAccountProviderAccessControlTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = false, isAllowedToConnectToHomeserver = true, - elementWellKnown = ElementWellKnown( + elementWellKnown = anElementWellKnown( enforceElementPro = null, ), ) @@ -85,7 +86,7 @@ class DefaultAccountProviderAccessControlTest { isEnterpriseBuild = false, isAllowedToConnectToHomeserver = false, allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2), - elementWellKnown = ElementWellKnown( + elementWellKnown = anElementWellKnown( enforceElementPro = false, ), ) @@ -97,7 +98,7 @@ class DefaultAccountProviderAccessControlTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = true, isAllowedToConnectToHomeserver = true, - elementWellKnown = ElementWellKnown( + elementWellKnown = anElementWellKnown( enforceElementPro = true, ), ) @@ -109,7 +110,7 @@ class DefaultAccountProviderAccessControlTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = true, isAllowedToConnectToHomeserver = true, - elementWellKnown = ElementWellKnown( + elementWellKnown = anElementWellKnown( enforceElementPro = false, ), ) @@ -122,7 +123,7 @@ class DefaultAccountProviderAccessControlTest { isEnterpriseBuild = true, isAllowedToConnectToHomeserver = false, allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2), - elementWellKnown = ElementWellKnown( + elementWellKnown = anElementWellKnown( enforceElementPro = true, ), ) @@ -135,7 +136,7 @@ class DefaultAccountProviderAccessControlTest { isEnterpriseBuild = true, isAllowedToConnectToHomeserver = false, allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2), - elementWellKnown = ElementWellKnown( + elementWellKnown = anElementWellKnown( enforceElementPro = false, ), ) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt index dc2b552ff5..620df9bba4 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt @@ -15,6 +15,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.wellknown.test.FakeWellknownRetriever +import io.element.android.features.wellknown.test.anElementWellKnown import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.matrix.test.A_HOMESERVER @@ -114,7 +115,7 @@ class ChangeServerPresenterTest { @Test fun `present - change server element pro required error`() = runTest { val getElementWellKnownResult = lambdaRecorder { - ElementWellKnown( + anElementWellKnown( enforceElementPro = true, ) } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt index 035be8f31b..c559db9056 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt @@ -36,6 +36,8 @@ import io.element.android.libraries.oidc.test.customtab.FakeOidcActionFlow import io.element.android.libraries.wellknown.api.WellknownRetriever import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.test +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -78,7 +80,6 @@ class OnBoardingPresenterTest { enterpriseService = FakeEnterpriseService( defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, EnterpriseService.ANY_ACCOUNT_PROVIDER) }, ), - rageshakeFeatureAvailability = { true }, ) presenter.test { val initialState = awaitItem() @@ -94,7 +95,7 @@ class OnBoardingPresenterTest { @Test fun `present - clicking on version 7 times has no effect if rageshake not available`() = runTest { val presenter = createPresenter( - rageshakeFeatureAvailability = { false }, + rageshakeFeatureAvailability = { flowOf(false) }, ) presenter.test { skipItems(1) @@ -239,7 +240,7 @@ private fun createPresenter( featureFlagService: FeatureFlagService = FakeFeatureFlagService(), enterpriseService: EnterpriseService = FakeEnterpriseService(), wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(), - rageshakeFeatureAvailability: () -> Boolean = { true }, + rageshakeFeatureAvailability: () -> Flow = { flowOf(true) }, loginHelper: LoginHelper = createLoginHelper(), ) = OnBoardingPresenter( params = params, 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 6fd2d635e1..ee73b7f721 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 @@ -83,7 +83,7 @@ class PreferencesRootPresenter @Inject constructor( var canDeactivateAccount by remember { mutableStateOf(false) } - val canReportBug = remember { rageshakeFeatureAvailability.isAvailable() } + val canReportBug by remember { rageshakeFeatureAvailability.isAvailable() }.collectAsState(false) LaunchedEffect(Unit) { canDeactivateAccount = matrixClient.canDeactivateAccount() } 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 1f1b527a45..a65e61661e 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 @@ -32,6 +32,7 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -102,7 +103,7 @@ class PreferencesRootPresenterTest { ) createPresenter( matrixClient = matrixClient, - rageshakeFeatureAvailability = { false }, + rageshakeFeatureAvailability = { flowOf(false) }, ).test { val initialState = awaitItem() assertThat(initialState.canReportBug).isFalse() @@ -119,7 +120,7 @@ class PreferencesRootPresenterTest { val indicatorService = FakeIndicatorService() createPresenter( matrixClient = matrixClient, - rageshakeFeatureAvailability = { false }, + rageshakeFeatureAvailability = { flowOf(false) }, indicatorService = indicatorService, ).test { skipItems(1) @@ -185,7 +186,7 @@ class PreferencesRootPresenterTest { matrixClient: FakeMatrixClient = FakeMatrixClient(), sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(), showDeveloperSettingsProvider: ShowDeveloperSettingsProvider = ShowDeveloperSettingsProvider(aBuildMeta(BuildType.DEBUG)), - rageshakeFeatureAvailability: RageshakeFeatureAvailability = RageshakeFeatureAvailability { true }, + rageshakeFeatureAvailability: RageshakeFeatureAvailability = RageshakeFeatureAvailability { flowOf(true) }, indicatorService: IndicatorService = FakeIndicatorService(), ) = PreferencesRootPresenter( matrixClient = matrixClient, diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/RageshakeFeatureAvailability.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/RageshakeFeatureAvailability.kt index 34e740d4ab..a22040355e 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/RageshakeFeatureAvailability.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/RageshakeFeatureAvailability.kt @@ -7,6 +7,8 @@ package io.element.android.features.rageshake.api +import kotlinx.coroutines.flow.Flow + fun interface RageshakeFeatureAvailability { - fun isAvailable(): Boolean + fun isAvailable(): Flow } diff --git a/features/rageshake/impl/build.gradle.kts b/features/rageshake/impl/build.gradle.kts index 2deeff4955..addfe3d794 100644 --- a/features/rageshake/impl/build.gradle.kts +++ b/features/rageshake/impl/build.gradle.kts @@ -26,6 +26,7 @@ setupAnvil() dependencies { implementation(projects.appconfig) + implementation(projects.features.enterprise.api) implementation(projects.services.toolbox.api) implementation(projects.libraries.androidutils) implementation(projects.libraries.core) @@ -50,6 +51,7 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(libs.test.mockk) + testImplementation(projects.features.enterprise.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.sessionStorage.implMemory) testImplementation(projects.libraries.sessionStorage.test) diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailability.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailability.kt index 5f7548e9ec..c9f78a23ce 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailability.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailability.kt @@ -8,15 +8,19 @@ package io.element.android.features.rageshake.impl import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.appconfig.RageshakeConfig -import io.element.android.appconfig.isEnabled import io.element.android.features.rageshake.api.RageshakeFeatureAvailability +import io.element.android.features.rageshake.impl.reporter.BugReporterUrlProvider import io.element.android.libraries.di.AppScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import javax.inject.Inject @ContributesBinding(AppScope::class) -class DefaultRageshakeFeatureAvailability @Inject constructor() : RageshakeFeatureAvailability { - override fun isAvailable(): Boolean { - return RageshakeConfig.isEnabled +class DefaultRageshakeFeatureAvailability @Inject constructor( + private val bugReporterUrlProvider: BugReporterUrlProvider, +) : RageshakeFeatureAvailability { + override fun isAvailable(): Flow { + return bugReporterUrlProvider.provide() + .map { it != null } } } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt index fffb87722a..7136393238 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt @@ -5,10 +5,13 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package io.element.android.features.rageshake.impl.crash import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import com.squareup.anvil.annotations.ContributesBinding @@ -19,6 +22,8 @@ import io.element.android.features.rageshake.api.crash.CrashDetectionState import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.AppScope import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch import javax.inject.Inject @@ -32,12 +37,15 @@ class DefaultCrashDetectionPresenter @Inject constructor( @Composable override fun present(): CrashDetectionState { val localCoroutineScope = rememberCoroutineScope() - val crashDetected = remember { - if (rageshakeFeatureAvailability.isAvailable()) { - crashDataStore.appHasCrashed() - } else { - flowOf(false) - } + val crashDetected by remember { + rageshakeFeatureAvailability.isAvailable() + .flatMapLatest { isAvailable -> + if (isAvailable) { + crashDataStore.appHasCrashed() + } else { + flowOf(false) + } + } }.collectAsState(false) fun handleEvents(event: CrashDetectionEvents) { @@ -49,7 +57,7 @@ class DefaultCrashDetectionPresenter @Inject constructor( return CrashDetectionState( appName = buildMeta.applicationName, - crashDetected = crashDetected.value, + crashDetected = crashDetected, eventSink = ::handleEvents ) } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt index b4f45a3bdc..6096388e21 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt @@ -39,7 +39,7 @@ class DefaultRageshakePreferencesPresenter @Inject constructor( val isSupported: MutableState = rememberSaveable { mutableStateOf(rageshake.isAvailable()) } - val isFeatureAvailable = remember { rageshakeFeatureAvailability.isAvailable() } + val isFeatureAvailable by remember { rageshakeFeatureAvailability.isAvailable() }.collectAsState(false) val isEnabled by remember { rageshakeDataStore.isEnabled() }.collectAsState(initial = false) diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReportAppNameProvider.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReportAppNameProvider.kt new file mode 100644 index 0000000000..b63dfcf669 --- /dev/null +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReportAppNameProvider.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2025 New Vector 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.rageshake.impl.reporter + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.appconfig.RageshakeConfig +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +fun interface BugReportAppNameProvider { + fun provide(): String +} + +@ContributesBinding(AppScope::class) +class DefaultBugReportAppNameProvider @Inject constructor() : BugReportAppNameProvider { + override fun provide(): String = RageshakeConfig.BUG_REPORT_APP_NAME +} diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterUrlProvider.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterUrlProvider.kt index d516a9492a..e7e0e4e060 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterUrlProvider.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterUrlProvider.kt @@ -7,8 +7,9 @@ package io.element.android.features.rageshake.impl.reporter +import kotlinx.coroutines.flow.Flow import okhttp3.HttpUrl fun interface BugReporterUrlProvider { - fun provide(): HttpUrl + fun provide(): Flow } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt index 028b671eb6..d56c75f464 100755 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt @@ -114,6 +114,12 @@ class DefaultBugReporter @Inject constructor( canContact: Boolean, listener: BugReporterListener, ) { + val url = bugReporterUrlProvider.provide().first() + if (url == null) { + // It should not happen, but if the URL is null, we cannot proceed + Timber.e("## sendBugReport() : bug report URL is null") + error("Bug report URL is null, cannot send bug report") + } // enumerate files to delete val bugReportFiles: MutableList = ArrayList() var response: Response? = null @@ -243,7 +249,7 @@ class DefaultBugReporter @Inject constructor( } // build the request val request = Request.Builder() - .url(bugReporterUrlProvider.provide()) + .url(url) .post(requestBody) .build() var errorMessage: String? = null diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProvider.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProvider.kt index e29b85cf77..3ebe5c1c29 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProvider.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProvider.kt @@ -9,14 +9,31 @@ package io.element.android.features.rageshake.impl.reporter import com.squareup.anvil.annotations.ContributesBinding import io.element.android.appconfig.RageshakeConfig +import io.element.android.features.enterprise.api.BugReportUrl +import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.libraries.di.AppScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import javax.inject.Inject @ContributesBinding(AppScope::class) -class DefaultBugReporterUrlProvider @Inject constructor() : BugReporterUrlProvider { - override fun provide(): HttpUrl { - return RageshakeConfig.BUG_REPORT_URL.toHttpUrl() +class DefaultBugReporterUrlProvider @Inject constructor( + private val bugReportAppNameProvider: BugReportAppNameProvider, + private val enterpriseService: EnterpriseService, +) : BugReporterUrlProvider { + override fun provide(): Flow { + if (bugReportAppNameProvider.provide().isEmpty()) return flowOf(null) + return enterpriseService.bugReportUrlFlow + .map { bugReportUrl -> + when (bugReportUrl) { + is BugReportUrl.Custom -> bugReportUrl.url + BugReportUrl.Disabled -> null + BugReportUrl.UseDefault -> RageshakeConfig.BUG_REPORT_URL.takeIf { it.isNotEmpty() } + } + } + .map { it?.toHttpUrl() } } } diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt index 92dfbb02a2..7c14177399 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt @@ -18,6 +18,7 @@ import io.element.android.features.rageshake.impl.crash.FakeCrashDataStore import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.tests.testutils.WarmUpRule +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -104,6 +105,6 @@ class CrashDetectionPresenterTest { ) = DefaultCrashDetectionPresenter( buildMeta = buildMeta, crashDataStore = crashDataStore, - rageshakeFeatureAvailability = { isFeatureAvailable }, + rageshakeFeatureAvailability = { flowOf(isFeatureAvailable) }, ) } diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt index 6f433a78d7..451bfd82ed 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt @@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.tests.testutils.WarmUpRule import io.mockk.mockk import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.BeforeClass import org.junit.Rule @@ -52,7 +53,7 @@ class RageshakeDetectionPresenterTest { preferencesPresenter = DefaultRageshakePreferencesPresenter( rageshake = rageshake, rageshakeDataStore = rageshakeDataStore, - rageshakeFeatureAvailability = { true }, + rageshakeFeatureAvailability = { flowOf(true) }, ) ) moleculeFlow(RecompositionMode.Immediate) { @@ -77,7 +78,7 @@ class RageshakeDetectionPresenterTest { preferencesPresenter = DefaultRageshakePreferencesPresenter( rageshake = rageshake, rageshakeDataStore = rageshakeDataStore, - rageshakeFeatureAvailability = { true }, + rageshakeFeatureAvailability = { flowOf(true) }, ) ) moleculeFlow(RecompositionMode.Immediate) { @@ -103,7 +104,7 @@ class RageshakeDetectionPresenterTest { preferencesPresenter = DefaultRageshakePreferencesPresenter( rageshake = rageshake, rageshakeDataStore = rageshakeDataStore, - rageshakeFeatureAvailability = { true }, + rageshakeFeatureAvailability = { flowOf(true) }, ) ) moleculeFlow(RecompositionMode.Immediate) { @@ -138,7 +139,7 @@ class RageshakeDetectionPresenterTest { preferencesPresenter = DefaultRageshakePreferencesPresenter( rageshake = rageshake, rageshakeDataStore = rageshakeDataStore, - rageshakeFeatureAvailability = { true }, + rageshakeFeatureAvailability = { flowOf(true) }, ) ) moleculeFlow(RecompositionMode.Immediate) { @@ -173,7 +174,7 @@ class RageshakeDetectionPresenterTest { preferencesPresenter = DefaultRageshakePreferencesPresenter( rageshake = rageshake, rageshakeDataStore = rageshakeDataStore, - rageshakeFeatureAvailability = { true }, + rageshakeFeatureAvailability = { flowOf(true) }, ) ) moleculeFlow(RecompositionMode.Immediate) { diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt index ba68345440..df34e1cf48 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt @@ -16,6 +16,7 @@ import io.element.android.features.rageshake.impl.rageshake.A_SENSITIVITY import io.element.android.features.rageshake.impl.rageshake.FakeRageShake import io.element.android.features.rageshake.impl.rageshake.FakeRageshakeDataStore import io.element.android.tests.testutils.WarmUpRule +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -29,7 +30,7 @@ class RageshakePreferencesPresenterTest { val presenter = DefaultRageshakePreferencesPresenter( FakeRageShake(isAvailableValue = true), FakeRageshakeDataStore(isEnabled = true), - rageshakeFeatureAvailability = { true }, + rageshakeFeatureAvailability = { flowOf(true) }, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -46,7 +47,7 @@ class RageshakePreferencesPresenterTest { val presenter = DefaultRageshakePreferencesPresenter( FakeRageShake(isAvailableValue = false), FakeRageshakeDataStore(isEnabled = true), - rageshakeFeatureAvailability = { true }, + rageshakeFeatureAvailability = { flowOf(true) }, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -63,7 +64,7 @@ class RageshakePreferencesPresenterTest { val presenter = DefaultRageshakePreferencesPresenter( FakeRageShake(isAvailableValue = true), FakeRageshakeDataStore(isEnabled = true), - rageshakeFeatureAvailability = { true }, + rageshakeFeatureAvailability = { flowOf(true) }, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -83,7 +84,7 @@ class RageshakePreferencesPresenterTest { val presenter = DefaultRageshakePreferencesPresenter( FakeRageShake(isAvailableValue = true), FakeRageshakeDataStore(isEnabled = true), - rageshakeFeatureAvailability = { true }, + rageshakeFeatureAvailability = { flowOf(true) }, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt index c7c424bfae..1a9592739d 100755 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt @@ -31,6 +31,7 @@ import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionSt import io.element.android.libraries.sessionstorage.test.aSessionData import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import okhttp3.MultipartReader @@ -464,7 +465,7 @@ class DefaultBugReporterTest { userAgentProvider = DefaultUserAgentProvider(buildMeta, FakeSdkMetadata("123456789")), sessionStore = sessionStore, buildMeta = buildMeta, - bugReporterUrlProvider = { server.url("/") }, + bugReporterUrlProvider = { flowOf(server.url("/")) }, sdkMetadata = FakeSdkMetadata("123456789"), matrixClientProvider = matrixClientProvider, tracingService = tracingService, diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt index 71563892dc..fb7464adf7 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt @@ -7,18 +7,44 @@ package io.element.android.features.rageshake.impl.reporter +import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.appconfig.RageshakeConfig +import io.element.android.features.enterprise.api.BugReportUrl +import io.element.android.features.enterprise.test.FakeEnterpriseService +import kotlinx.coroutines.test.runTest import okhttp3.HttpUrl.Companion.toHttpUrl import org.junit.Test class DefaultBugReporterUrlProviderTest { @Test - fun `test DefaultBugReporterUrlProvider`() { - val sut = DefaultBugReporterUrlProvider() - if (RageshakeConfig.BUG_REPORT_URL.isNotEmpty()) { - val result = sut.provide() - assertThat(result).isEqualTo(RageshakeConfig.BUG_REPORT_URL.toHttpUrl()) + fun `provide return values when there is an rageshake app name`() = runTest { + val enterpriseService = FakeEnterpriseService() + val sut = DefaultBugReporterUrlProvider( + bugReportAppNameProvider = { "rageshakeAppName" }, + enterpriseService = enterpriseService, + ) + sut.provide().test { + assertThat(awaitItem()).isEqualTo( + RageshakeConfig.BUG_REPORT_URL.takeIf { it.isNotEmpty() }?.toHttpUrl() + ) + enterpriseService.bugReportUrlMutableFlow.emit(BugReportUrl.Disabled) + assertThat(awaitItem()).isNull() + enterpriseService.bugReportUrlMutableFlow.emit(BugReportUrl.Custom("https://aURL.org")) + assertThat(awaitItem()).isEqualTo("https://aURL.org".toHttpUrl()) + } + } + + @Test + fun `provide return null when there is no rageshake app name`() = runTest { + val enterpriseService = FakeEnterpriseService() + val sut = DefaultBugReporterUrlProvider( + bugReportAppNameProvider = { "" }, + enterpriseService = enterpriseService, + ) + sut.provide().test { + assertThat(awaitItem()).isNull() + awaitComplete() } } } diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt index 5d6563e3c3..064416eec1 100644 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt +++ b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.wellknown.api data class ElementWellKnown( - val registrationHelperUrl: String? = null, - val enforceElementPro: Boolean? = null, + val registrationHelperUrl: String?, + val enforceElementPro: Boolean?, + val rageshakeUrl: String?, ) diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellKnown.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellKnown.kt index 9b41b52e2f..59f63d1655 100644 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellKnown.kt +++ b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellKnown.kt @@ -8,10 +8,10 @@ package io.element.android.libraries.wellknown.api data class WellKnown( - val homeServer: WellKnownBaseConfig? = null, - val identityServer: WellKnownBaseConfig? = null, + val homeServer: WellKnownBaseConfig?, + val identityServer: WellKnownBaseConfig?, ) data class WellKnownBaseConfig( - val baseURL: String? = null + val baseURL: String? ) diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt index 0d45f60be5..5d67cd32f0 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt @@ -13,7 +13,6 @@ import io.element.android.libraries.di.AppScope import io.element.android.libraries.network.RetrofitFactory import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.WellKnown -import io.element.android.libraries.wellknown.api.WellKnownBaseConfig import io.element.android.libraries.wellknown.api.WellknownRetriever import timber.log.Timber import javax.inject.Inject diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt index 9e902d9b92..e81d78d498 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt @@ -25,4 +25,6 @@ data class InternalElementWellKnown( val registrationHelperUrl: String? = null, @SerialName("enforce_element_pro") val enforceElementPro: Boolean? = null, + @SerialName("rageshake_url") + val rageshakeUrl: String? = null, ) diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt index 8625571c62..9c1618f699 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt @@ -14,6 +14,7 @@ import io.element.android.libraries.wellknown.api.WellKnownBaseConfig internal fun InternalElementWellKnown.map() = ElementWellKnown( registrationHelperUrl = registrationHelperUrl, enforceElementPro = enforceElementPro, + rageshakeUrl = rageshakeUrl, ) internal fun InternalWellKnown.map() = WellKnown( diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt new file mode 100644 index 0000000000..6c2c141622 --- /dev/null +++ b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2025 New Vector 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.wellknown.test + +import io.element.android.libraries.wellknown.api.ElementWellKnown +import io.element.android.libraries.wellknown.api.SessionWellknownRetriever +import io.element.android.libraries.wellknown.api.WellKnown +import io.element.android.tests.testutils.simulateLongTask + +class FakeSessionWellknownRetriever( + private val getWellKnownResult: () -> WellKnown? = { null }, + private val getElementWellKnownResult: () -> ElementWellKnown? = { null }, +) : SessionWellknownRetriever { + override suspend fun getWellKnown(): WellKnown? = simulateLongTask { + getWellKnownResult() + } + + override suspend fun getElementWellKnown(): ElementWellKnown? = simulateLongTask { + getElementWellKnownResult() + } +} diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt new file mode 100644 index 0000000000..686026b78d --- /dev/null +++ b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2025 New Vector 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.wellknown.test + +import io.element.android.libraries.wellknown.api.ElementWellKnown + +fun anElementWellKnown( + registrationHelperUrl: String? = null, + enforceElementPro: Boolean? = null, + rageshakeUrl: String? = null, +) = ElementWellKnown( + registrationHelperUrl = registrationHelperUrl, + enforceElementPro = enforceElementPro, + rageshakeUrl = rageshakeUrl, +)