1
.idea/dictionaries/shared.xml
generated
1
.idea/dictionaries/shared.xml
generated
@@ -8,6 +8,7 @@
|
||||
<w>measurables</w>
|
||||
<w>onboarding</w>
|
||||
<w>placeables</w>
|
||||
<w>posthog</w>
|
||||
<w>showkase</w>
|
||||
<w>snackbar</w>
|
||||
<w>swipeable</w>
|
||||
|
||||
@@ -33,7 +33,7 @@ class FakeAnalyticsService(
|
||||
private val didAskUserConsentFlow = MutableStateFlow(didAskUserConsent)
|
||||
val capturedEvents = mutableListOf<VectorAnalyticsEvent>()
|
||||
|
||||
override fun getAvailableAnalyticsProviders(): List<AnalyticsProvider> = emptyList()
|
||||
override fun getAvailableAnalyticsProviders(): Set<AnalyticsProvider> = emptySet()
|
||||
|
||||
override fun getUserConsent(): Flow<Boolean> = isEnabledFlow
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ dependencies {
|
||||
implementation(projects.features.ftue.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.features.logout.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(projects.services.toolbox.api)
|
||||
implementation(libs.datetime)
|
||||
implementation(libs.accompanist.placeholder)
|
||||
|
||||
@@ -56,6 +56,12 @@ fun DeveloperSettingsView(
|
||||
RageshakePreferencesView(
|
||||
state = state.rageshakeState,
|
||||
)
|
||||
PreferenceCategory(title = "Crash", showDivider = false) {
|
||||
PreferenceText(
|
||||
title = "Crash the app 💥",
|
||||
onClick = { error("This crash is a test.") }
|
||||
)
|
||||
}
|
||||
val cache = state.cacheSize
|
||||
PreferenceCategory(title = "Cache", showDivider = false) {
|
||||
PreferenceText(
|
||||
|
||||
@@ -35,6 +35,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.api.user.getCurrentUser
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
@@ -43,6 +44,7 @@ class PreferencesRootPresenter @Inject constructor(
|
||||
private val logoutPresenter: LogoutPreferencePresenter,
|
||||
private val matrixClient: MatrixClient,
|
||||
private val sessionVerificationService: SessionVerificationService,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val buildType: BuildType,
|
||||
private val versionFormatter: VersionFormatter,
|
||||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
@@ -58,6 +60,7 @@ class PreferencesRootPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
||||
val hasAnalyticsProviders = remember { analyticsService.getAvailableAnalyticsProviders().isNotEmpty() }
|
||||
|
||||
// Session verification status (unknown, not verified, verified)
|
||||
val sessionVerifiedStatus by sessionVerificationService.sessionVerifiedStatus.collectAsState()
|
||||
@@ -72,6 +75,7 @@ class PreferencesRootPresenter @Inject constructor(
|
||||
myUser = matrixUser.value,
|
||||
version = versionFormatter.get(),
|
||||
showCompleteVerification = sessionIsNotVerified,
|
||||
showAnalyticsSettings = hasAnalyticsProviders,
|
||||
showDeveloperSettings = showDeveloperSettings,
|
||||
snackbarMessage = snackbarMessage,
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ data class PreferencesRootState(
|
||||
val myUser: MatrixUser?,
|
||||
val version: String,
|
||||
val showCompleteVerification: Boolean,
|
||||
val showAnalyticsSettings: Boolean,
|
||||
val showDeveloperSettings: Boolean,
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ fun aPreferencesRootState() = PreferencesRootState(
|
||||
myUser = null,
|
||||
version = "Version 1.1 (1)",
|
||||
showCompleteVerification = true,
|
||||
showAnalyticsSettings = true,
|
||||
showDeveloperSettings = true,
|
||||
snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete),
|
||||
)
|
||||
|
||||
@@ -82,11 +82,13 @@ fun PreferencesRootView(
|
||||
)
|
||||
Divider()
|
||||
}
|
||||
PreferenceText(
|
||||
title = stringResource(id = CommonStrings.common_analytics),
|
||||
icon = Icons.Outlined.InsertChart,
|
||||
onClick = onOpenAnalytics,
|
||||
)
|
||||
if (state.showAnalyticsSettings) {
|
||||
PreferenceText(
|
||||
title = stringResource(id = CommonStrings.common_analytics),
|
||||
icon = Icons.Outlined.InsertChart,
|
||||
onClick = onOpenAnalytics,
|
||||
)
|
||||
}
|
||||
PreferenceText(
|
||||
title = stringResource(id = CommonStrings.action_report_bug),
|
||||
icon = Icons.Outlined.BugReport,
|
||||
|
||||
@@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.features.logout.impl.DefaultLogoutPreferencePresenter
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.core.meta.BuildType
|
||||
@@ -41,6 +42,7 @@ class PreferencesRootPresenterTest {
|
||||
logoutPresenter,
|
||||
matrixClient,
|
||||
FakeSessionVerificationService(),
|
||||
FakeAnalyticsService(),
|
||||
BuildType.DEBUG,
|
||||
FakeVersionFormatter(),
|
||||
SnackbarDispatcher(),
|
||||
@@ -61,6 +63,7 @@ class PreferencesRootPresenterTest {
|
||||
)
|
||||
)
|
||||
assertThat(loadedState.showDeveloperSettings).isEqualTo(true)
|
||||
assertThat(loadedState.showAnalyticsSettings).isEqualTo(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:2.0.0"
|
||||
|
||||
# Analytics
|
||||
posthog = "com.posthog.android:posthog:2.0.3"
|
||||
sentry_android = "io.sentry:sentry-android:6.26.0"
|
||||
sentry = "io.sentry:sentry-android:6.26.0"
|
||||
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:42b2faa417c1e95f430bf8f6e379adba25ad5ef8"
|
||||
|
||||
# Di
|
||||
|
||||
@@ -103,8 +103,12 @@ fun DependencyHandlerScope.allLibrariesImpl() {
|
||||
}
|
||||
|
||||
fun DependencyHandlerScope.allServicesImpl() {
|
||||
// For analytics configuration, either use noop, or use the impl, with at least one analyticsproviders implementation
|
||||
// implementation(project(":services:analytics:noop"))
|
||||
implementation(project(":services:analytics:impl"))
|
||||
implementation(project(":services:analyticsproviders:posthog"))
|
||||
implementation(project(":services:analyticsproviders:sentry"))
|
||||
|
||||
implementation(project(":services:apperror:impl"))
|
||||
implementation(project(":services:appnavstate:impl"))
|
||||
implementation(project(":services:toolbox:impl"))
|
||||
|
||||
@@ -22,7 +22,10 @@ import io.element.android.services.analyticsproviders.api.trackers.ErrorTracker
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AnalyticsService: AnalyticsTracker, ErrorTracker {
|
||||
fun getAvailableAnalyticsProviders(): List<AnalyticsProvider>
|
||||
/**
|
||||
* Get the available analytics providers.
|
||||
*/
|
||||
fun getAvailableAnalyticsProviders(): Set<AnalyticsProvider>
|
||||
|
||||
/**
|
||||
* Return a Flow of Boolean, true if the user has given their consent.
|
||||
|
||||
@@ -56,8 +56,8 @@ class DefaultAnalyticsService @Inject constructor(
|
||||
observeSessions()
|
||||
}
|
||||
|
||||
override fun getAvailableAnalyticsProviders(): List<AnalyticsProvider> {
|
||||
return analyticsProviders.sortedBy { it.index }
|
||||
override fun getAvailableAnalyticsProviders(): Set<AnalyticsProvider> {
|
||||
return analyticsProviders
|
||||
}
|
||||
|
||||
override fun getUserConsent(): Flow<Boolean> {
|
||||
|
||||
@@ -19,7 +19,7 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.services.analytics.impl"
|
||||
namespace = "io.element.android.services.analytics.noop"
|
||||
}
|
||||
|
||||
anvil {
|
||||
@@ -28,6 +28,7 @@ anvil {
|
||||
|
||||
dependencies {
|
||||
implementation(libs.dagger)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.di)
|
||||
api(projects.services.analytics.api)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.services.analytics.noop
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
|
||||
import im.vector.app.features.analytics.plan.UserProperties
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analyticsproviders.api.AnalyticsProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import javax.inject.Inject
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
@ContributesBinding(AppScope::class)
|
||||
class NoopAnalyticsService @Inject constructor(
|
||||
) : AnalyticsService {
|
||||
override fun getAvailableAnalyticsProviders(): Set<AnalyticsProvider> = emptySet()
|
||||
override fun getUserConsent(): Flow<Boolean> = flowOf(false)
|
||||
override suspend fun setUserConsent(userConsent: Boolean) = Unit
|
||||
override fun didAskUserConsent(): Flow<Boolean> = flowOf(true)
|
||||
override suspend fun setDidAskUserConsent() = Unit
|
||||
override fun getAnalyticsId(): Flow<String> = flowOf("")
|
||||
override suspend fun setAnalyticsId(analyticsId: String) = Unit
|
||||
override suspend fun onSignOut() = Unit
|
||||
override suspend fun reset() = Unit
|
||||
override fun capture(event: VectorAnalyticsEvent) = Unit
|
||||
override fun screen(screen: VectorAnalyticsScreen) = Unit
|
||||
override fun updateUserProperties(userProperties: UserProperties) = Unit
|
||||
override fun trackError(throwable: Throwable) = Unit
|
||||
}
|
||||
@@ -20,11 +20,6 @@ import io.element.android.services.analyticsproviders.api.trackers.AnalyticsTrac
|
||||
import io.element.android.services.analyticsproviders.api.trackers.ErrorTracker
|
||||
|
||||
interface AnalyticsProvider: AnalyticsTracker, ErrorTracker {
|
||||
/**
|
||||
* Allow to sort providers, from lower index to higher index.
|
||||
*/
|
||||
val index: Int
|
||||
|
||||
/**
|
||||
* User friendly name.
|
||||
*/
|
||||
|
||||
@@ -35,7 +35,6 @@ import javax.inject.Inject
|
||||
class PosthogAnalyticsProvider @Inject constructor(
|
||||
private val postHogFactory: PostHogFactory,
|
||||
) : AnalyticsProvider {
|
||||
override val index = PosthogConfig.index
|
||||
override val name = PosthogConfig.name
|
||||
|
||||
private var posthog: PostHog? = null
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package io.element.android.services.analyticsproviders.posthog
|
||||
|
||||
object PosthogConfig {
|
||||
const val index = 0
|
||||
const val name = "Posthog"
|
||||
const val postHogHost = "https://posthog.element.dev"
|
||||
const val postHogApiKey = "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN"
|
||||
|
||||
@@ -18,4 +18,4 @@ package io.element.android.services.analyticsproviders.posthog.log
|
||||
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
|
||||
val analyticsTag = LoggerTag("Analytics")
|
||||
internal val analyticsTag = LoggerTag("Posthog")
|
||||
|
||||
35
services/analyticsproviders/sentry/build.gradle.kts
Normal file
35
services/analyticsproviders/sentry/build.gradle.kts
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
alias(libs.plugins.anvil)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.services.analyticsproviders.sentry"
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.dagger)
|
||||
implementation(libs.sentry)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.di)
|
||||
implementation(projects.services.analyticsproviders.api)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2023 New Vector Ltd
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<!-- Sentry auto-initialization disabled -->
|
||||
<meta-data
|
||||
android:name="io.sentry.auto-init"
|
||||
android:value="false" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.services.analyticsproviders.sentry
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.anvil.annotations.ContributesMultibinding
|
||||
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
|
||||
import im.vector.app.features.analytics.plan.UserProperties
|
||||
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.di.ApplicationContext
|
||||
import io.element.android.services.analyticsproviders.api.AnalyticsProvider
|
||||
import io.element.android.services.analyticsproviders.sentry.log.analyticsTag
|
||||
import io.sentry.Sentry
|
||||
import io.sentry.SentryOptions
|
||||
import io.sentry.android.core.SentryAndroid
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesMultibinding(AppScope::class)
|
||||
class SentryAnalyticsProvider @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val buildMeta: BuildMeta,
|
||||
) : AnalyticsProvider {
|
||||
override val name = SentryConfig.name
|
||||
|
||||
override fun init() {
|
||||
Timber.tag(analyticsTag.value).d("Initializing Sentry")
|
||||
if (Sentry.isEnabled()) return
|
||||
SentryAndroid.init(context) { options ->
|
||||
options.dsn = SentryConfig.dns
|
||||
options.beforeSend = SentryOptions.BeforeSendCallback { event, _ -> event }
|
||||
options.tracesSampleRate = 1.0
|
||||
options.isEnableUserInteractionTracing = true
|
||||
options.environment = buildMeta.buildType.toSentryEnv()
|
||||
options.diagnosticLevel
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
Timber.tag(analyticsTag.value).d("Stopping Sentry")
|
||||
Sentry.close()
|
||||
}
|
||||
|
||||
override fun capture(event: VectorAnalyticsEvent) {
|
||||
}
|
||||
|
||||
override fun screen(screen: VectorAnalyticsScreen) {
|
||||
}
|
||||
|
||||
override fun updateUserProperties(userProperties: UserProperties) {
|
||||
}
|
||||
|
||||
override fun trackError(throwable: Throwable) {
|
||||
Sentry.captureException(throwable)
|
||||
}
|
||||
}
|
||||
|
||||
private fun BuildType.toSentryEnv() = when (this) {
|
||||
BuildType.RELEASE -> SentryConfig.envRelease
|
||||
BuildType.NIGHTLY,
|
||||
BuildType.DEBUG -> SentryConfig.envDebug
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.services.analyticsproviders.sentry
|
||||
|
||||
object SentryConfig {
|
||||
const val name = "Sentry"
|
||||
const val dns = "https://32f7ff6a6e724f90838b7654042b2e81@sentry.tools.element.io/59"
|
||||
const val envDebug = "DEBUG"
|
||||
const val envRelease = "RELEASE"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.services.analyticsproviders.sentry.log
|
||||
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
|
||||
internal val analyticsTag = LoggerTag("Sentry")
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user