Use embedded version of Element Call (#4470)
* Use embedded version of Element Call: for in-app room calls, the app will use an embedded version of Element Call shipped with the app instead of using an external service. * Remove `ElementCallBaseUrlProvider` so we don't use the Element well known file to get the base URL anymore * Remove `ElementCallConfig.DEFAULT_BASE_URL` since it's not used anymore * Restore the usage of the custom EC base URL in developer settings as the actual base URL, it present * Add a way to customise the embedded EC analytic credentials * Update CI to use the EC analytic credentials as secrets * Improve the custom URL placeholder to include the `/room` suffix
This commit is contained in:
committed by
GitHub
parent
bac26635af
commit
e2febabcf6
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -46,6 +46,10 @@ jobs:
|
||||
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
|
||||
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
|
||||
ELEMENT_ANDROID_SENTRY_DSN: ${{ secrets.ELEMENT_ANDROID_SENTRY_DSN }}
|
||||
ELEMENT_CALL_SENTRY_DSN: ${{ secrets.ELEMENT_CALL_SENTRY_DSN }}
|
||||
ELEMENT_CALL_POSTHOG_API_HOST: ${{ secrets.ELEMENT_CALL_POSTHOG_API_HOST }}
|
||||
ELEMENT_CALL_POSTHOG_API_KEY: ${{ secrets.ELEMENT_CALL_POSTHOG_API_KEY }}
|
||||
ELEMENT_CALL_RAGESHAKE_URL: ${{ secrets.ELEMENT_CALL_RAGESHAKE_URL }}
|
||||
run: ./gradlew :app:assembleGplayDebug app:assembleFDroidDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Upload debug APKs
|
||||
if: ${{ matrix.variant == 'debug' }}
|
||||
|
||||
4
.github/workflows/build_enterprise.yml
vendored
4
.github/workflows/build_enterprise.yml
vendored
@@ -54,6 +54,10 @@ jobs:
|
||||
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
|
||||
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
|
||||
ELEMENT_ANDROID_SENTRY_DSN: ${{ secrets.ELEMENT_ANDROID_SENTRY_DSN }}
|
||||
ELEMENT_CALL_SENTRY_DSN: ${{ secrets.ELEMENT_CALL_SENTRY_DSN }}
|
||||
ELEMENT_CALL_POSTHOG_API_HOST: ${{ secrets.ELEMENT_CALL_POSTHOG_API_HOST }}
|
||||
ELEMENT_CALL_POSTHOG_API_KEY: ${{ secrets.ELEMENT_CALL_POSTHOG_API_KEY }}
|
||||
ELEMENT_CALL_RAGESHAKE_URL: ${{ secrets.ELEMENT_CALL_RAGESHAKE_URL }}
|
||||
run: ./gradlew :app:assembleGplayDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Upload debug Enterprise APKs
|
||||
if: ${{ matrix.variant == 'debug' }}
|
||||
|
||||
4
.github/workflows/nightly.yml
vendored
4
.github/workflows/nightly.yml
vendored
@@ -30,6 +30,10 @@ jobs:
|
||||
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
|
||||
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
|
||||
ELEMENT_ANDROID_SENTRY_DSN: ${{ secrets.ELEMENT_ANDROID_SENTRY_DSN }}
|
||||
ELEMENT_CALL_SENTRY_DSN: ${{ secrets.ELEMENT_CALL_SENTRY_DSN }}
|
||||
ELEMENT_CALL_POSTHOG_API_HOST: ${{ secrets.ELEMENT_CALL_POSTHOG_API_HOST }}
|
||||
ELEMENT_CALL_POSTHOG_API_KEY: ${{ secrets.ELEMENT_CALL_POSTHOG_API_KEY }}
|
||||
ELEMENT_CALL_RAGESHAKE_URL: ${{ secrets.ELEMENT_CALL_RAGESHAKE_URL }}
|
||||
ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }}
|
||||
ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }}
|
||||
ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD }}
|
||||
|
||||
4
.github/workflows/nightly_enterprise.yml
vendored
4
.github/workflows/nightly_enterprise.yml
vendored
@@ -36,6 +36,10 @@ jobs:
|
||||
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
|
||||
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
|
||||
ELEMENT_ANDROID_SENTRY_DSN: ${{ secrets.ELEMENT_ANDROID_SENTRY_DSN }}
|
||||
ELEMENT_CALL_SENTRY_DSN: ${{ secrets.ELEMENT_CALL_SENTRY_DSN }}
|
||||
ELEMENT_CALL_POSTHOG_API_HOST: ${{ secrets.ELEMENT_CALL_POSTHOG_API_HOST }}
|
||||
ELEMENT_CALL_POSTHOG_API_KEY: ${{ secrets.ELEMENT_CALL_POSTHOG_API_KEY }}
|
||||
ELEMENT_CALL_RAGESHAKE_URL: ${{ secrets.ELEMENT_CALL_RAGESHAKE_URL }}
|
||||
ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }}
|
||||
ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }}
|
||||
ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD }}
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -32,6 +32,10 @@ jobs:
|
||||
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
|
||||
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
|
||||
ELEMENT_ANDROID_SENTRY_DSN: ${{ secrets.ELEMENT_ANDROID_SENTRY_DSN }}
|
||||
ELEMENT_CALL_SENTRY_DSN: ${{ secrets.ELEMENT_CALL_SENTRY_DSN }}
|
||||
ELEMENT_CALL_POSTHOG_API_HOST: ${{ secrets.ELEMENT_CALL_POSTHOG_API_HOST }}
|
||||
ELEMENT_CALL_POSTHOG_API_KEY: ${{ secrets.ELEMENT_CALL_POSTHOG_API_KEY }}
|
||||
ELEMENT_CALL_RAGESHAKE_URL: ${{ secrets.ELEMENT_CALL_RAGESHAKE_URL }}
|
||||
run: ./gradlew bundleGplayRelease $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Upload bundle as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -13,7 +13,7 @@ import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||
|
||||
class ContributesNodeProcessorProvider : SymbolProcessorProvider {
|
||||
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
|
||||
val enableLogging = environment.options["enableLogging"]?.toBoolean() ?: false
|
||||
val enableLogging = environment.options["enableLogging"]?.toBoolean() == true
|
||||
return ContributesNodeProcessor(
|
||||
logger = environment.logger,
|
||||
codeGenerator = environment.codeGenerator,
|
||||
|
||||
@@ -8,11 +8,6 @@
|
||||
package io.element.android.appconfig
|
||||
|
||||
object ElementCallConfig {
|
||||
/**
|
||||
* The default base URL for the Element Call service.
|
||||
*/
|
||||
const val DEFAULT_BASE_URL = "https://call.element.io"
|
||||
|
||||
/**
|
||||
* The default duration of a ringing call in seconds before it's automatically dismissed.
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import extension.readLocalProperty
|
||||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
@@ -23,6 +24,49 @@ android {
|
||||
testOptions {
|
||||
unitTests.isIncludeAndroidResources = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
buildConfigField(
|
||||
type = "String",
|
||||
name = "SENTRY_DSN",
|
||||
value = (System.getenv("ELEMENT_CALL_SENTRY_DSN")
|
||||
?: readLocalProperty("features.call.sentry.dsn")
|
||||
?: ""
|
||||
).let { "\"$it\"" }
|
||||
)
|
||||
buildConfigField(
|
||||
type = "String",
|
||||
name = "POSTHOG_USER_ID",
|
||||
value = (System.getenv("ELEMENT_CALL_POSTHOG_USER_ID")
|
||||
?: readLocalProperty("features.call.posthog.userid")
|
||||
?: ""
|
||||
).let { "\"$it\"" }
|
||||
)
|
||||
buildConfigField(
|
||||
type = "String",
|
||||
name = "POSTHOG_API_HOST",
|
||||
value = (System.getenv("ELEMENT_CALL_POSTHOG_API_HOST")
|
||||
?: readLocalProperty("features.call.posthog.api.host")
|
||||
?: ""
|
||||
).let { "\"$it\"" }
|
||||
)
|
||||
buildConfigField(
|
||||
type = "String",
|
||||
name = "POSTHOG_API_KEY",
|
||||
value = (System.getenv("ELEMENT_CALL_POSTHOG_API_KEY")
|
||||
?: readLocalProperty("features.call.posthog.api.key")
|
||||
?: ""
|
||||
).let { "\"$it\"" }
|
||||
)
|
||||
buildConfigField(
|
||||
type = "String",
|
||||
name = "RAGESHAKE_URL",
|
||||
value = (System.getenv("ELEMENT_CALL_RAGESHAKE_URL")
|
||||
?: readLocalProperty("features.call.regeshake.url")
|
||||
?: ""
|
||||
).let { "\"$it\"" }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setupAnvil()
|
||||
@@ -47,6 +91,7 @@ dependencies {
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.network.retrofit)
|
||||
implementation(libs.serialization.json)
|
||||
implementation(libs.element.call.embedded)
|
||||
api(projects.features.call.api)
|
||||
|
||||
testImplementation(libs.coroutines.test)
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.call.impl.utils
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.call.impl.BuildConfig
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.widget.CallAnalyticCredentialsProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultCallAnalyticCredentialsProvider @Inject constructor() : CallAnalyticCredentialsProvider {
|
||||
override val posthogUserId: String? = BuildConfig.POSTHOG_USER_ID.takeIf { it.isNotBlank() }
|
||||
override val posthogApiHost: String? = BuildConfig.POSTHOG_API_HOST.takeIf { it.isNotBlank() }
|
||||
override val posthogApiKey: String? = BuildConfig.POSTHOG_API_KEY.takeIf { it.isNotBlank() }
|
||||
override val rageshakeSubmitUrl: String? = BuildConfig.RAGESHAKE_URL.takeIf { it.isNotBlank() }
|
||||
override val sentryDsn: String? = BuildConfig.SENTRY_DSN.takeIf { it.isNotBlank() }
|
||||
}
|
||||
@@ -8,10 +8,8 @@
|
||||
package io.element.android.features.call.impl.utils
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.appconfig.ElementCallConfig
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.call.ElementCallBaseUrlProvider
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
|
||||
@@ -19,12 +17,13 @@ import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val EMBEDDED_CALL_WIDGET_BASE_URL = "https://appassets.androidplatform.net/element-call/index.html"
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultCallWidgetProvider @Inject constructor(
|
||||
private val matrixClientsProvider: MatrixClientProvider,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val callWidgetSettingsProvider: CallWidgetSettingsProvider,
|
||||
private val elementCallBaseUrlProvider: ElementCallBaseUrlProvider,
|
||||
) : CallWidgetProvider {
|
||||
override suspend fun getWidget(
|
||||
sessionId: SessionId,
|
||||
@@ -35,9 +34,10 @@ class DefaultCallWidgetProvider @Inject constructor(
|
||||
): Result<CallWidgetProvider.GetWidgetResult> = runCatching {
|
||||
val matrixClient = matrixClientsProvider.getOrRestore(sessionId).getOrThrow()
|
||||
val room = matrixClient.getRoom(roomId) ?: error("Room not found")
|
||||
val baseUrl = appPreferencesStore.getCustomElementCallBaseUrlFlow().firstOrNull()
|
||||
?: elementCallBaseUrlProvider.provides(matrixClient)
|
||||
?: ElementCallConfig.DEFAULT_BASE_URL
|
||||
|
||||
val customBaseUrl = appPreferencesStore.getCustomElementCallBaseUrlFlow().firstOrNull()
|
||||
val baseUrl = customBaseUrl ?: EMBEDDED_CALL_WIDGET_BASE_URL
|
||||
|
||||
val isEncrypted = room.info().isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow()
|
||||
val widgetSettings = callWidgetSettingsProvider.provide(baseUrl, encrypted = isEncrypted)
|
||||
val callUrl = room.generateWidgetWebViewUrl(
|
||||
@@ -46,9 +46,10 @@ class DefaultCallWidgetProvider @Inject constructor(
|
||||
languageTag = languageTag,
|
||||
theme = theme,
|
||||
).getOrThrow()
|
||||
|
||||
CallWidgetProvider.GetWidgetResult(
|
||||
driver = room.getWidgetDriver(widgetSettings).getOrThrow(),
|
||||
url = callUrl
|
||||
url = callUrl,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.core.net.toUri
|
||||
import androidx.webkit.WebViewAssetLoader
|
||||
import androidx.webkit.WebViewCompat
|
||||
import androidx.webkit.WebViewFeature
|
||||
import io.element.android.features.call.impl.BuildConfig
|
||||
@@ -37,6 +39,10 @@ class WebViewWidgetMessageInterceptor(
|
||||
override val interceptedMessages = MutableSharedFlow<String>(extraBufferCapacity = 10)
|
||||
|
||||
init {
|
||||
val assetLoader = WebViewAssetLoader.Builder()
|
||||
.addPathHandler("/", WebViewAssetLoader.AssetsPathHandler(webView.context))
|
||||
.build()
|
||||
|
||||
webView.webViewClient = object : WebViewClient() {
|
||||
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
||||
super.onPageStarted(view, url, favicon)
|
||||
@@ -117,6 +123,14 @@ class WebViewWidgetMessageInterceptor(
|
||||
|
||||
super.onReceivedSslError(view, handler, error)
|
||||
}
|
||||
|
||||
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest): WebResourceResponse? {
|
||||
return assetLoader.shouldInterceptRequest(request.url)
|
||||
}
|
||||
|
||||
override fun shouldInterceptRequest(view: WebView?, url: String): WebResourceResponse? {
|
||||
return assetLoader.shouldInterceptRequest(url.toUri())
|
||||
}
|
||||
}
|
||||
|
||||
// Create a WebMessageListener, which will receive messages from the WebView and reply to them
|
||||
|
||||
@@ -9,9 +9,7 @@ package io.element.android.features.call.utils
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.call.impl.utils.DefaultCallWidgetProvider
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.call.ElementCallBaseUrlProvider
|
||||
import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
@@ -22,8 +20,6 @@ import io.element.android.libraries.matrix.test.widget.FakeCallWidgetSettingsPro
|
||||
import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
@@ -104,42 +100,13 @@ class DefaultCallWidgetProviderTest {
|
||||
assertThat(settingsProvider.providedBaseUrls).containsExactly("https://custom.element.io")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getWidget - will use a wellknown base url if it exists`() = runTest {
|
||||
val aCustomUrl = "https://custom.element.io"
|
||||
val providesLambda = lambdaRecorder<MatrixClient, String?> { _ -> aCustomUrl }
|
||||
val elementCallBaseUrlProvider = FakeElementCallBaseUrlProvider { matrixClient ->
|
||||
providesLambda(matrixClient)
|
||||
}
|
||||
val room = FakeMatrixRoom(
|
||||
generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") },
|
||||
getWidgetDriverResult = { Result.success(FakeMatrixWidgetDriver()) },
|
||||
)
|
||||
val client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val settingsProvider = FakeCallWidgetSettingsProvider()
|
||||
val provider = createProvider(
|
||||
matrixClientProvider = FakeMatrixClientProvider { Result.success(client) },
|
||||
callWidgetSettingsProvider = settingsProvider,
|
||||
elementCallBaseUrlProvider = elementCallBaseUrlProvider,
|
||||
)
|
||||
provider.getWidget(A_SESSION_ID, A_ROOM_ID, "clientId", "languageTag", "theme")
|
||||
assertThat(settingsProvider.providedBaseUrls).containsExactly(aCustomUrl)
|
||||
providesLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(client))
|
||||
}
|
||||
|
||||
private fun createProvider(
|
||||
matrixClientProvider: MatrixClientProvider = FakeMatrixClientProvider(),
|
||||
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||
callWidgetSettingsProvider: CallWidgetSettingsProvider = FakeCallWidgetSettingsProvider(),
|
||||
elementCallBaseUrlProvider: ElementCallBaseUrlProvider = FakeElementCallBaseUrlProvider { _ -> null },
|
||||
) = DefaultCallWidgetProvider(
|
||||
matrixClientsProvider = matrixClientProvider,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
callWidgetSettingsProvider = callWidgetSettingsProvider,
|
||||
elementCallBaseUrlProvider = elementCallBaseUrlProvider,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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.call.utils
|
||||
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.call.ElementCallBaseUrlProvider
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeElementCallBaseUrlProvider(
|
||||
private val providesLambda: (MatrixClient) -> String? = { lambdaError() }
|
||||
) : ElementCallBaseUrlProvider {
|
||||
override suspend fun provides(matrixClient: MatrixClient): String? {
|
||||
return providesLambda(matrixClient)
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import io.element.android.appconfig.ElementCallConfig
|
||||
import io.element.android.features.preferences.impl.developer.tracing.toLogLevel
|
||||
import io.element.android.features.preferences.impl.developer.tracing.toLogLevelItem
|
||||
import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
|
||||
@@ -112,8 +111,7 @@ class DeveloperSettingsPresenter @Inject constructor(
|
||||
triggerClearCache = { handleEvents(DeveloperSettingsEvents.ClearCache) }
|
||||
)
|
||||
is DeveloperSettingsEvents.SetCustomElementCallBaseUrl -> coroutineScope.launch {
|
||||
// If the URL is either empty or the default one, we want to save 'null' to remove the custom URL
|
||||
val urlToSave = event.baseUrl.takeIf { !it.isNullOrEmpty() && it != ElementCallConfig.DEFAULT_BASE_URL }
|
||||
val urlToSave = event.baseUrl.takeIf { !it.isNullOrEmpty() }
|
||||
appPreferencesStore.setCustomElementCallBaseUrl(urlToSave)
|
||||
}
|
||||
DeveloperSettingsEvents.ClearCache -> coroutineScope.clearCache(clearCacheAction)
|
||||
@@ -133,7 +131,6 @@ class DeveloperSettingsPresenter @Inject constructor(
|
||||
rageshakeState = rageshakeState,
|
||||
customElementCallBaseUrlState = CustomElementCallBaseUrlState(
|
||||
baseUrl = customElementCallBaseUrl,
|
||||
defaultUrl = ElementCallConfig.DEFAULT_BASE_URL,
|
||||
validator = ::customElementCallUrlValidator,
|
||||
),
|
||||
hideImagesAndVideos = hideImagesAndVideos,
|
||||
|
||||
@@ -27,6 +27,5 @@ data class DeveloperSettingsState(
|
||||
|
||||
data class CustomElementCallBaseUrlState(
|
||||
val baseUrl: String?,
|
||||
val defaultUrl: String,
|
||||
val validator: (String?) -> Boolean,
|
||||
)
|
||||
|
||||
@@ -47,10 +47,8 @@ fun aDeveloperSettingsState(
|
||||
|
||||
fun aCustomElementCallBaseUrlState(
|
||||
baseUrl: String? = null,
|
||||
defaultUrl: String = "https://call.element.io",
|
||||
validator: (String?) -> Boolean = { true },
|
||||
) = CustomElementCallBaseUrlState(
|
||||
baseUrl = baseUrl,
|
||||
defaultUrl = defaultUrl,
|
||||
validator = validator,
|
||||
)
|
||||
|
||||
@@ -136,22 +136,20 @@ private fun ElementCallCategory(
|
||||
) {
|
||||
PreferenceCategory(title = "Element Call", showTopDivider = true) {
|
||||
val callUrlState = state.customElementCallBaseUrlState
|
||||
fun isUsingDefaultUrl(value: String?): Boolean {
|
||||
return value.isNullOrEmpty() || value == callUrlState.defaultUrl
|
||||
}
|
||||
|
||||
val supportingText = if (isUsingDefaultUrl(callUrlState.baseUrl)) {
|
||||
val supportingText = if (callUrlState.baseUrl.isNullOrEmpty()) {
|
||||
stringResource(R.string.screen_advanced_settings_element_call_base_url_description)
|
||||
} else {
|
||||
callUrlState.baseUrl
|
||||
}
|
||||
PreferenceTextField(
|
||||
headline = stringResource(R.string.screen_advanced_settings_element_call_base_url),
|
||||
value = callUrlState.baseUrl ?: callUrlState.defaultUrl,
|
||||
value = callUrlState.baseUrl,
|
||||
placeholder = "https://.../room",
|
||||
supportingText = supportingText,
|
||||
validation = callUrlState.validator,
|
||||
onValidationErrorMessage = stringResource(R.string.screen_advanced_settings_element_call_base_url_validation_error),
|
||||
displayValue = { value -> !isUsingDefaultUrl(value) },
|
||||
displayValue = { value -> !value.isNullOrEmpty() },
|
||||
keyboardOptions = KeyboardOptions.Default.copy(autoCorrectEnabled = false, keyboardType = KeyboardType.Uri),
|
||||
onChange = { state.eventSink(DeveloperSettingsEvents.SetCustomElementCallBaseUrl(it)) }
|
||||
)
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
package io.element.android.features.preferences.impl.developer
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appconfig.ElementCallConfig
|
||||
import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem
|
||||
import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase
|
||||
@@ -130,7 +129,6 @@ class DeveloperSettingsPresenterTest {
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.customElementCallBaseUrlState.baseUrl).isEqualTo("https://call.element.ahoy")
|
||||
assertThat(state.customElementCallBaseUrlState.defaultUrl).isEqualTo(ElementCallConfig.DEFAULT_BASE_URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,16 @@
|
||||
package io.element.android.features.preferences.impl.developer
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.filterToOne
|
||||
import androidx.compose.ui.test.hasAnyAncestor
|
||||
import androidx.compose.ui.test.isDialog
|
||||
import androidx.compose.ui.test.isEditable
|
||||
import androidx.compose.ui.test.isFocusable
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.preferences.impl.R
|
||||
import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem
|
||||
@@ -56,8 +62,10 @@ class DeveloperSettingsViewTest {
|
||||
),
|
||||
)
|
||||
rule.clickOn(R.string.screen_advanced_settings_element_call_base_url)
|
||||
val textInputNode = rule.onAllNodes(isEditable().and(isFocusable())).filterToOne(hasAnyAncestor(isDialog()))
|
||||
textInputNode.performTextInput("https://call.element.dev")
|
||||
rule.clickOn(CommonStrings.action_ok)
|
||||
eventsRecorder.assertSingle(DeveloperSettingsEvents.SetCustomElementCallBaseUrl("https://call.element.io"))
|
||||
eventsRecorder.assertSingle(DeveloperSettingsEvents.SetCustomElementCallBaseUrl("https://call.element.dev"))
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
|
||||
@@ -211,6 +211,9 @@ dagger_compiler = { module = "com.google.dagger:dagger-compiler", version.ref =
|
||||
anvil_compiler_api = { module = "dev.zacsweers.anvil:compiler-api", version.ref = "anvil" }
|
||||
anvil_compiler_utils = { module = "dev.zacsweers.anvil:compiler-utils", version.ref = "anvil" }
|
||||
|
||||
# Element Call
|
||||
element_call_embedded = "io.element.android:element-call-embedded:0.9.0-rc.2"
|
||||
|
||||
# Auto services
|
||||
google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" }
|
||||
google_autoservice_annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "autoservice" }
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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.libraries.matrix.api.call
|
||||
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
|
||||
interface ElementCallBaseUrlProvider {
|
||||
suspend fun provides(matrixClient: MatrixClient): String?
|
||||
}
|
||||
@@ -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.libraries.matrix.api.widget
|
||||
|
||||
interface CallAnalyticCredentialsProvider {
|
||||
val posthogUserId: String?
|
||||
val posthogApiHost: String?
|
||||
val posthogApiKey: String?
|
||||
val rageshakeSubmitUrl: String?
|
||||
val sentryDsn: String?
|
||||
}
|
||||
@@ -33,8 +33,6 @@ dependencies {
|
||||
implementation(projects.libraries.network)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(projects.services.analyticsproviders.posthog)
|
||||
implementation(projects.services.analyticsproviders.sentry)
|
||||
implementation(projects.services.toolbox.api)
|
||||
api(projects.libraries.matrix.api)
|
||||
implementation(libs.dagger)
|
||||
|
||||
@@ -189,12 +189,12 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
runCatching {
|
||||
val client = currentClient ?: error("You need to call `setHomeserver()` first")
|
||||
val oAuthAuthenticationData = client.urlForOidc(
|
||||
val oAuthAuthorizationData = client.urlForOidc(
|
||||
oidcConfiguration = oidcConfigurationProvider.get(),
|
||||
prompt = prompt.toRustPrompt(),
|
||||
)
|
||||
val url = oAuthAuthenticationData.loginUrl()
|
||||
pendingOAuthAuthorizationData = oAuthAuthenticationData
|
||||
val url = oAuthAuthorizationData.loginUrl()
|
||||
pendingOAuthAuthorizationData = oAuthAuthorizationData
|
||||
OidcDetails(url)
|
||||
}.mapFailure { failure ->
|
||||
failure.mapAuthenticationException()
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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.libraries.matrix.impl.call
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.call.ElementCallBaseUrlProvider
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultElementCallBaseUrlProvider @Inject constructor(
|
||||
private val elementWellKnownParser: ElementWellKnownParser,
|
||||
) : ElementCallBaseUrlProvider {
|
||||
override suspend fun provides(matrixClient: MatrixClient): String? {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(matrixClient.userIdServerName())
|
||||
append("/.well-known/element/element.json")
|
||||
}
|
||||
return matrixClient.getUrl(url)
|
||||
.onFailure { failure ->
|
||||
Timber.w(failure, "Failed to fetch well-known element.json")
|
||||
}
|
||||
.getOrNull()
|
||||
?.let { wellKnownStr ->
|
||||
elementWellKnownParser.parse(wellKnownStr)
|
||||
.onFailure { failure ->
|
||||
// Can be a HTML 404.
|
||||
Timber.w(failure, "Failed to parse content")
|
||||
}
|
||||
.getOrNull()
|
||||
}
|
||||
?.call
|
||||
?.widgetUrl
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,13 @@
|
||||
package io.element.android.libraries.matrix.impl.widget
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.appconfig.RageshakeConfig
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.core.meta.BuildType
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.widget.CallAnalyticCredentialsProvider
|
||||
import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
||||
import io.element.android.services.analytics.api.store.AnalyticsStore
|
||||
import io.element.android.services.analyticsproviders.posthog.PosthogEndpointConfigProvider
|
||||
import io.element.android.services.analyticsproviders.sentry.SentryConfig
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.coroutines.flow.first
|
||||
import org.matrix.rustcomponents.sdk.EncryptionSystem
|
||||
import org.matrix.rustcomponents.sdk.VirtualElementCallWidgetOptions
|
||||
@@ -27,12 +25,11 @@ import org.matrix.rustcomponents.sdk.Intent as CallIntent
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultCallWidgetSettingsProvider @Inject constructor(
|
||||
private val buildMeta: BuildMeta,
|
||||
private val posthogEndpointConfigProvider: PosthogEndpointConfigProvider,
|
||||
private val analyticsStore: AnalyticsStore,
|
||||
private val callAnalyticsCredentialsProvider: CallAnalyticCredentialsProvider,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : CallWidgetSettingsProvider {
|
||||
override suspend fun provide(baseUrl: String, widgetId: String, encrypted: Boolean): MatrixWidgetSettings {
|
||||
val analyticsEnabled = analyticsStore.userConsentFlow.first()
|
||||
val posthogEndpointConfig = posthogEndpointConfigProvider.provide()
|
||||
val isAnalyticsEnabled = analyticsService.getUserConsent().first()
|
||||
val options = VirtualElementCallWidgetOptions(
|
||||
elementCallUrl = baseUrl,
|
||||
widgetId = widgetId,
|
||||
@@ -44,12 +41,12 @@ class DefaultCallWidgetSettingsProvider @Inject constructor(
|
||||
encryption = if (encrypted) EncryptionSystem.PerParticipantKeys else EncryptionSystem.Unencrypted,
|
||||
intent = CallIntent.START_CALL,
|
||||
hideScreensharing = false,
|
||||
posthogUserId = null,
|
||||
posthogApiHost = posthogEndpointConfig.host.takeIf { analyticsEnabled },
|
||||
posthogApiKey = posthogEndpointConfig.apiKey.takeIf { analyticsEnabled },
|
||||
rageshakeSubmitUrl = RageshakeConfig.BUG_REPORT_URL,
|
||||
sentryDsn = SentryConfig.DSN.takeIf { analyticsEnabled },
|
||||
sentryEnvironment = if (buildMeta.buildType == BuildType.RELEASE) SentryConfig.ENV_RELEASE else SentryConfig.ENV_DEBUG,
|
||||
posthogUserId = callAnalyticsCredentialsProvider.posthogUserId.takeIf { isAnalyticsEnabled },
|
||||
posthogApiHost = callAnalyticsCredentialsProvider.posthogApiHost.takeIf { isAnalyticsEnabled },
|
||||
posthogApiKey = callAnalyticsCredentialsProvider.posthogApiKey.takeIf { isAnalyticsEnabled },
|
||||
rageshakeSubmitUrl = callAnalyticsCredentialsProvider.rageshakeSubmitUrl,
|
||||
sentryDsn = callAnalyticsCredentialsProvider.sentryDsn.takeIf { isAnalyticsEnabled },
|
||||
sentryEnvironment = if (buildMeta.buildType == BuildType.RELEASE) "RELEASE" else "DEBUG",
|
||||
parentUrl = null,
|
||||
hideHeader = true,
|
||||
)
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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.libraries.matrix.impl.call
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.matrix.rustcomponents.sdk.ElementCallWellKnown
|
||||
import org.matrix.rustcomponents.sdk.ElementWellKnown
|
||||
|
||||
class DefaultElementCallBaseUrlProviderTest {
|
||||
@Test
|
||||
fun `provides returns null when getUrl returns an error`() = runTest {
|
||||
val userIdServerNameLambda = lambdaRecorder<String> { "example.com" }
|
||||
val getUrlLambda = lambdaRecorder<String, Result<String>> { _ ->
|
||||
Result.failure(AN_EXCEPTION)
|
||||
}
|
||||
val sut = DefaultElementCallBaseUrlProvider(
|
||||
FakeElementWellKnownParser(
|
||||
Result.success(createElementWellKnown(""))
|
||||
)
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
userIdServerNameLambda = userIdServerNameLambda,
|
||||
getUrlLambda = getUrlLambda,
|
||||
)
|
||||
val result = sut.provides(matrixClient)
|
||||
assertThat(result).isNull()
|
||||
userIdServerNameLambda.assertions().isCalledOnce()
|
||||
getUrlLambda.assertions().isCalledOnce()
|
||||
.with(value("https://example.com/.well-known/element/element.json"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `provides returns null when content parsing fails`() = runTest {
|
||||
val userIdServerNameLambda = lambdaRecorder<String> { "example.com" }
|
||||
val getUrlLambda = lambdaRecorder<String, Result<String>> { _ ->
|
||||
Result.success("""{"call":{"widget_url":"https://example.com/call"}}""")
|
||||
}
|
||||
val sut = DefaultElementCallBaseUrlProvider(
|
||||
createFakeElementWellKnownParser(
|
||||
Result.failure(AN_EXCEPTION)
|
||||
)
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
userIdServerNameLambda = userIdServerNameLambda,
|
||||
getUrlLambda = getUrlLambda,
|
||||
)
|
||||
val result = sut.provides(matrixClient)
|
||||
assertThat(result).isNull()
|
||||
userIdServerNameLambda.assertions().isCalledOnce()
|
||||
getUrlLambda.assertions().isCalledOnce()
|
||||
.with(value("https://example.com/.well-known/element/element.json"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `provides returns value when getUrl returns correct content`() = runTest {
|
||||
val userIdServerNameLambda = lambdaRecorder<String> { "example.com" }
|
||||
val getUrlLambda = lambdaRecorder<String, Result<String>> { _ ->
|
||||
Result.success("""{"call":{"widget_url":"https://example.com/call"}}""")
|
||||
}
|
||||
val sut = DefaultElementCallBaseUrlProvider(
|
||||
createFakeElementWellKnownParser(
|
||||
Result.success(createElementWellKnown("aUrl"))
|
||||
)
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
userIdServerNameLambda = userIdServerNameLambda,
|
||||
getUrlLambda = getUrlLambda,
|
||||
)
|
||||
val result = sut.provides(matrixClient)
|
||||
assertThat(result).isEqualTo("aUrl")
|
||||
userIdServerNameLambda.assertions().isCalledOnce()
|
||||
getUrlLambda.assertions().isCalledOnce()
|
||||
.with(value("https://example.com/.well-known/element/element.json"))
|
||||
}
|
||||
|
||||
private fun createFakeElementWellKnownParser(result: Result<ElementWellKnown>): FakeElementWellKnownParser {
|
||||
return FakeElementWellKnownParser(result)
|
||||
}
|
||||
|
||||
private fun createElementWellKnown(widgetUrl: String): ElementWellKnown {
|
||||
return ElementWellKnown(
|
||||
call = ElementCallWellKnown(
|
||||
widgetUrl = widgetUrl,
|
||||
),
|
||||
registrationHelperUrl = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* 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.services.analytics.api.store
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Local storage for:
|
||||
* - user consent (Boolean);
|
||||
* - did ask user consent (Boolean);
|
||||
* - analytics Id (String).
|
||||
*/
|
||||
interface AnalyticsStore {
|
||||
val userConsentFlow: Flow<Boolean>
|
||||
val didAskUserConsentFlow: Flow<Boolean>
|
||||
val analyticsIdFlow: Flow<String>
|
||||
suspend fun setUserConsent(newUserConsent: Boolean)
|
||||
suspend fun setDidAskUserConsent(newValue: Boolean = true)
|
||||
suspend fun setAnalyticsId(newAnalyticsId: String)
|
||||
suspend fun reset()
|
||||
}
|
||||
@@ -17,8 +17,8 @@ import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.sessionstorage.api.observer.SessionListener
|
||||
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.api.store.AnalyticsStore
|
||||
import io.element.android.services.analytics.impl.log.analyticsTag
|
||||
import io.element.android.services.analytics.impl.store.AnalyticsStore
|
||||
import io.element.android.services.analyticsproviders.api.AnalyticsProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@@ -18,7 +18,6 @@ import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.services.analytics.api.store.AnalyticsStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -29,6 +28,22 @@ import javax.inject.Inject
|
||||
*/
|
||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "vector_analytics")
|
||||
|
||||
/**
|
||||
* Local storage for:
|
||||
* - user consent (Boolean);
|
||||
* - did ask user consent (Boolean);
|
||||
* - analytics Id (String).
|
||||
*/
|
||||
interface AnalyticsStore {
|
||||
val userConsentFlow: Flow<Boolean>
|
||||
val didAskUserConsentFlow: Flow<Boolean>
|
||||
val analyticsIdFlow: Flow<String>
|
||||
suspend fun setUserConsent(newUserConsent: Boolean)
|
||||
suspend fun setDidAskUserConsent(newValue: Boolean = true)
|
||||
suspend fun setAnalyticsId(newAnalyticsId: String)
|
||||
suspend fun reset()
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultAnalyticsStore @Inject constructor(
|
||||
@ApplicationContext private val context: Context
|
||||
@@ -18,7 +18,7 @@ import im.vector.app.features.analytics.plan.SuperProperties
|
||||
import im.vector.app.features.analytics.plan.UserProperties
|
||||
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
|
||||
import io.element.android.libraries.sessionstorage.test.observer.NoOpSessionObserver
|
||||
import io.element.android.services.analytics.api.store.AnalyticsStore
|
||||
import io.element.android.services.analytics.impl.store.AnalyticsStore
|
||||
import io.element.android.services.analytics.impl.store.FakeAnalyticsStore
|
||||
import io.element.android.services.analyticsproviders.api.AnalyticsProvider
|
||||
import io.element.android.services.analyticsproviders.test.FakeAnalyticsProvider
|
||||
|
||||
@@ -7,11 +7,10 @@
|
||||
|
||||
package io.element.android.services.analytics.impl.store
|
||||
|
||||
import io.element.android.services.analytics.api.store.AnalyticsStore
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class FakeAnalyticsStore(
|
||||
internal class FakeAnalyticsStore(
|
||||
defaultUserConsent: Boolean = false,
|
||||
defaultDidAskUserConsent: Boolean = false,
|
||||
defaultAnalyticsId: String = "",
|
||||
|
||||
Reference in New Issue
Block a user