Let enterprise build be able to override (or disable) the bug report URL.
This commit is contained in:
@@ -9,8 +9,6 @@ package io.element.android.x
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.startup.AppInitializer
|
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.features.cachecleaner.api.CacheCleanerInitializer
|
||||||
import io.element.android.libraries.di.DaggerComponentOwner
|
import io.element.android.libraries.di.DaggerComponentOwner
|
||||||
import io.element.android.x.di.AppComponent
|
import io.element.android.x.di.AppComponent
|
||||||
@@ -25,9 +23,7 @@ class ElementXApplication : Application(), DaggerComponentOwner {
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
AppInitializer.getInstance(this).apply {
|
AppInitializer.getInstance(this).apply {
|
||||||
if (RageshakeConfig.isEnabled) {
|
initializeComponent(CrashInitializer::class.java)
|
||||||
initializeComponent(CrashInitializer::class.java)
|
|
||||||
}
|
|
||||||
initializeComponent(PlatformInitializer::class.java)
|
initializeComponent(PlatformInitializer::class.java)
|
||||||
initializeComponent(CacheCleanerInitializer::class.java)
|
initializeComponent(CacheCleanerInitializer::class.java)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,3 @@ object RageshakeConfig {
|
|||||||
*/
|
*/
|
||||||
const val MAX_LOG_UPLOAD_SIZE = 50 * 1024 * 1024L
|
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()
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import io.element.android.appnav.room.RoomFlowNode
|
|||||||
import io.element.android.appnav.room.RoomNavigationTarget
|
import io.element.android.appnav.room.RoomNavigationTarget
|
||||||
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
||||||
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
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.FtueEntryPoint
|
||||||
import io.element.android.features.ftue.api.state.FtueService
|
import io.element.android.features.ftue.api.state.FtueService
|
||||||
import io.element.android.features.ftue.api.state.FtueState
|
import io.element.android.features.ftue.api.state.FtueState
|
||||||
@@ -125,6 +126,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||||||
private val logoutEntryPoint: LogoutEntryPoint,
|
private val logoutEntryPoint: LogoutEntryPoint,
|
||||||
private val incomingVerificationEntryPoint: IncomingVerificationEntryPoint,
|
private val incomingVerificationEntryPoint: IncomingVerificationEntryPoint,
|
||||||
private val mediaPreviewConfigMigration: MediaPreviewConfigMigration,
|
private val mediaPreviewConfigMigration: MediaPreviewConfigMigration,
|
||||||
|
private val sessionEnterpriseService: SessionEnterpriseService,
|
||||||
snackbarDispatcher: SnackbarDispatcher,
|
snackbarDispatcher: SnackbarDispatcher,
|
||||||
) : BaseFlowNode<LoggedInFlowNode.NavTarget>(
|
) : BaseFlowNode<LoggedInFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
@@ -182,7 +184,9 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||||||
|
|
||||||
override fun onBuilt() {
|
override fun onBuilt() {
|
||||||
super.onBuilt()
|
super.onBuilt()
|
||||||
|
lifecycleScope.launch {
|
||||||
|
sessionEnterpriseService.init()
|
||||||
|
}
|
||||||
lifecycle.subscribe(
|
lifecycle.subscribe(
|
||||||
onCreate = {
|
onCreate = {
|
||||||
appNavigationStateService.onNavigateToSession(id, matrixClient.sessionId)
|
appNavigationStateService.onNavigateToSession(id, matrixClient.sessionId)
|
||||||
|
|||||||
Submodule enterprise updated: b7ababb953...f3546ac27a
@@ -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
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ package io.element.android.features.enterprise.api
|
|||||||
|
|
||||||
import io.element.android.compound.tokens.generated.SemanticColors
|
import io.element.android.compound.tokens.generated.SemanticColors
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface EnterpriseService {
|
interface EnterpriseService {
|
||||||
val isEnterpriseBuild: Boolean
|
val isEnterpriseBuild: Boolean
|
||||||
@@ -22,6 +23,8 @@ interface EnterpriseService {
|
|||||||
fun firebasePushGateway(): String?
|
fun firebasePushGateway(): String?
|
||||||
fun unifiedPushDefaultPushGateway(): String?
|
fun unifiedPushDefaultPushGateway(): String?
|
||||||
|
|
||||||
|
val bugReportUrlFlow: Flow<BugReportUrl>
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ANY_ACCOUNT_PROVIDER = "*"
|
const val ANY_ACCOUNT_PROVIDER = "*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,4 +9,6 @@ package io.element.android.features.enterprise.api
|
|||||||
|
|
||||||
interface SessionEnterpriseService {
|
interface SessionEnterpriseService {
|
||||||
suspend fun isElementCallAvailable(): Boolean
|
suspend fun isElementCallAvailable(): Boolean
|
||||||
|
|
||||||
|
suspend fun init()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.SemanticColors
|
||||||
import io.element.android.compound.tokens.generated.compoundColorsDark
|
import io.element.android.compound.tokens.generated.compoundColorsDark
|
||||||
import io.element.android.compound.tokens.generated.compoundColorsLight
|
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.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
@@ -31,4 +33,6 @@ class DefaultEnterpriseService @Inject constructor() : EnterpriseService {
|
|||||||
|
|
||||||
override fun firebasePushGateway(): String? = null
|
override fun firebasePushGateway(): String? = null
|
||||||
override fun unifiedPushDefaultPushGateway(): String? = null
|
override fun unifiedPushDefaultPushGateway(): String? = null
|
||||||
|
|
||||||
|
override val bugReportUrlFlow = flowOf(BugReportUrl.UseDefault)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,5 +14,6 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@ContributesBinding(SessionScope::class)
|
@ContributesBinding(SessionScope::class)
|
||||||
class DefaultSessionEnterpriseService @Inject constructor() : SessionEnterpriseService {
|
class DefaultSessionEnterpriseService @Inject constructor() : SessionEnterpriseService {
|
||||||
|
override suspend fun init() = Unit
|
||||||
override suspend fun isElementCallAvailable(): Boolean = true
|
override suspend fun isElementCallAvailable(): Boolean = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,14 @@
|
|||||||
package io.element.android.features.enterprise.test
|
package io.element.android.features.enterprise.test
|
||||||
|
|
||||||
import io.element.android.compound.tokens.generated.SemanticColors
|
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.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.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.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
class FakeEnterpriseService(
|
class FakeEnterpriseService(
|
||||||
override val isEnterpriseBuild: Boolean = false,
|
override val isEnterpriseBuild: Boolean = false,
|
||||||
@@ -51,7 +55,6 @@ class FakeEnterpriseService(
|
|||||||
return unifiedPushDefaultPushGatewayResult()
|
return unifiedPushDefaultPushGatewayResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
val bugReportUrlMutableFlow = MutableStateFlow<BugReportUrl>(BugReportUrl.UseDefault)
|
||||||
const val A_FAKE_HOMESERVER = "a_fake_homeserver"
|
override val bugReportUrlFlow: Flow<BugReportUrl> = bugReportUrlMutableFlow.asStateFlow()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import io.element.android.tests.testutils.simulateLongTask
|
|||||||
class FakeSessionEnterpriseService(
|
class FakeSessionEnterpriseService(
|
||||||
private val isElementCallAvailableResult: () -> Boolean = { lambdaError() },
|
private val isElementCallAvailableResult: () -> Boolean = { lambdaError() },
|
||||||
) : SessionEnterpriseService {
|
) : SessionEnterpriseService {
|
||||||
|
override suspend fun init() {
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun isElementCallAvailable(): Boolean = simulateLongTask {
|
override suspend fun isElementCallAvailable(): Boolean = simulateLongTask {
|
||||||
isElementCallAvailableResult()
|
isElementCallAvailableResult()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class HomePresenter @Inject constructor(
|
|||||||
override fun present(): HomeState {
|
override fun present(): HomeState {
|
||||||
val matrixUser = client.userProfile.collectAsState()
|
val matrixUser = client.userProfile.collectAsState()
|
||||||
val isOnline by syncService.isOnline.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 roomListState = roomListPresenter.present()
|
||||||
val isSpaceFeatureEnabled by remember {
|
val isSpaceFeatureEnabled by remember {
|
||||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.Space)
|
featureFlagService.isFeatureEnabledFlow(FeatureFlags.Space)
|
||||||
|
|||||||
@@ -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.libraries.matrix.test.sync.FakeSyncService
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
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)))
|
matrixClient.givenGetProfileResult(matrixClient.sessionId, Result.success(MatrixUser(matrixClient.sessionId, A_USER_NAME, AN_AVATAR_URL)))
|
||||||
val presenter = createHomePresenter(
|
val presenter = createHomePresenter(
|
||||||
client = matrixClient,
|
client = matrixClient,
|
||||||
rageshakeFeatureAvailability = { false },
|
rageshakeFeatureAvailability = { flowOf(false) },
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
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
|
@Test
|
||||||
fun `present - space feature enabled`() = runTest {
|
fun `present - space feature enabled`() = runTest {
|
||||||
val presenter = createHomePresenter(
|
val presenter = createHomePresenter(
|
||||||
@@ -132,7 +148,7 @@ class HomePresenterTest {
|
|||||||
client: MatrixClient = FakeMatrixClient(),
|
client: MatrixClient = FakeMatrixClient(),
|
||||||
syncService: SyncService = FakeSyncService(),
|
syncService: SyncService = FakeSyncService(),
|
||||||
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
|
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
|
||||||
rageshakeFeatureAvailability: RageshakeFeatureAvailability = RageshakeFeatureAvailability { true },
|
rageshakeFeatureAvailability: RageshakeFeatureAvailability = RageshakeFeatureAvailability { flowOf(false) },
|
||||||
indicatorService: IndicatorService = FakeIndicatorService(),
|
indicatorService: IndicatorService = FakeIndicatorService(),
|
||||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService()
|
featureFlagService: FeatureFlagService = FakeFeatureFlagService()
|
||||||
) = HomePresenter(
|
) = HomePresenter(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
package io.element.android.features.login.impl.screens.onboarding
|
package io.element.android.features.login.impl.screens.onboarding
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
@@ -82,7 +83,7 @@ class OnBoardingPresenter @AssistedInject constructor(
|
|||||||
value = linkAccountProvider == null &&
|
value = linkAccountProvider == null &&
|
||||||
featureFlagService.isFeatureEnabled(FeatureFlags.QrCodeLogin)
|
featureFlagService.isFeatureEnabled(FeatureFlags.QrCodeLogin)
|
||||||
}
|
}
|
||||||
val canReportBug = remember { rageshakeFeatureAvailability.isAvailable() }
|
val canReportBug by remember { rageshakeFeatureAvailability.isAvailable() }.collectAsState(false)
|
||||||
var showReportBug by rememberSaveable { mutableStateOf(false) }
|
var showReportBug by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
val loginMode by loginHelper.collectLoginMode()
|
val loginMode by loginHelper.collectLoginMode()
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.google.common.truth.Truth.assertThat
|
|||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
import io.element.android.features.login.impl.changeserver.AccountProviderAccessException
|
import io.element.android.features.login.impl.changeserver.AccountProviderAccessException
|
||||||
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
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
|
||||||
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2
|
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2
|
||||||
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_URL
|
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_URL
|
||||||
@@ -25,7 +26,7 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = false,
|
isEnterpriseBuild = false,
|
||||||
isAllowedToConnectToHomeserver = true,
|
isAllowedToConnectToHomeserver = true,
|
||||||
elementWellKnown = ElementWellKnown(
|
elementWellKnown = anElementWellKnown(
|
||||||
enforceElementPro = true,
|
enforceElementPro = true,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -38,7 +39,7 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
isEnterpriseBuild = false,
|
isEnterpriseBuild = false,
|
||||||
// false here.
|
// false here.
|
||||||
isAllowedToConnectToHomeserver = false,
|
isAllowedToConnectToHomeserver = false,
|
||||||
elementWellKnown = ElementWellKnown(
|
elementWellKnown = anElementWellKnown(
|
||||||
enforceElementPro = true,
|
enforceElementPro = true,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -50,7 +51,7 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = false,
|
isEnterpriseBuild = false,
|
||||||
isAllowedToConnectToHomeserver = true,
|
isAllowedToConnectToHomeserver = true,
|
||||||
elementWellKnown = ElementWellKnown(
|
elementWellKnown = anElementWellKnown(
|
||||||
enforceElementPro = false,
|
enforceElementPro = false,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -62,7 +63,7 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = false,
|
isEnterpriseBuild = false,
|
||||||
isAllowedToConnectToHomeserver = true,
|
isAllowedToConnectToHomeserver = true,
|
||||||
elementWellKnown = ElementWellKnown(
|
elementWellKnown = anElementWellKnown(
|
||||||
enforceElementPro = null,
|
enforceElementPro = null,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -85,7 +86,7 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
isEnterpriseBuild = false,
|
isEnterpriseBuild = false,
|
||||||
isAllowedToConnectToHomeserver = false,
|
isAllowedToConnectToHomeserver = false,
|
||||||
allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2),
|
allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2),
|
||||||
elementWellKnown = ElementWellKnown(
|
elementWellKnown = anElementWellKnown(
|
||||||
enforceElementPro = false,
|
enforceElementPro = false,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -97,7 +98,7 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = true,
|
isEnterpriseBuild = true,
|
||||||
isAllowedToConnectToHomeserver = true,
|
isAllowedToConnectToHomeserver = true,
|
||||||
elementWellKnown = ElementWellKnown(
|
elementWellKnown = anElementWellKnown(
|
||||||
enforceElementPro = true,
|
enforceElementPro = true,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -109,7 +110,7 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
val accessControl = createDefaultAccountProviderAccessControl(
|
val accessControl = createDefaultAccountProviderAccessControl(
|
||||||
isEnterpriseBuild = true,
|
isEnterpriseBuild = true,
|
||||||
isAllowedToConnectToHomeserver = true,
|
isAllowedToConnectToHomeserver = true,
|
||||||
elementWellKnown = ElementWellKnown(
|
elementWellKnown = anElementWellKnown(
|
||||||
enforceElementPro = false,
|
enforceElementPro = false,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -122,7 +123,7 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
isEnterpriseBuild = true,
|
isEnterpriseBuild = true,
|
||||||
isAllowedToConnectToHomeserver = false,
|
isAllowedToConnectToHomeserver = false,
|
||||||
allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2),
|
allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2),
|
||||||
elementWellKnown = ElementWellKnown(
|
elementWellKnown = anElementWellKnown(
|
||||||
enforceElementPro = true,
|
enforceElementPro = true,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -135,7 +136,7 @@ class DefaultAccountProviderAccessControlTest {
|
|||||||
isEnterpriseBuild = true,
|
isEnterpriseBuild = true,
|
||||||
isAllowedToConnectToHomeserver = false,
|
isAllowedToConnectToHomeserver = false,
|
||||||
allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2),
|
allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2),
|
||||||
elementWellKnown = ElementWellKnown(
|
elementWellKnown = anElementWellKnown(
|
||||||
enforceElementPro = false,
|
enforceElementPro = false,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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.accountprovider.AccountProviderDataSource
|
||||||
import io.element.android.features.login.impl.error.ChangeServerError
|
import io.element.android.features.login.impl.error.ChangeServerError
|
||||||
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
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.architecture.AsyncData
|
||||||
import io.element.android.libraries.core.uri.ensureProtocol
|
import io.element.android.libraries.core.uri.ensureProtocol
|
||||||
import io.element.android.libraries.matrix.test.A_HOMESERVER
|
import io.element.android.libraries.matrix.test.A_HOMESERVER
|
||||||
@@ -114,7 +115,7 @@ class ChangeServerPresenterTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `present - change server element pro required error`() = runTest {
|
fun `present - change server element pro required error`() = runTest {
|
||||||
val getElementWellKnownResult = lambdaRecorder<String, ElementWellKnown> {
|
val getElementWellKnownResult = lambdaRecorder<String, ElementWellKnown> {
|
||||||
ElementWellKnown(
|
anElementWellKnown(
|
||||||
enforceElementPro = true,
|
enforceElementPro = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.libraries.wellknown.api.WellknownRetriever
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -78,7 +80,6 @@ class OnBoardingPresenterTest {
|
|||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, EnterpriseService.ANY_ACCOUNT_PROVIDER) },
|
defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, EnterpriseService.ANY_ACCOUNT_PROVIDER) },
|
||||||
),
|
),
|
||||||
rageshakeFeatureAvailability = { true },
|
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
@@ -94,7 +95,7 @@ class OnBoardingPresenterTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `present - clicking on version 7 times has no effect if rageshake not available`() = runTest {
|
fun `present - clicking on version 7 times has no effect if rageshake not available`() = runTest {
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
rageshakeFeatureAvailability = { false },
|
rageshakeFeatureAvailability = { flowOf(false) },
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
@@ -239,7 +240,7 @@ private fun createPresenter(
|
|||||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
||||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||||
wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(),
|
wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(),
|
||||||
rageshakeFeatureAvailability: () -> Boolean = { true },
|
rageshakeFeatureAvailability: () -> Flow<Boolean> = { flowOf(true) },
|
||||||
loginHelper: LoginHelper = createLoginHelper(),
|
loginHelper: LoginHelper = createLoginHelper(),
|
||||||
) = OnBoardingPresenter(
|
) = OnBoardingPresenter(
|
||||||
params = params,
|
params = params,
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class PreferencesRootPresenter @Inject constructor(
|
|||||||
var canDeactivateAccount by remember {
|
var canDeactivateAccount by remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
val canReportBug = remember { rageshakeFeatureAvailability.isAvailable() }
|
val canReportBug by remember { rageshakeFeatureAvailability.isAvailable() }.collectAsState(false)
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
canDeactivateAccount = matrixClient.canDeactivateAccount()
|
canDeactivateAccount = matrixClient.canDeactivateAccount()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.lambda.value
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -102,7 +103,7 @@ class PreferencesRootPresenterTest {
|
|||||||
)
|
)
|
||||||
createPresenter(
|
createPresenter(
|
||||||
matrixClient = matrixClient,
|
matrixClient = matrixClient,
|
||||||
rageshakeFeatureAvailability = { false },
|
rageshakeFeatureAvailability = { flowOf(false) },
|
||||||
).test {
|
).test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.canReportBug).isFalse()
|
assertThat(initialState.canReportBug).isFalse()
|
||||||
@@ -119,7 +120,7 @@ class PreferencesRootPresenterTest {
|
|||||||
val indicatorService = FakeIndicatorService()
|
val indicatorService = FakeIndicatorService()
|
||||||
createPresenter(
|
createPresenter(
|
||||||
matrixClient = matrixClient,
|
matrixClient = matrixClient,
|
||||||
rageshakeFeatureAvailability = { false },
|
rageshakeFeatureAvailability = { flowOf(false) },
|
||||||
indicatorService = indicatorService,
|
indicatorService = indicatorService,
|
||||||
).test {
|
).test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
@@ -185,7 +186,7 @@ class PreferencesRootPresenterTest {
|
|||||||
matrixClient: FakeMatrixClient = FakeMatrixClient(),
|
matrixClient: FakeMatrixClient = FakeMatrixClient(),
|
||||||
sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
|
sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
|
||||||
showDeveloperSettingsProvider: ShowDeveloperSettingsProvider = ShowDeveloperSettingsProvider(aBuildMeta(BuildType.DEBUG)),
|
showDeveloperSettingsProvider: ShowDeveloperSettingsProvider = ShowDeveloperSettingsProvider(aBuildMeta(BuildType.DEBUG)),
|
||||||
rageshakeFeatureAvailability: RageshakeFeatureAvailability = RageshakeFeatureAvailability { true },
|
rageshakeFeatureAvailability: RageshakeFeatureAvailability = RageshakeFeatureAvailability { flowOf(true) },
|
||||||
indicatorService: IndicatorService = FakeIndicatorService(),
|
indicatorService: IndicatorService = FakeIndicatorService(),
|
||||||
) = PreferencesRootPresenter(
|
) = PreferencesRootPresenter(
|
||||||
matrixClient = matrixClient,
|
matrixClient = matrixClient,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
package io.element.android.features.rageshake.api
|
package io.element.android.features.rageshake.api
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
fun interface RageshakeFeatureAvailability {
|
fun interface RageshakeFeatureAvailability {
|
||||||
fun isAvailable(): Boolean
|
fun isAvailable(): Flow<Boolean>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ setupAnvil()
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.appconfig)
|
implementation(projects.appconfig)
|
||||||
|
implementation(projects.features.enterprise.api)
|
||||||
implementation(projects.services.toolbox.api)
|
implementation(projects.services.toolbox.api)
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
@@ -50,6 +51,7 @@ dependencies {
|
|||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
testImplementation(libs.test.mockk)
|
testImplementation(libs.test.mockk)
|
||||||
|
testImplementation(projects.features.enterprise.test)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.sessionStorage.implMemory)
|
testImplementation(projects.libraries.sessionStorage.implMemory)
|
||||||
testImplementation(projects.libraries.sessionStorage.test)
|
testImplementation(projects.libraries.sessionStorage.test)
|
||||||
|
|||||||
@@ -8,15 +8,19 @@
|
|||||||
package io.element.android.features.rageshake.impl
|
package io.element.android.features.rageshake.impl
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
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.api.RageshakeFeatureAvailability
|
||||||
|
import io.element.android.features.rageshake.impl.reporter.BugReporterUrlProvider
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultRageshakeFeatureAvailability @Inject constructor() : RageshakeFeatureAvailability {
|
class DefaultRageshakeFeatureAvailability @Inject constructor(
|
||||||
override fun isAvailable(): Boolean {
|
private val bugReporterUrlProvider: BugReporterUrlProvider,
|
||||||
return RageshakeConfig.isEnabled
|
) : RageshakeFeatureAvailability {
|
||||||
|
override fun isAvailable(): Flow<Boolean> {
|
||||||
|
return bugReporterUrlProvider.provide()
|
||||||
|
.map { it != null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,13 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
|
||||||
package io.element.android.features.rageshake.impl.crash
|
package io.element.android.features.rageshake.impl.crash
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
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.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -32,12 +37,15 @@ class DefaultCrashDetectionPresenter @Inject constructor(
|
|||||||
@Composable
|
@Composable
|
||||||
override fun present(): CrashDetectionState {
|
override fun present(): CrashDetectionState {
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
val crashDetected = remember {
|
val crashDetected by remember {
|
||||||
if (rageshakeFeatureAvailability.isAvailable()) {
|
rageshakeFeatureAvailability.isAvailable()
|
||||||
crashDataStore.appHasCrashed()
|
.flatMapLatest { isAvailable ->
|
||||||
} else {
|
if (isAvailable) {
|
||||||
flowOf(false)
|
crashDataStore.appHasCrashed()
|
||||||
}
|
} else {
|
||||||
|
flowOf(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}.collectAsState(false)
|
}.collectAsState(false)
|
||||||
|
|
||||||
fun handleEvents(event: CrashDetectionEvents) {
|
fun handleEvents(event: CrashDetectionEvents) {
|
||||||
@@ -49,7 +57,7 @@ class DefaultCrashDetectionPresenter @Inject constructor(
|
|||||||
|
|
||||||
return CrashDetectionState(
|
return CrashDetectionState(
|
||||||
appName = buildMeta.applicationName,
|
appName = buildMeta.applicationName,
|
||||||
crashDetected = crashDetected.value,
|
crashDetected = crashDetected,
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class DefaultRageshakePreferencesPresenter @Inject constructor(
|
|||||||
val isSupported: MutableState<Boolean> = rememberSaveable {
|
val isSupported: MutableState<Boolean> = rememberSaveable {
|
||||||
mutableStateOf(rageshake.isAvailable())
|
mutableStateOf(rageshake.isAvailable())
|
||||||
}
|
}
|
||||||
val isFeatureAvailable = remember { rageshakeFeatureAvailability.isAvailable() }
|
val isFeatureAvailable by remember { rageshakeFeatureAvailability.isAvailable() }.collectAsState(false)
|
||||||
val isEnabled by remember {
|
val isEnabled by remember {
|
||||||
rageshakeDataStore.isEnabled()
|
rageshakeDataStore.isEnabled()
|
||||||
}.collectAsState(initial = false)
|
}.collectAsState(initial = false)
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -7,8 +7,9 @@
|
|||||||
|
|
||||||
package io.element.android.features.rageshake.impl.reporter
|
package io.element.android.features.rageshake.impl.reporter
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
|
|
||||||
fun interface BugReporterUrlProvider {
|
fun interface BugReporterUrlProvider {
|
||||||
fun provide(): HttpUrl
|
fun provide(): Flow<HttpUrl?>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,12 @@ class DefaultBugReporter @Inject constructor(
|
|||||||
canContact: Boolean,
|
canContact: Boolean,
|
||||||
listener: BugReporterListener,
|
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
|
// enumerate files to delete
|
||||||
val bugReportFiles: MutableList<File> = ArrayList()
|
val bugReportFiles: MutableList<File> = ArrayList()
|
||||||
var response: Response? = null
|
var response: Response? = null
|
||||||
@@ -243,7 +249,7 @@ class DefaultBugReporter @Inject constructor(
|
|||||||
}
|
}
|
||||||
// build the request
|
// build the request
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(bugReporterUrlProvider.provide())
|
.url(url)
|
||||||
.post(requestBody)
|
.post(requestBody)
|
||||||
.build()
|
.build()
|
||||||
var errorMessage: String? = null
|
var errorMessage: String? = null
|
||||||
|
|||||||
@@ -9,14 +9,31 @@ package io.element.android.features.rageshake.impl.reporter
|
|||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.appconfig.RageshakeConfig
|
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 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
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultBugReporterUrlProvider @Inject constructor() : BugReporterUrlProvider {
|
class DefaultBugReporterUrlProvider @Inject constructor(
|
||||||
override fun provide(): HttpUrl {
|
private val bugReportAppNameProvider: BugReportAppNameProvider,
|
||||||
return RageshakeConfig.BUG_REPORT_URL.toHttpUrl()
|
private val enterpriseService: EnterpriseService,
|
||||||
|
) : BugReporterUrlProvider {
|
||||||
|
override fun provide(): Flow<HttpUrl?> {
|
||||||
|
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() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -104,6 +105,6 @@ class CrashDetectionPresenterTest {
|
|||||||
) = DefaultCrashDetectionPresenter(
|
) = DefaultCrashDetectionPresenter(
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
crashDataStore = crashDataStore,
|
crashDataStore = crashDataStore,
|
||||||
rageshakeFeatureAvailability = { isFeatureAvailable },
|
rageshakeFeatureAvailability = { flowOf(isFeatureAvailable) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
|||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.BeforeClass
|
import org.junit.BeforeClass
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
@@ -52,7 +53,7 @@ class RageshakeDetectionPresenterTest {
|
|||||||
preferencesPresenter = DefaultRageshakePreferencesPresenter(
|
preferencesPresenter = DefaultRageshakePreferencesPresenter(
|
||||||
rageshake = rageshake,
|
rageshake = rageshake,
|
||||||
rageshakeDataStore = rageshakeDataStore,
|
rageshakeDataStore = rageshakeDataStore,
|
||||||
rageshakeFeatureAvailability = { true },
|
rageshakeFeatureAvailability = { flowOf(true) },
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
@@ -77,7 +78,7 @@ class RageshakeDetectionPresenterTest {
|
|||||||
preferencesPresenter = DefaultRageshakePreferencesPresenter(
|
preferencesPresenter = DefaultRageshakePreferencesPresenter(
|
||||||
rageshake = rageshake,
|
rageshake = rageshake,
|
||||||
rageshakeDataStore = rageshakeDataStore,
|
rageshakeDataStore = rageshakeDataStore,
|
||||||
rageshakeFeatureAvailability = { true },
|
rageshakeFeatureAvailability = { flowOf(true) },
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
@@ -103,7 +104,7 @@ class RageshakeDetectionPresenterTest {
|
|||||||
preferencesPresenter = DefaultRageshakePreferencesPresenter(
|
preferencesPresenter = DefaultRageshakePreferencesPresenter(
|
||||||
rageshake = rageshake,
|
rageshake = rageshake,
|
||||||
rageshakeDataStore = rageshakeDataStore,
|
rageshakeDataStore = rageshakeDataStore,
|
||||||
rageshakeFeatureAvailability = { true },
|
rageshakeFeatureAvailability = { flowOf(true) },
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
@@ -138,7 +139,7 @@ class RageshakeDetectionPresenterTest {
|
|||||||
preferencesPresenter = DefaultRageshakePreferencesPresenter(
|
preferencesPresenter = DefaultRageshakePreferencesPresenter(
|
||||||
rageshake = rageshake,
|
rageshake = rageshake,
|
||||||
rageshakeDataStore = rageshakeDataStore,
|
rageshakeDataStore = rageshakeDataStore,
|
||||||
rageshakeFeatureAvailability = { true },
|
rageshakeFeatureAvailability = { flowOf(true) },
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
@@ -173,7 +174,7 @@ class RageshakeDetectionPresenterTest {
|
|||||||
preferencesPresenter = DefaultRageshakePreferencesPresenter(
|
preferencesPresenter = DefaultRageshakePreferencesPresenter(
|
||||||
rageshake = rageshake,
|
rageshake = rageshake,
|
||||||
rageshakeDataStore = rageshakeDataStore,
|
rageshakeDataStore = rageshakeDataStore,
|
||||||
rageshakeFeatureAvailability = { true },
|
rageshakeFeatureAvailability = { flowOf(true) },
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|||||||
@@ -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.FakeRageShake
|
||||||
import io.element.android.features.rageshake.impl.rageshake.FakeRageshakeDataStore
|
import io.element.android.features.rageshake.impl.rageshake.FakeRageshakeDataStore
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -29,7 +30,7 @@ class RageshakePreferencesPresenterTest {
|
|||||||
val presenter = DefaultRageshakePreferencesPresenter(
|
val presenter = DefaultRageshakePreferencesPresenter(
|
||||||
FakeRageShake(isAvailableValue = true),
|
FakeRageShake(isAvailableValue = true),
|
||||||
FakeRageshakeDataStore(isEnabled = true),
|
FakeRageshakeDataStore(isEnabled = true),
|
||||||
rageshakeFeatureAvailability = { true },
|
rageshakeFeatureAvailability = { flowOf(true) },
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
@@ -46,7 +47,7 @@ class RageshakePreferencesPresenterTest {
|
|||||||
val presenter = DefaultRageshakePreferencesPresenter(
|
val presenter = DefaultRageshakePreferencesPresenter(
|
||||||
FakeRageShake(isAvailableValue = false),
|
FakeRageShake(isAvailableValue = false),
|
||||||
FakeRageshakeDataStore(isEnabled = true),
|
FakeRageshakeDataStore(isEnabled = true),
|
||||||
rageshakeFeatureAvailability = { true },
|
rageshakeFeatureAvailability = { flowOf(true) },
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
@@ -63,7 +64,7 @@ class RageshakePreferencesPresenterTest {
|
|||||||
val presenter = DefaultRageshakePreferencesPresenter(
|
val presenter = DefaultRageshakePreferencesPresenter(
|
||||||
FakeRageShake(isAvailableValue = true),
|
FakeRageShake(isAvailableValue = true),
|
||||||
FakeRageshakeDataStore(isEnabled = true),
|
FakeRageshakeDataStore(isEnabled = true),
|
||||||
rageshakeFeatureAvailability = { true },
|
rageshakeFeatureAvailability = { flowOf(true) },
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
@@ -83,7 +84,7 @@ class RageshakePreferencesPresenterTest {
|
|||||||
val presenter = DefaultRageshakePreferencesPresenter(
|
val presenter = DefaultRageshakePreferencesPresenter(
|
||||||
FakeRageShake(isAvailableValue = true),
|
FakeRageShake(isAvailableValue = true),
|
||||||
FakeRageshakeDataStore(isEnabled = true),
|
FakeRageshakeDataStore(isEnabled = true),
|
||||||
rageshakeFeatureAvailability = { true },
|
rageshakeFeatureAvailability = { flowOf(true) },
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|||||||
@@ -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.libraries.sessionstorage.test.aSessionData
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import okhttp3.MultipartReader
|
import okhttp3.MultipartReader
|
||||||
@@ -464,7 +465,7 @@ class DefaultBugReporterTest {
|
|||||||
userAgentProvider = DefaultUserAgentProvider(buildMeta, FakeSdkMetadata("123456789")),
|
userAgentProvider = DefaultUserAgentProvider(buildMeta, FakeSdkMetadata("123456789")),
|
||||||
sessionStore = sessionStore,
|
sessionStore = sessionStore,
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
bugReporterUrlProvider = { server.url("/") },
|
bugReporterUrlProvider = { flowOf(server.url("/")) },
|
||||||
sdkMetadata = FakeSdkMetadata("123456789"),
|
sdkMetadata = FakeSdkMetadata("123456789"),
|
||||||
matrixClientProvider = matrixClientProvider,
|
matrixClientProvider = matrixClientProvider,
|
||||||
tracingService = tracingService,
|
tracingService = tracingService,
|
||||||
|
|||||||
@@ -7,18 +7,44 @@
|
|||||||
|
|
||||||
package io.element.android.features.rageshake.impl.reporter
|
package io.element.android.features.rageshake.impl.reporter
|
||||||
|
|
||||||
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.appconfig.RageshakeConfig
|
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 okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class DefaultBugReporterUrlProviderTest {
|
class DefaultBugReporterUrlProviderTest {
|
||||||
@Test
|
@Test
|
||||||
fun `test DefaultBugReporterUrlProvider`() {
|
fun `provide return values when there is an rageshake app name`() = runTest {
|
||||||
val sut = DefaultBugReporterUrlProvider()
|
val enterpriseService = FakeEnterpriseService()
|
||||||
if (RageshakeConfig.BUG_REPORT_URL.isNotEmpty()) {
|
val sut = DefaultBugReporterUrlProvider(
|
||||||
val result = sut.provide()
|
bugReportAppNameProvider = { "rageshakeAppName" },
|
||||||
assertThat(result).isEqualTo(RageshakeConfig.BUG_REPORT_URL.toHttpUrl())
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
package io.element.android.libraries.wellknown.api
|
package io.element.android.libraries.wellknown.api
|
||||||
|
|
||||||
data class ElementWellKnown(
|
data class ElementWellKnown(
|
||||||
val registrationHelperUrl: String? = null,
|
val registrationHelperUrl: String?,
|
||||||
val enforceElementPro: Boolean? = null,
|
val enforceElementPro: Boolean?,
|
||||||
|
val rageshakeUrl: String?,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
package io.element.android.libraries.wellknown.api
|
package io.element.android.libraries.wellknown.api
|
||||||
|
|
||||||
data class WellKnown(
|
data class WellKnown(
|
||||||
val homeServer: WellKnownBaseConfig? = null,
|
val homeServer: WellKnownBaseConfig?,
|
||||||
val identityServer: WellKnownBaseConfig? = null,
|
val identityServer: WellKnownBaseConfig?,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class WellKnownBaseConfig(
|
data class WellKnownBaseConfig(
|
||||||
val baseURL: String? = null
|
val baseURL: String?
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import io.element.android.libraries.di.AppScope
|
|||||||
import io.element.android.libraries.network.RetrofitFactory
|
import io.element.android.libraries.network.RetrofitFactory
|
||||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||||
import io.element.android.libraries.wellknown.api.WellKnown
|
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 io.element.android.libraries.wellknown.api.WellknownRetriever
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|||||||
@@ -25,4 +25,6 @@ data class InternalElementWellKnown(
|
|||||||
val registrationHelperUrl: String? = null,
|
val registrationHelperUrl: String? = null,
|
||||||
@SerialName("enforce_element_pro")
|
@SerialName("enforce_element_pro")
|
||||||
val enforceElementPro: Boolean? = null,
|
val enforceElementPro: Boolean? = null,
|
||||||
|
@SerialName("rageshake_url")
|
||||||
|
val rageshakeUrl: String? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import io.element.android.libraries.wellknown.api.WellKnownBaseConfig
|
|||||||
internal fun InternalElementWellKnown.map() = ElementWellKnown(
|
internal fun InternalElementWellKnown.map() = ElementWellKnown(
|
||||||
registrationHelperUrl = registrationHelperUrl,
|
registrationHelperUrl = registrationHelperUrl,
|
||||||
enforceElementPro = enforceElementPro,
|
enforceElementPro = enforceElementPro,
|
||||||
|
rageshakeUrl = rageshakeUrl,
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun InternalWellKnown.map() = WellKnown(
|
internal fun InternalWellKnown.map() = WellKnown(
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user