From d2263372d2838b3d019b45d47513997d04c13245 Mon Sep 17 00:00:00 2001 From: Yoan Pintas Date: Mon, 5 Jun 2023 15:11:34 +0200 Subject: [PATCH] Init analytics modules (#350) --- .gitignore | 3 +- .maestro/tests/account/login.yaml | 2 + .../assertions/assertAnalyticsDisplayed.yaml | 5 + appnav/build.gradle.kts | 3 +- .../android/appnav/LoggedInFlowNode.kt | 52 +- .../appnav/loggedin/LoggedInPresenterTest.kt | 4 +- features/analytics/api/build.gradle.kts | 32 ++ .../analytics/api/AnalyticsEntryPoint.kt | 21 + .../analytics/api/AnalyticsOptInEvents.kt | 21 + .../AnalyticsPreferencesPresenter.kt | 21 + .../preferences/AnalyticsPreferencesState.kt | 25 + .../AnalyticsPreferencesStateProvider.kt | 32 ++ .../preferences/AnalyticsPreferencesView.kt | 94 ++++ features/analytics/impl/build.gradle.kts | 57 +++ .../analytics/impl/AnalyticsOptInNode.kt | 44 ++ .../analytics/impl/AnalyticsOptInPresenter.kt | 56 +++ .../analytics/impl/AnalyticsOptInState.kt | 24 + .../impl/AnalyticsOptInStateProvider.kt | 35 ++ .../analytics/impl/AnalyticsOptInView.kt | 238 +++++++++ .../impl/DefaultAnalyticsEntryPoint.kt | 21 +- .../DefaultAnalyticsPreferencesPresenter.kt | 61 +++ .../main/res/drawable/element_logo_stars.xml | 57 +++ .../src/main/res/values-de/translations.xml | 10 + .../src/main/res/values-ro/translations.xml | 10 + .../impl/src/main/res/values/localazy.xml | 10 + .../impl/AnalyticsOptInPresenterTest.kt | 68 +++ .../AnalyticsPreferencesPresenterTest.kt | 78 +++ features/analytics/test/build.gradle.kts | 28 ++ .../analytics/test/FakeAnalyticsService.kt | 68 +++ .../features/analytics/test/TestData.kt | 35 ++ features/preferences/impl/build.gradle.kts | 3 + .../impl/root/PreferencesRootPresenter.kt | 4 + .../impl/root/PreferencesRootState.kt | 2 + .../impl/root/PreferencesRootStateProvider.kt | 2 + .../impl/root/PreferencesRootView.kt | 4 + .../impl/root/PreferencesRootPresenterTest.kt | 9 +- gradle/libs.versions.toml | 7 + .../android/libraries/push/api/PushService.kt | 4 +- .../libraries/push/impl/DefaultPushService.kt | 4 +- .../libraries/push/impl/PushersManager.kt | 2 +- .../NotificationBroadcastReceiver.kt | 3 +- .../push/impl/push/DefaultPushHandler.kt | 4 +- libraries/pushproviders/api/build.gradle.kts | 2 +- .../api/Distributor.kt | 2 +- .../api/PushData.kt | 2 +- .../api/PushHandler.kt | 2 +- .../api/PushProvider.kt | 2 +- .../api/PusherSubscriber.kt | 2 +- .../pushproviders/firebase/build.gradle.kts | 2 +- .../firebase/src/main/AndroidManifest.xml | 2 +- .../EnsureFcmTokenIsRetrievedUseCase.kt | 2 +- .../firebase/FirebaseConfig.kt | 2 +- .../firebase/FirebaseNewTokenHandler.kt | 4 +- .../firebase/FirebasePushParser.kt | 4 +- .../firebase/FirebasePushProvider.kt | 8 +- .../firebase/FirebaseStore.kt | 2 +- .../firebase/FirebaseTroubleshooter.kt | 2 +- .../firebase/PushDataFirebase.kt | 4 +- .../VectorFirebaseMessagingService.kt | 4 +- .../VectorFirebaseMessagingServiceBindings.kt | 2 +- .../firebase/FirebasePushParserTest.kt | 4 +- .../unifiedpush/build.gradle.kts | 2 +- .../unifiedpush/GuardServiceStarter.kt | 2 +- .../unifiedpush/KeepInternalDistributor.kt | 2 +- .../unifiedpush/PushDataUnifiedPush.kt | 4 +- .../unifiedpush/RegisterUnifiedPushUseCase.kt | 6 +- .../unifiedpush/UnifiedPushConfig.kt | 2 +- .../unifiedpush/UnifiedPushGatewayResolver.kt | 4 +- .../UnifiedPushNewGatewayHandler.kt | 4 +- .../unifiedpush/UnifiedPushParser.kt | 4 +- .../unifiedpush/UnifiedPushProvider.kt | 6 +- .../unifiedpush/UnifiedPushStore.kt | 2 +- .../UnregisterUnifiedPushUseCase.kt | 2 +- .../VectorUnifiedPushMessagingReceiver.kt | 4 +- ...torUnifiedPushMessagingReceiverBindings.kt | 2 +- .../unifiedpush/network/DiscoveryResponse.kt | 2 +- .../network/DiscoveryUnifiedPush.kt | 2 +- .../unifiedpush/network/UnifiedPushApi.kt | 2 +- .../unifiedpush/UnifiedPushParserTest.kt | 4 +- .../src/main/res/values-de/translations.xml | 7 - .../src/main/res/values-ro/translations.xml | 7 - .../src/main/res/values/localazy.xml | 7 - .../kotlin/extension/DependencyHandleScope.kt | 3 +- services/analytics/api/build.gradle.kts | 5 + .../analytics/api/AnalyticsService.kt | 61 +++ .../services/analytics/api/plan/CallEnded.kt | 56 --- .../services/analytics/api/plan/CallError.kt | 51 -- .../analytics/api/plan/CallStarted.kt | 51 -- .../services/analytics/api/plan/Composer.kt | 58 --- .../analytics/api/plan/CreatedRoom.kt | 41 -- .../services/analytics/api/plan/Error.kt | 82 --- .../analytics/api/plan/Interaction.kt | 468 ------------------ .../services/analytics/api/plan/JoinedRoom.kt | 107 ---- .../analytics/api/plan/MobileScreen.kt | 327 ------------ .../analytics/api/plan/PerformanceTimer.kt | 109 ---- .../analytics/api/plan/PermissionChanged.kt | 53 -- .../services/analytics/api/plan/Signup.kt | 84 ---- .../analytics/api/plan/SlashCommand.kt | 46 -- .../api/plan/UnauthenticatedError.kt | 66 --- .../analytics/api/plan/UserProperties.kt | 98 ---- .../services/analytics/api/plan/ViewRoom.kt | 306 ------------ services/analytics/impl/build.gradle.kts | 46 ++ .../analytics/impl/DefaultAnalyticsService.kt | 158 ++++++ .../analytics/impl/log/AnalyticsLoggerTag.kt} | 9 +- .../analytics/impl/store/AnalyticsStore.kt | 86 ++++ .../analyticsproviders/api/build.gradle.kts | 26 + .../api/AnalyticsProvider.kt | 36 ++ .../api/trackers}/AnalyticsTracker.kt | 6 +- .../api/trackers/ErrorTracker.kt | 21 + .../posthog/build.gradle.kts | 37 ++ .../posthog/PostHogFactory.kt | 55 ++ .../posthog/PosthogAnalyticsProvider.kt | 109 ++++ .../posthog/PosthogConfig.kt | 24 + .../posthog/extensions/InteractionExt.kt | 25 + .../posthog/log/AnalyticsLoggerTag.kt} | 9 +- settings.gradle.kts | 1 + ...ewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 3 + ...wLightPreview_0_null_0,NEXUS_5,1.0,en].png | 3 + ...ewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 3 + ...wLightPreview_0_null_0,NEXUS_5,1.0,en].png | 3 + ...arkPreview--1_1_null_0,NEXUS_5,1.0,en].png | 4 +- ...arkPreview--1_1_null_1,NEXUS_5,1.0,en].png | 4 +- ...ghtPreview--0_0_null_0,NEXUS_5,1.0,en].png | 4 +- ...ghtPreview--0_0_null_1,NEXUS_5,1.0,en].png | 4 +- tools/localazy/config.json | 6 + 125 files changed, 2018 insertions(+), 2130 deletions(-) create mode 100644 .maestro/tests/assertions/assertAnalyticsDisplayed.yaml create mode 100644 features/analytics/api/build.gradle.kts create mode 100644 features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsEntryPoint.kt create mode 100644 features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsOptInEvents.kt create mode 100644 features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesPresenter.kt create mode 100644 features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt create mode 100644 features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt create mode 100644 features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt create mode 100644 features/analytics/impl/build.gradle.kts create mode 100644 features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt create mode 100644 features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt create mode 100644 features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt create mode 100644 features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt create mode 100644 features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt rename services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsTracker.kt => features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/DefaultAnalyticsEntryPoint.kt (56%) create mode 100644 features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/DefaultAnalyticsPreferencesPresenter.kt create mode 100644 features/analytics/impl/src/main/res/drawable/element_logo_stars.xml create mode 100644 features/analytics/impl/src/main/res/values-de/translations.xml create mode 100644 features/analytics/impl/src/main/res/values-ro/translations.xml create mode 100644 features/analytics/impl/src/main/res/values/localazy.xml create mode 100644 features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt create mode 100644 features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt create mode 100644 features/analytics/test/build.gradle.kts create mode 100644 features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt create mode 100644 features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/TestData.kt rename libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/api/Distributor.kt (92%) rename libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/api/PushData.kt (95%) rename libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/api/PushHandler.kt (92%) rename libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/api/PushProvider.kt (95%) rename libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/api/PusherSubscriber.kt (94%) rename libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/firebase/EnsureFcmTokenIsRetrievedUseCase.kt (96%) rename libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/firebase/FirebaseConfig.kt (93%) rename libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/firebase/FirebaseNewTokenHandler.kt (94%) rename libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/firebase/FirebasePushParser.kt (89%) rename libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/firebase/FirebasePushProvider.kt (89%) rename libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/firebase/FirebaseStore.kt (95%) rename libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/firebase/FirebaseTroubleshooter.kt (98%) rename libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/firebase/PushDataFirebase.kt (91%) rename libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/firebase/VectorFirebaseMessagingService.kt (94%) rename libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/firebase/VectorFirebaseMessagingServiceBindings.kt (93%) rename libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/{push/providers => pushproviders}/firebase/FirebasePushParserTest.kt (96%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/GuardServiceStarter.kt (93%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/KeepInternalDistributor.kt (94%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/PushDataUnifiedPush.kt (93%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/RegisterUnifiedPushUseCase.kt (93%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/UnifiedPushConfig.kt (93%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/UnifiedPushGatewayResolver.kt (93%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/UnifiedPushNewGatewayHandler.kt (94%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/UnifiedPushParser.kt (89%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/UnifiedPushProvider.kt (92%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/UnifiedPushStore.kt (97%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/UnregisterUnifiedPushUseCase.kt (96%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/VectorUnifiedPushMessagingReceiver.kt (97%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/VectorUnifiedPushMessagingReceiverBindings.kt (93%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/network/DiscoveryResponse.kt (91%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/network/DiscoveryUnifiedPush.kt (91%) rename libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/network/UnifiedPushApi.kt (91%) rename libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/{push/providers => pushproviders}/unifiedpush/UnifiedPushParserTest.kt (95%) create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallEnded.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallError.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallStarted.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Composer.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CreatedRoom.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Error.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Interaction.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/JoinedRoom.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/MobileScreen.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PerformanceTimer.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PermissionChanged.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Signup.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/SlashCommand.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UnauthenticatedError.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UserProperties.kt delete mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/ViewRoom.kt create mode 100644 services/analytics/impl/build.gradle.kts create mode 100644 services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt rename services/analytics/{api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsEvent.kt => impl/src/main/kotlin/io/element/android/services/analytics/impl/log/AnalyticsLoggerTag.kt} (79%) create mode 100644 services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/store/AnalyticsStore.kt create mode 100644 services/analyticsproviders/api/build.gradle.kts create mode 100644 services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/AnalyticsProvider.kt rename services/{analytics/api/src/main/kotlin/io/element/android/services/analytics/api => analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers}/AnalyticsTracker.kt (78%) create mode 100644 services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/ErrorTracker.kt create mode 100644 services/analyticsproviders/posthog/build.gradle.kts create mode 100644 services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt create mode 100644 services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt create mode 100644 services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogConfig.kt create mode 100644 services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/extensions/InteractionExt.kt rename services/{analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsScreen.kt => analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/log/AnalyticsLoggerTag.kt} (77%) create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.api.preferences_null_DefaultGroup_AnalyticsPreferencesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.api.preferences_null_DefaultGroup_AnalyticsPreferencesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewLightPreview_0_null_0,NEXUS_5,1.0,en].png diff --git a/.gitignore b/.gitignore index cde20b0085..70599626ce 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ captures/ .idea/.name .idea/assetWizardSettings.xml .idea/compiler.xml +.idea/deploymentTargetDropDown.xml .idea/gradle.xml .idea/jarRepositories.xml .idea/misc.xml @@ -54,6 +55,7 @@ captures/ .idea/inspectionProfiles # Shelved changes in the IDE .idea/shelf +.idea/sonarlint # Keystore files # Uncomment the following lines if you do not want to check your keystore files in. @@ -88,7 +90,6 @@ lint/generated/ lint/outputs/ lint/tmp/ # lint/reports/ -/.idea/deploymentTargetDropDown.xml /tmp .DS_Store diff --git a/.maestro/tests/account/login.yaml b/.maestro/tests/account/login.yaml index 59cc0980e4..845746a76b 100644 --- a/.maestro/tests/account/login.yaml +++ b/.maestro/tests/account/login.yaml @@ -21,4 +21,6 @@ appId: ${APP_ID} - inputText: ${PASSWORD} - pressKey: Enter - tapOn: "Continue" +- runFlow: ../assertions/assertAnalyticsDisplayed.yaml +- tapOn: "Not now" - runFlow: ../assertions/assertHomeDisplayed.yaml diff --git a/.maestro/tests/assertions/assertAnalyticsDisplayed.yaml b/.maestro/tests/assertions/assertAnalyticsDisplayed.yaml new file mode 100644 index 0000000000..e7463c231e --- /dev/null +++ b/.maestro/tests/assertions/assertAnalyticsDisplayed.yaml @@ -0,0 +1,5 @@ +appId: ${APP_ID} +--- +- extendedWaitUntil: + visible: "Help improve ElementX dbg" + timeout: 10_000 diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts index 2ed497567f..aa21c1f6d1 100644 --- a/appnav/build.gradle.kts +++ b/appnav/build.gradle.kts @@ -51,13 +51,12 @@ dependencies { implementation(projects.libraries.permissions.api) implementation(projects.libraries.permissions.noop) - implementation(projects.features.verifysession.api) - implementation(projects.features.roomdetails.api) implementation(projects.tests.uitests) implementation(libs.coil) implementation(projects.services.apperror.impl) implementation(projects.services.appnavstate.api) + implementation(projects.services.analytics.api) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 62c58caadb..d23cbaabce 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.lifecycle.lifecycleScope import coil.Coil import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.lifecycle.subscribe @@ -39,6 +40,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.appnav.loggedin.LoggedInNode +import io.element.android.features.analytics.api.AnalyticsEntryPoint import io.element.android.features.createroom.api.CreateRoomEntryPoint import io.element.android.features.invitelist.api.InviteListEntryPoint import io.element.android.features.preferences.api.PreferencesEntryPoint @@ -50,6 +52,7 @@ import io.element.android.libraries.architecture.animation.rememberDefaultTransi import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.di.AppScope @@ -57,8 +60,13 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.MAIN_SPACE import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.ui.di.MatrixUIBindings +import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize @ContributesNode(AppScope::class) @@ -68,20 +76,43 @@ class LoggedInFlowNode @AssistedInject constructor( private val roomListEntryPoint: RoomListEntryPoint, private val preferencesEntryPoint: PreferencesEntryPoint, private val createRoomEntryPoint: CreateRoomEntryPoint, + private val analyticsOptInEntryPoint: AnalyticsEntryPoint, private val appNavigationStateService: AppNavigationStateService, private val verifySessionEntryPoint: VerifySessionEntryPoint, private val inviteListEntryPoint: InviteListEntryPoint, + private val analyticsService: AnalyticsService, private val coroutineScope: CoroutineScope, snackbarDispatcher: SnackbarDispatcher, ) : BackstackNode( backstack = BackStack( - initialElement = NavTarget.RoomList, + initialElement = NavTarget.SplashScreen, savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, plugins = plugins ) { + private fun observeAnalyticsState() { + analyticsService.didAskUserConsent() + .distinctUntilChanged() + .onEach { isConsentAsked -> + if (isConsentAsked) { + switchToRoomList() + } else { + switchToAnalytics() + } + } + .launchIn(lifecycleScope) + } + + private fun switchToRoomList() { + backstack.safeRoot(NavTarget.RoomList) + } + + private fun switchToAnalytics() { + backstack.safeRoot(NavTarget.AnalyticsSettings) + } + interface Callback : Plugin { fun onOpenBugReport() = Unit } @@ -105,6 +136,7 @@ class LoggedInFlowNode @AssistedInject constructor( override fun onBuilt() { super.onBuilt() + observeAnalyticsState() lifecycle.subscribe( onCreate = { plugins().forEach { it.onFlowCreated(inputs.matrixClient) } @@ -128,6 +160,9 @@ class LoggedInFlowNode @AssistedInject constructor( } sealed interface NavTarget : Parcelable { + @Parcelize + object SplashScreen : NavTarget + @Parcelize object Permanent : NavTarget @@ -150,11 +185,15 @@ class LoggedInFlowNode @AssistedInject constructor( object VerifySession : NavTarget @Parcelize - object InviteList: NavTarget + object InviteList : NavTarget + + @Parcelize + object AnalyticsSettings : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { + NavTarget.SplashScreen -> splashNode(buildContext) NavTarget.Permanent -> { createNode(buildContext) } @@ -244,6 +283,9 @@ class LoggedInFlowNode @AssistedInject constructor( .callback(callback) .build() } + NavTarget.AnalyticsSettings -> { + analyticsOptInEntryPoint.createNode(this, buildContext) + } } } @@ -260,6 +302,12 @@ class LoggedInFlowNode @AssistedInject constructor( } } + private fun splashNode(buildContext: BuildContext) = node(buildContext) { + Box(modifier = it.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + } + @Composable override fun View(modifier: Modifier) { Box(modifier = modifier) { diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt index 2fd6d2d8f7..83bda0ad82 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt @@ -25,8 +25,8 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter import io.element.android.libraries.push.api.PushService -import io.element.android.libraries.push.providers.api.Distributor -import io.element.android.libraries.push.providers.api.PushProvider +import io.element.android.libraries.pushproviders.api.Distributor +import io.element.android.libraries.pushproviders.api.PushProvider import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/analytics/api/build.gradle.kts b/features/analytics/api/build.gradle.kts new file mode 100644 index 0000000000..3d3a3b9189 --- /dev/null +++ b/features/analytics/api/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * 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-compose-library") + alias(libs.plugins.ksp) +} + +android { + namespace = "io.element.android.features.analytics.api" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(projects.libraries.designsystem) + implementation(projects.libraries.androidutils) + implementation(projects.libraries.uiStrings) + + ksp(libs.showkase.processor) +} diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsEntryPoint.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsEntryPoint.kt new file mode 100644 index 0000000000..b773754a11 --- /dev/null +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsEntryPoint.kt @@ -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.features.analytics.api + +import io.element.android.libraries.architecture.SimpleFeatureEntryPoint + +interface AnalyticsEntryPoint : SimpleFeatureEntryPoint diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsOptInEvents.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsOptInEvents.kt new file mode 100644 index 0000000000..0804f8ea44 --- /dev/null +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsOptInEvents.kt @@ -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.features.analytics.api + +sealed interface AnalyticsOptInEvents { + data class EnableAnalytics(val isEnabled: Boolean) : AnalyticsOptInEvents +} diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesPresenter.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesPresenter.kt new file mode 100644 index 0000000000..ad7538cafe --- /dev/null +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesPresenter.kt @@ -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.features.analytics.api.preferences + +import io.element.android.libraries.architecture.Presenter + +interface AnalyticsPreferencesPresenter : Presenter diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt new file mode 100644 index 0000000000..7cf0f51dfd --- /dev/null +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt @@ -0,0 +1,25 @@ +/* + * 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.features.analytics.api.preferences + +import io.element.android.features.analytics.api.AnalyticsOptInEvents + +data class AnalyticsPreferencesState( + val applicationName: String, + val isEnabled: Boolean, + val eventSink: (AnalyticsOptInEvents) -> Unit, +) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt new file mode 100644 index 0000000000..7ff4a5d1a7 --- /dev/null +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt @@ -0,0 +1,32 @@ +/* + * 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.features.analytics.api.preferences + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +open class AnalyticsPreferencesStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aAnalyticsPreferencesState().copy(isEnabled = true), + ) +} + +fun aAnalyticsPreferencesState() = AnalyticsPreferencesState( + applicationName = "ElementX", + isEnabled = false, + eventSink = {} +) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt new file mode 100644 index 0000000000..d656547631 --- /dev/null +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -0,0 +1,94 @@ +/* + * 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.features.analytics.api.preferences + +import androidx.annotation.StringRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.libraries.designsystem.LinkColor +import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory +import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.ui.strings.R as StringR + +@Composable +fun AnalyticsPreferencesView( + state: AnalyticsPreferencesState, + modifier: Modifier = Modifier, +) { + fun onEnabledChanged(isEnabled: Boolean) { + state.eventSink(AnalyticsOptInEvents.EnableAnalytics(isEnabled = isEnabled)) + } + + PreferenceCategory(title = stringResource(id = StringR.string.screen_analytics_settings_share_data)) { + val firstPart = stringResource(id = StringR.string.screen_analytics_settings_help_us_improve, state.applicationName) + val secondPart = buildAnnotatedStringWithColoredPart( + StringR.string.screen_analytics_settings_read_terms, + StringR.string.screen_analytics_settings_read_terms_content_link + ) + val title = "$firstPart\n\n$secondPart" + + PreferenceSwitch( + title = title, + isChecked = state.isEnabled, + onCheckedChange = ::onEnabledChanged + ) + } +} + +@Composable +fun buildAnnotatedStringWithColoredPart( + @StringRes fullTextRes: Int, + @StringRes coloredTextRes: Int, + color: Color = LinkColor, + underline: Boolean = true, +) = buildAnnotatedString { + val coloredPart = stringResource(coloredTextRes) + val fullText = stringResource(fullTextRes, coloredPart) + val startIndex = fullText.indexOf(coloredPart) + append(fullText) + addStyle( + style = SpanStyle( + color = color, + textDecoration = if (underline) TextDecoration.Underline else null + ), start = startIndex, end = startIndex + coloredPart.length + ) +} + +@Preview +@Composable +fun AnalyticsPreferencesViewLightPreview(@PreviewParameter(AnalyticsPreferencesStateProvider::class) state: AnalyticsPreferencesState) = + ElementPreviewLight { ContentToPreview(state) } + +@Preview +@Composable +fun AnalyticsPreferencesViewDarkPreview(@PreviewParameter(AnalyticsPreferencesStateProvider::class) state: AnalyticsPreferencesState) = + ElementPreviewDark { ContentToPreview(state) } + +@Composable +private fun ContentToPreview(state: AnalyticsPreferencesState) { + AnalyticsPreferencesView(state) +} diff --git a/features/analytics/impl/build.gradle.kts b/features/analytics/impl/build.gradle.kts new file mode 100644 index 0000000000..b72d8dbbec --- /dev/null +++ b/features/analytics/impl/build.gradle.kts @@ -0,0 +1,57 @@ +/* + * 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-compose-library") + alias(libs.plugins.anvil) + alias(libs.plugins.ksp) + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.features.analytics.impl" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(projects.anvilannotations) + anvil(projects.anvilcodegen) + implementation(projects.libraries.androidutils) + implementation(projects.libraries.core) + implementation(projects.libraries.architecture) + implementation(projects.libraries.designsystem) + implementation(projects.libraries.elementresources) + implementation(projects.libraries.uiStrings) + api(projects.features.analytics.api) + api(projects.services.analytics.api) + implementation(libs.androidx.datastore.preferences) + ksp(libs.showkase.processor) + + testImplementation(libs.test.junit) + testImplementation(libs.coroutines.test) + testImplementation(libs.molecule.runtime) + testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) + testImplementation(libs.test.mockk) + testImplementation(projects.libraries.matrix.test) + testImplementation(projects.features.analytics.test) + testImplementation(projects.features.analytics.impl) + + androidTestImplementation(libs.test.junitext) +} diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt new file mode 100644 index 0000000000..aafb3d4490 --- /dev/null +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt @@ -0,0 +1,44 @@ +/* + * 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.features.analytics.impl + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class AnalyticsOptInNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: AnalyticsOptInPresenter, +) : Node(buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + AnalyticsOptInView( + state = state, + modifier = modifier, + ) + } +} diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt new file mode 100644 index 0000000000..3cd2203dbe --- /dev/null +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt @@ -0,0 +1,56 @@ +/* + * 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.features.analytics.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.services.analytics.api.AnalyticsService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import javax.inject.Inject + +class AnalyticsOptInPresenter @Inject constructor( + private val buildMeta: BuildMeta, + private val analyticsService: AnalyticsService, +) : Presenter { + + @Composable + override fun present(): AnalyticsOptInState { + val localCoroutineScope = rememberCoroutineScope() + + fun handleEvents(event: AnalyticsOptInEvents) { + when (event) { + is AnalyticsOptInEvents.EnableAnalytics -> localCoroutineScope.setIsEnabled(event.isEnabled) + } + localCoroutineScope.launch { + analyticsService.setDidAskUserConsent() + } + } + + return AnalyticsOptInState( + applicationName = buildMeta.applicationName, + eventSink = ::handleEvents + ) + } + + private fun CoroutineScope.setIsEnabled(enabled: Boolean) = launch { + analyticsService.setUserConsent(enabled) + } +} diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt new file mode 100644 index 0000000000..a12cbaa7ea --- /dev/null +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt @@ -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.features.analytics.impl + +import io.element.android.features.analytics.api.AnalyticsOptInEvents + +data class AnalyticsOptInState( + val applicationName: String, + val eventSink: (AnalyticsOptInEvents) -> Unit +) diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt new file mode 100644 index 0000000000..e0e3450ca3 --- /dev/null +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt @@ -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. + */ + +package io.element.android.features.analytics.impl + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.core.meta.BuildMeta +import javax.inject.Inject + +open class AnalyticsOptInStateProvider @Inject constructor( +) : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aAnalyticsOptInState(), + ) +} + +fun aAnalyticsOptInState() = AnalyticsOptInState( + applicationName = "ElementX", + eventSink = {} +) diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt new file mode 100644 index 0000000000..899c217e00 --- /dev/null +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt @@ -0,0 +1,238 @@ +/* + * 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.features.analytics.impl + +import android.graphics.Typeface +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.text.style.StyleSpan +import android.text.style.UnderlineSpan +import androidx.annotation.StringRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.CheckCircle +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.libraries.designsystem.LinkColor +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TextButton +import io.element.android.libraries.designsystem.utils.LogCompositions +import io.element.android.libraries.ui.strings.R as StringR + +@Composable +fun AnalyticsOptInView( + state: AnalyticsOptInState, + modifier: Modifier = Modifier, +) { + LogCompositions(tag = "Analytics", msg = "Root") + val eventSink = state.eventSink + Box( + modifier = modifier + .fillMaxSize() + .systemBarsPadding() + .imePadding() + ) { + Column( + modifier = Modifier.padding(horizontal = 16.dp), + ) { + Column(modifier = Modifier.weight(1f)) { + Image( + painterResource(id = R.drawable.element_logo_stars), + contentDescription = null, + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(16.dp) + ) + Text( + text = stringResource(id = R.string.screen_analytics_prompt_title, state.applicationName), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 16.dp), + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + color = MaterialTheme.colorScheme.primary, + ) + Text( + text = stringResource(id = R.string.screen_analytics_prompt_help_us_improve, state.applicationName), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 16.dp), + textAlign = TextAlign.Center, + fontSize = 16.sp, + color = MaterialTheme.colorScheme.secondary, + ) + + Text( + text = buildAnnotatedStringWithColoredPart( + R.string.screen_analytics_prompt_read_terms, + R.string.screen_analytics_prompt_read_terms_content_link + ), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 16.dp), + textAlign = TextAlign.Center, + fontSize = 16.sp, + color = MaterialTheme.colorScheme.secondary, + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon(imageVector = Icons.Outlined.CheckCircle, + contentDescription = null, + tint = MaterialTheme.colorScheme.secondary) + Text( + text = stringResource(id = R.string.screen_analytics_prompt_data_usage).toAnnotatedString(), + color = MaterialTheme.colorScheme.secondary, + ) + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon(imageVector = Icons.Outlined.CheckCircle, + contentDescription = null, + tint = MaterialTheme.colorScheme.secondary) + Text( + text = stringResource(id = R.string.screen_analytics_prompt_third_party_sharing).toAnnotatedString(), + color = MaterialTheme.colorScheme.secondary, + ) + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon(imageVector = Icons.Outlined.CheckCircle, + contentDescription = null, + tint = MaterialTheme.colorScheme.secondary) + Text( + text = stringResource(id = R.string.screen_analytics_prompt_settings), + color = MaterialTheme.colorScheme.secondary, + ) + } + } + + Button( + onClick = { eventSink(AnalyticsOptInEvents.EnableAnalytics(true)) }, + modifier = Modifier.fillMaxWidth(), + ) { + Text(text = stringResource(id = StringR.string.action_enable)) + } + Spacer(modifier = Modifier.height(16.dp)) + TextButton( + onClick = { eventSink(AnalyticsOptInEvents.EnableAnalytics(false)) }, + modifier = Modifier.fillMaxWidth(), + ) { + Text(text = stringResource(id = StringR.string.action_not_now)) + } + Spacer(Modifier.height(40.dp)) + } + } +} + +fun String.toAnnotatedString(): AnnotatedString = buildAnnotatedString { + append(this@toAnnotatedString) + val spannable = SpannableString(this@toAnnotatedString) + spannable.getSpans(0, spannable.length, Any::class.java).forEach { span -> + val start = spannable.getSpanStart(span) + val end = spannable.getSpanEnd(span) + when (span) { + is StyleSpan -> when (span.style) { + Typeface.BOLD -> addStyle(SpanStyle(fontWeight = FontWeight.Bold), start, end) + Typeface.ITALIC -> addStyle(SpanStyle(fontStyle = FontStyle.Italic), start, end) + Typeface.BOLD_ITALIC -> addStyle(SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic), start, end) + } + is UnderlineSpan -> addStyle(SpanStyle(textDecoration = TextDecoration.Underline), start, end) + is ForegroundColorSpan -> addStyle(SpanStyle(color = Color(span.foregroundColor)), start, end) + } + } +} + +@Composable +fun buildAnnotatedStringWithColoredPart( + @StringRes fullTextRes: Int, + @StringRes coloredTextRes: Int, + color: Color = LinkColor, + underline: Boolean = true, +) = buildAnnotatedString { + val coloredPart = stringResource(coloredTextRes) + val fullText = stringResource(fullTextRes, coloredPart) + val startIndex = fullText.indexOf(coloredPart) + append(fullText) + addStyle( + style = SpanStyle( + color = color, + textDecoration = if (underline) TextDecoration.Underline else null + ), start = startIndex, end = startIndex + coloredPart.length + ) +} + +@Preview +@Composable +fun AnalyticsOptInViewLightPreview(@PreviewParameter(AnalyticsOptInStateProvider::class) state: AnalyticsOptInState) = ElementPreviewLight { + ContentToPreview(state) +} + +@Preview +@Composable +fun AnalyticsOptInViewDarkPreview(@PreviewParameter(AnalyticsOptInStateProvider::class) state: AnalyticsOptInState) = ElementPreviewDark { + ContentToPreview(state) +} + +@Composable +private fun ContentToPreview(state: AnalyticsOptInState) { + AnalyticsOptInView(state = state) +} diff --git a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsTracker.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/DefaultAnalyticsEntryPoint.kt similarity index 56% rename from services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsTracker.kt rename to features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/DefaultAnalyticsEntryPoint.kt index 1448a06591..6b2e26f763 100644 --- a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsTracker.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/DefaultAnalyticsEntryPoint.kt @@ -14,22 +14,19 @@ * limitations under the License. */ -package io.element.android.services.analytics.noop +package io.element.android.features.analytics.impl +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.services.analytics.api.AnalyticsTracker -import io.element.android.services.analytics.api.VectorAnalyticsEvent -import io.element.android.services.analytics.api.VectorAnalyticsScreen -import io.element.android.services.analytics.api.plan.UserProperties +import io.element.android.features.analytics.api.AnalyticsEntryPoint +import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.AppScope import javax.inject.Inject @ContributesBinding(AppScope::class) -class NoopAnalyticsTracker @Inject constructor() : AnalyticsTracker { - - override fun capture(event: VectorAnalyticsEvent) = Unit - - override fun screen(screen: VectorAnalyticsScreen) = Unit - - override fun updateUserProperties(userProperties: UserProperties) = Unit +class DefaultAnalyticsEntryPoint @Inject constructor() : AnalyticsEntryPoint { + override fun createNode(parentNode: Node, buildContext: BuildContext): Node { + return parentNode.createNode(buildContext) + } } diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/DefaultAnalyticsPreferencesPresenter.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/DefaultAnalyticsPreferencesPresenter.kt new file mode 100644 index 0000000000..6debe4c232 --- /dev/null +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/DefaultAnalyticsPreferencesPresenter.kt @@ -0,0 +1,61 @@ +/* + * 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.features.analytics.impl.preferences + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.rememberCoroutineScope +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesPresenter +import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesState +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.di.AppScope +import io.element.android.services.analytics.api.AnalyticsService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultAnalyticsPreferencesPresenter @Inject constructor( + private val analyticsService: AnalyticsService, + private val buildMeta: BuildMeta, +) : AnalyticsPreferencesPresenter { + + @Composable + override fun present(): AnalyticsPreferencesState { + val localCoroutineScope = rememberCoroutineScope() + val isEnabled = analyticsService.getUserConsent() + .collectAsState(initial = false) + + fun handleEvents(event: AnalyticsOptInEvents) { + when (event) { + is AnalyticsOptInEvents.EnableAnalytics -> localCoroutineScope.setIsEnabled(event.isEnabled) + } + } + + return AnalyticsPreferencesState( + applicationName = buildMeta.applicationName, + isEnabled = isEnabled.value, + eventSink = ::handleEvents + ) + } + + private fun CoroutineScope.setIsEnabled(enabled: Boolean) = launch { + analyticsService.setUserConsent(enabled) + } +} diff --git a/features/analytics/impl/src/main/res/drawable/element_logo_stars.xml b/features/analytics/impl/src/main/res/drawable/element_logo_stars.xml new file mode 100644 index 0000000000..d982fbedc4 --- /dev/null +++ b/features/analytics/impl/src/main/res/drawable/element_logo_stars.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + diff --git a/features/analytics/impl/src/main/res/values-de/translations.xml b/features/analytics/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..b606a6d82e --- /dev/null +++ b/features/analytics/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,10 @@ + + + "Wir erfassen und analysieren ""keine"" Account-Daten" + "Helfen Sie uns, Probleme zu identifizieren und %1$s zu verbessern, indem Sie anonyme Nutzungsdaten weitergeben." + "Sie können alle unsere Nutzerbedingungen %1$s lesen." + "hier" + "Sie können die Analyse jederzeit in den Einstellungen deaktivieren" + "Wir geben ""keine"" Informationen an Dritte weiter" + "Helfen Sie %1$s zu verbessern" + \ No newline at end of file diff --git a/features/analytics/impl/src/main/res/values-ro/translations.xml b/features/analytics/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..91beaa7c52 --- /dev/null +++ b/features/analytics/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,10 @@ + + + "Nu"" înregistrăm sau profilăm datele contului" + "Ajutați-ne să identificăm problemele și să îmbunătățim %1$s prin partajarea datelor de utilizare anonime." + "Puteți citi toate condițiile noastre %1$s." + "aici" + "Puteți dezactiva această opțiune oricând din setări" + "Nu"" împărtășim informații cu terți" + "Ajutați la îmbunătățirea %1$s" + \ No newline at end of file diff --git a/features/analytics/impl/src/main/res/values/localazy.xml b/features/analytics/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..e6b1c6419d --- /dev/null +++ b/features/analytics/impl/src/main/res/values/localazy.xml @@ -0,0 +1,10 @@ + + + "We ""don\'t"" record or profile any account data" + "Help us identify issues and improve %1$s by sharing anonymous usage data." + "You can read all our terms %1$s." + "here" + "You can turn this off anytime in settings" + "We ""don\'t"" share information with third parties" + "Help improve %1$s" + \ No newline at end of file diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt new file mode 100644 index 0000000000..01e95099d9 --- /dev/null +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt @@ -0,0 +1,68 @@ +/* + * 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.features.analytics.impl + +import app.cash.molecule.RecompositionClock +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.features.analytics.impl.preferences.DefaultAnalyticsPreferencesPresenter +import io.element.android.features.analytics.test.A_BUILD_META +import io.element.android.features.analytics.test.FakeAnalyticsService +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class AnalyticsOptInPresenterTest { + @Test + fun `present - enable`() = runTest { + val analyticsService = FakeAnalyticsService(isEnabled = false) + val presenter = AnalyticsOptInPresenter( + A_BUILD_META, + analyticsService + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(analyticsService.didAskUserConsent().first()).isFalse() + initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(true)) + assertThat(analyticsService.didAskUserConsent().first()).isTrue() + assertThat(analyticsService.getUserConsent().first()).isTrue() + } + } + + @Test + fun `present - not now`() = runTest { + val analyticsService = FakeAnalyticsService(isEnabled = false) + val presenter = AnalyticsOptInPresenter( + A_BUILD_META, + analyticsService + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(analyticsService.didAskUserConsent().first()).isFalse() + initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(false)) + assertThat(analyticsService.didAskUserConsent().first()).isTrue() + assertThat(analyticsService.getUserConsent().first()).isFalse() + } + } +} + diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt new file mode 100644 index 0000000000..c37bf09928 --- /dev/null +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt @@ -0,0 +1,78 @@ +/* + * 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.features.analytics.impl.preferences + +import app.cash.molecule.RecompositionClock +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.features.analytics.test.A_BUILD_META +import io.element.android.features.analytics.test.FakeAnalyticsService +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class AnalyticsPreferencesPresenterTest { + @Test + fun `present - initial state available`() = runTest { + val presenter = DefaultAnalyticsPreferencesPresenter( + FakeAnalyticsService(isEnabled = true, didAskUserConsent = true), + A_BUILD_META + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + skipItems(1) + val initialState = awaitItem() + assertThat(initialState.isEnabled).isTrue() + } + } + + @Test + fun `present - initial state not available`() = runTest { + val presenter = DefaultAnalyticsPreferencesPresenter( + FakeAnalyticsService(isEnabled = false, didAskUserConsent = false), + A_BUILD_META + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.isEnabled).isFalse() + } + } + + @Test + fun `present - enable and disable`() = runTest { + val presenter = DefaultAnalyticsPreferencesPresenter( + FakeAnalyticsService(isEnabled = true, didAskUserConsent = true), + A_BUILD_META + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + skipItems(1) + val initialState = awaitItem() + assertThat(initialState.isEnabled).isTrue() + initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(false)) + assertThat(awaitItem().isEnabled).isFalse() + initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(true)) + assertThat(awaitItem().isEnabled).isTrue() + } + } +} + diff --git a/features/analytics/test/build.gradle.kts b/features/analytics/test/build.gradle.kts new file mode 100644 index 0000000000..9f1796b156 --- /dev/null +++ b/features/analytics/test/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * 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") +} + +android { + namespace = "io.element.android.features.analytics.test" +} + +dependencies { + implementation(projects.services.analytics.api) + implementation(projects.libraries.core) + implementation(libs.coroutines.core) +} diff --git a/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt b/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt new file mode 100644 index 0000000000..47eba919ed --- /dev/null +++ b/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt @@ -0,0 +1,68 @@ +/* + * 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.features.analytics.test + +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.services.analytics.api.AnalyticsService +import io.element.android.services.analyticsproviders.api.AnalyticsProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeAnalyticsService( + isEnabled: Boolean = false, + didAskUserConsent: Boolean = false +): AnalyticsService { + + private var isEnabledFlow = MutableStateFlow(isEnabled) + private var didAskUserConsentFlow = MutableStateFlow(didAskUserConsent) + + override fun getAvailableAnalyticsProviders(): List = emptyList() + + override fun getUserConsent(): Flow = isEnabledFlow + + override suspend fun setUserConsent(userConsent: Boolean) { + isEnabledFlow.value = userConsent + } + + override fun didAskUserConsent(): Flow = didAskUserConsentFlow + + override suspend fun setDidAskUserConsent() { + didAskUserConsentFlow.value = true + } + + override fun getAnalyticsId(): Flow = MutableStateFlow("") + + override suspend fun setAnalyticsId(analyticsId: String) { + } + + override suspend fun onSignOut() { + } + + override fun capture(event: VectorAnalyticsEvent) { + } + + override fun screen(screen: VectorAnalyticsScreen) { + } + + override fun updateUserProperties(userProperties: UserProperties) { + } + + override fun trackError(throwable: Throwable) { + } +} diff --git a/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/TestData.kt b/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/TestData.kt new file mode 100644 index 0000000000..b23a4ab3b0 --- /dev/null +++ b/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/TestData.kt @@ -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. + */ + +package io.element.android.features.analytics.test + +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.core.meta.BuildType + +val A_BUILD_META = BuildMeta( + isDebuggable = true, + buildType = BuildType.DEBUG, + applicationName = "Element X test", + applicationId = "", + lowPrivacyLoggingEnabled = false, + versionName = "", + gitRevision = "", + gitRevisionDate = "", + gitBranchName = "", + flavorDescription = "", + flavorShortDescription = "", +) + diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index 8e96500aab..631884fe47 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) implementation(projects.features.rageshake.api) + implementation(projects.features.analytics.api) implementation(projects.libraries.matrixui) implementation(projects.features.logout.api) implementation(libs.datetime) @@ -59,6 +60,8 @@ dependencies { testImplementation(projects.features.rageshake.test) testImplementation(projects.features.rageshake.impl) testImplementation(projects.features.logout.impl) + testImplementation(projects.features.analytics.test) + testImplementation(projects.features.analytics.impl) androidTestImplementation(libs.test.junitext) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt index b1f1762dfc..2f441bcfa2 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt @@ -17,6 +17,7 @@ package io.element.android.features.preferences.impl.root import androidx.compose.runtime.Composable +import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesPresenter import io.element.android.features.logout.api.LogoutPreferencePresenter import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter import io.element.android.libraries.architecture.Async @@ -27,6 +28,7 @@ import javax.inject.Inject class PreferencesRootPresenter @Inject constructor( private val logoutPresenter: LogoutPreferencePresenter, private val rageshakePresenter: RageshakePreferencesPresenter, + private val analyticsPresenter: AnalyticsPreferencesPresenter, private val buildType: BuildType, ) : Presenter { @@ -34,10 +36,12 @@ class PreferencesRootPresenter @Inject constructor( override fun present(): PreferencesRootState { val logoutState = logoutPresenter.present() val rageshakeState = rageshakePresenter.present() + val analyticsState = analyticsPresenter.present() val showDeveloperSettings = buildType != BuildType.RELEASE return PreferencesRootState( logoutState = logoutState, rageshakeState = rageshakeState, + analyticsState = analyticsState, myUser = Async.Uninitialized, showDeveloperSettings = showDeveloperSettings ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt index 710d0e10ed..78435e7ad5 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt @@ -16,6 +16,7 @@ package io.element.android.features.preferences.impl.root +import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesState import io.element.android.features.logout.api.LogoutPreferenceState import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState import io.element.android.libraries.architecture.Async @@ -24,6 +25,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser data class PreferencesRootState( val logoutState: LogoutPreferenceState, val rageshakeState: RageshakePreferencesState, + val analyticsState: AnalyticsPreferencesState, val myUser: Async, val showDeveloperSettings: Boolean ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt index 87b5b36e0d..2c78c1f431 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt @@ -16,6 +16,7 @@ package io.element.android.features.preferences.impl.root +import io.element.android.features.analytics.api.preferences.aAnalyticsPreferencesState import io.element.android.features.logout.api.aLogoutPreferenceState import io.element.android.features.rageshake.api.preferences.aRageshakePreferencesState import io.element.android.libraries.architecture.Async @@ -23,6 +24,7 @@ import io.element.android.libraries.architecture.Async fun aPreferencesRootState() = PreferencesRootState( logoutState = aLogoutPreferenceState(), rageshakeState = aRageshakePreferencesState(), + analyticsState = aAnalyticsPreferencesState(), myUser = Async.Uninitialized, showDeveloperSettings = true ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 329aad28ee..cbdcd13a15 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.logout.api.LogoutPreferenceView import io.element.android.features.preferences.impl.user.UserPreferences +import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesView import io.element.android.features.rageshake.api.preferences.RageshakePreferencesView import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory @@ -52,6 +53,9 @@ fun PreferencesRootView( title = stringResource(id = StringR.string.common_settings) ) { UserPreferences(state.myUser) + AnalyticsPreferencesView( + state = state.analyticsState, + ) RageshakePreferencesView( state = state.rageshakeState, onOpenRageshake = onOpenRageShake, diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt index 791b0cf533..2ecd3276ee 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt @@ -20,12 +20,14 @@ import app.cash.molecule.RecompositionClock import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.features.analytics.impl.preferences.DefaultAnalyticsPreferencesPresenter +import io.element.android.features.analytics.test.A_BUILD_META +import io.element.android.features.analytics.test.FakeAnalyticsService import io.element.android.features.logout.impl.DefaultLogoutPreferencePresenter import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePreferencesPresenter import io.element.android.features.rageshake.test.rageshake.FakeRageShake import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore import io.element.android.libraries.architecture.Async -import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.matrix.test.FakeMatrixClient import kotlinx.coroutines.test.runTest import org.junit.Test @@ -35,10 +37,12 @@ class PreferencesRootPresenterTest { fun `present - initial state`() = runTest { val logoutPresenter = DefaultLogoutPreferencePresenter(FakeMatrixClient()) val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore()) + val analyticsPresenter = DefaultAnalyticsPreferencesPresenter(FakeAnalyticsService(), A_BUILD_META) val presenter = PreferencesRootPresenter( logoutPresenter, rageshakePresenter, - BuildType.DEBUG + analyticsPresenter, + A_BUILD_META.buildType ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -46,6 +50,7 @@ class PreferencesRootPresenterTest { skipItems(1) val initialState = awaitItem() assertThat(initialState.logoutState.logoutAction).isEqualTo(Async.Uninitialized) + assertThat(initialState.analyticsState.isEnabled).isFalse() assertThat(initialState.rageshakeState.isEnabled).isTrue() assertThat(initialState.rageshakeState.isSupported).isTrue() assertThat(initialState.rageshakeState.sensitivity).isEqualTo(1.0f) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7b6efb4d8f..570e0321fb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,6 +41,7 @@ showkase = "1.0.0-beta18" jsoup = "1.16.1" appyx = "1.2.0" dependencycheck = "8.2.1" +dependencyanalysis = "1.20.0" stem = "2.3.0" sqldelight = "1.5.5" telephoto = "0.4.0" @@ -148,6 +149,11 @@ otaliastudios_transcoder = "com.otaliastudios:transcoder:0.10.5" vanniktech_blurhash = "com.vanniktech:blurhash:0.1.0" telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" } +# Analytics +posthog = "com.posthog.android:posthog:2.0.3" +sentry_android = "io.sentry:sentry-android:6.17.0" +matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:main-SNAPSHOT" + # Di inject = "javax.inject:javax.inject:1" dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" } @@ -178,6 +184,7 @@ detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } ktlint = "org.jlleitschuh.gradle.ktlint:11.3.2" dependencygraph = { id = "com.savvasdalkitsis.module-dependency-graph", version.ref = "dependencygraph" } dependencycheck = { id = "org.owasp.dependencycheck", version.ref = "dependencycheck" } +dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyanalysis" } paparazzi = "app.cash.paparazzi:1.2.0" sonarqube = "org.sonarqube:4.2.0.3129" kover = "org.jetbrains.kotlinx.kover:0.6.1" diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt index 83504e7a8a..1a0eb7a93c 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt @@ -17,8 +17,8 @@ package io.element.android.libraries.push.api import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.push.providers.api.Distributor -import io.element.android.libraries.push.providers.api.PushProvider +import io.element.android.libraries.pushproviders.api.Distributor +import io.element.android.libraries.pushproviders.api.PushProvider interface PushService { // TODO Move away diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt index 28a58d1058..f57651c3a7 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt @@ -22,8 +22,8 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.push.api.PushService import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret import io.element.android.libraries.push.impl.notifications.NotificationDrawerManager -import io.element.android.libraries.push.providers.api.Distributor -import io.element.android.libraries.push.providers.api.PushProvider +import io.element.android.libraries.pushproviders.api.Distributor +import io.element.android.libraries.pushproviders.api.PushProvider import io.element.android.libraries.pushstore.api.UserPushStoreFactory import javax.inject.Inject diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt index 8bb1ddf20c..81a86c5345 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt @@ -26,7 +26,7 @@ import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData import io.element.android.libraries.push.impl.config.PushConfig import io.element.android.libraries.push.impl.log.pushLoggerTag import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest -import io.element.android.libraries.push.providers.api.PusherSubscriber +import io.element.android.libraries.pushproviders.api.PusherSubscriber import io.element.android.libraries.pushstore.api.UserPushStoreFactory import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret import io.element.android.services.toolbox.api.appname.AppNameProvider diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt index b82fac2711..de97986026 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt @@ -26,7 +26,6 @@ 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.core.ThreadId import io.element.android.libraries.push.impl.log.notificationLoggerTag -import io.element.android.services.analytics.api.AnalyticsTracker import io.element.android.services.toolbox.api.systemclock.SystemClock import timber.log.Timber import javax.inject.Inject @@ -41,7 +40,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { @Inject lateinit var notificationDrawerManager: NotificationDrawerManager //@Inject lateinit var activeSessionHolder: ActiveSessionHolder - @Inject lateinit var analyticsTracker: AnalyticsTracker + //@Inject lateinit var analyticsTracker: AnalyticsTracker @Inject lateinit var clock: SystemClock @Inject lateinit var actionIds: NotificationActionIds diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt index 9b6be804c9..1bc0ceae93 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt @@ -33,8 +33,8 @@ import io.element.android.libraries.push.impl.notifications.NotifiableEventResol import io.element.android.libraries.push.impl.notifications.NotificationActionIds import io.element.android.libraries.push.impl.notifications.NotificationDrawerManager import io.element.android.libraries.push.impl.store.DefaultPushDataStore -import io.element.android.libraries.push.providers.api.PushData -import io.element.android.libraries.push.providers.api.PushHandler +import io.element.android.libraries.pushproviders.api.PushData +import io.element.android.libraries.pushproviders.api.PushHandler import io.element.android.libraries.pushstore.api.UserPushStoreFactory import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret import kotlinx.coroutines.CoroutineScope diff --git a/libraries/pushproviders/api/build.gradle.kts b/libraries/pushproviders/api/build.gradle.kts index 999533ee64..f22fc18735 100644 --- a/libraries/pushproviders/api/build.gradle.kts +++ b/libraries/pushproviders/api/build.gradle.kts @@ -18,7 +18,7 @@ plugins { } android { - namespace = "io.element.android.libraries.push.providers.api" + namespace = "io.element.android.libraries.pushproviders.api" } dependencies { diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/Distributor.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/Distributor.kt similarity index 92% rename from libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/Distributor.kt rename to libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/Distributor.kt index 3d4d0add28..7eda80fed9 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/Distributor.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/Distributor.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.api +package io.element.android.libraries.pushproviders.api data class Distributor( val value: String, diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PushData.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushData.kt similarity index 95% rename from libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PushData.kt rename to libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushData.kt index b304d10b34..bcfa723469 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PushData.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushData.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.api +package io.element.android.libraries.pushproviders.api import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PushHandler.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushHandler.kt similarity index 92% rename from libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PushHandler.kt rename to libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushHandler.kt index 09ca420a1f..9677a63dfc 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PushHandler.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.api +package io.element.android.libraries.pushproviders.api interface PushHandler { suspend fun handle(pushData: PushData) diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PushProvider.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt similarity index 95% rename from libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PushProvider.kt rename to libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt index 4ad0179403..d71cd9ec3f 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PushProvider.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.api +package io.element.android.libraries.pushproviders.api import io.element.android.libraries.matrix.api.MatrixClient diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PusherSubscriber.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PusherSubscriber.kt similarity index 94% rename from libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PusherSubscriber.kt rename to libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PusherSubscriber.kt index 0bf0f949f3..2529e4bb96 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PusherSubscriber.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PusherSubscriber.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.api +package io.element.android.libraries.pushproviders.api import io.element.android.libraries.matrix.api.MatrixClient diff --git a/libraries/pushproviders/firebase/build.gradle.kts b/libraries/pushproviders/firebase/build.gradle.kts index efe8782075..96faa3197b 100644 --- a/libraries/pushproviders/firebase/build.gradle.kts +++ b/libraries/pushproviders/firebase/build.gradle.kts @@ -19,7 +19,7 @@ plugins { } android { - namespace = "io.element.android.libraries.push.providers.firebase" + namespace = "io.element.android.libraries.pushproviders.firebase" } anvil { diff --git a/libraries/pushproviders/firebase/src/main/AndroidManifest.xml b/libraries/pushproviders/firebase/src/main/AndroidManifest.xml index 40dc254644..fb1ed56229 100644 --- a/libraries/pushproviders/firebase/src/main/AndroidManifest.xml +++ b/libraries/pushproviders/firebase/src/main/AndroidManifest.xml @@ -21,7 +21,7 @@ android:name="firebase_analytics_collection_deactivated" android:value="true" /> diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/EnsureFcmTokenIsRetrievedUseCase.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/EnsureFcmTokenIsRetrievedUseCase.kt similarity index 96% rename from libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/EnsureFcmTokenIsRetrievedUseCase.kt rename to libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/EnsureFcmTokenIsRetrievedUseCase.kt index e859976789..d557aa0334 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/EnsureFcmTokenIsRetrievedUseCase.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/EnsureFcmTokenIsRetrievedUseCase.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.firebase +package io.element.android.libraries.pushproviders.firebase import javax.inject.Inject diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseConfig.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseConfig.kt similarity index 93% rename from libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseConfig.kt rename to libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseConfig.kt index bf35a1b18a..62081a9e56 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseConfig.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseConfig.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.firebase +package io.element.android.libraries.pushproviders.firebase object FirebaseConfig { /** diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseNewTokenHandler.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt similarity index 94% rename from libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseNewTokenHandler.kt rename to libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt index c567b03bb9..dc938bd141 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseNewTokenHandler.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.firebase +package io.element.android.libraries.pushproviders.firebase import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.push.providers.api.PusherSubscriber +import io.element.android.libraries.pushproviders.api.PusherSubscriber import io.element.android.libraries.pushstore.api.UserPushStoreFactory import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.api.toUserList diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebasePushParser.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushParser.kt similarity index 89% rename from libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebasePushParser.kt rename to libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushParser.kt index d3af7d8448..e529b7c44c 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebasePushParser.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushParser.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.firebase +package io.element.android.libraries.pushproviders.firebase -import io.element.android.libraries.push.providers.api.PushData +import io.element.android.libraries.pushproviders.api.PushData import javax.inject.Inject class FirebasePushParser @Inject constructor() { diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebasePushProvider.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt similarity index 89% rename from libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebasePushProvider.kt rename to libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt index 33f2e87ffd..5d496b39ca 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebasePushProvider.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt @@ -14,15 +14,15 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.firebase +package io.element.android.libraries.pushproviders.firebase import com.squareup.anvil.annotations.ContributesMultibinding import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.push.providers.api.Distributor -import io.element.android.libraries.push.providers.api.PushProvider -import io.element.android.libraries.push.providers.api.PusherSubscriber +import io.element.android.libraries.pushproviders.api.Distributor +import io.element.android.libraries.pushproviders.api.PushProvider +import io.element.android.libraries.pushproviders.api.PusherSubscriber import timber.log.Timber import javax.inject.Inject diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseStore.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt similarity index 95% rename from libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseStore.kt rename to libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt index f25ce08bc7..0342c67462 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseStore.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.firebase +package io.element.android.libraries.pushproviders.firebase import android.content.SharedPreferences import androidx.core.content.edit diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseTroubleshooter.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt similarity index 98% rename from libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseTroubleshooter.kt rename to libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt index 9fb9b5708d..f3efba1a16 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseTroubleshooter.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.firebase +package io.element.android.libraries.pushproviders.firebase import android.content.Context import com.google.android.gms.common.ConnectionResult diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/PushDataFirebase.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/PushDataFirebase.kt similarity index 91% rename from libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/PushDataFirebase.kt rename to libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/PushDataFirebase.kt index c244c39f02..9dedf9648f 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/PushDataFirebase.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/PushDataFirebase.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.firebase +package io.element.android.libraries.pushproviders.firebase import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.push.providers.api.PushData +import io.element.android.libraries.pushproviders.api.PushData /** * In this case, the format is: diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/VectorFirebaseMessagingService.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt similarity index 94% rename from libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/VectorFirebaseMessagingService.kt rename to libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt index 35434ceb2e..56ac65a338 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/VectorFirebaseMessagingService.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt @@ -14,13 +14,13 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.firebase +package io.element.android.libraries.pushproviders.firebase import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import io.element.android.libraries.architecture.bindings import io.element.android.libraries.core.log.logger.LoggerTag -import io.element.android.libraries.push.providers.api.PushHandler +import io.element.android.libraries.pushproviders.api.PushHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/VectorFirebaseMessagingServiceBindings.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceBindings.kt similarity index 93% rename from libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/VectorFirebaseMessagingServiceBindings.kt rename to libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceBindings.kt index e17cc922ee..e34d946957 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/VectorFirebaseMessagingServiceBindings.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceBindings.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.firebase +package io.element.android.libraries.pushproviders.firebase import com.squareup.anvil.annotations.ContributesTo import io.element.android.libraries.di.AppScope diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/push/providers/firebase/FirebasePushParserTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushParserTest.kt similarity index 96% rename from libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/push/providers/firebase/FirebasePushParserTest.kt rename to libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushParserTest.kt index 0729a9d340..0f5f1bb38f 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/push/providers/firebase/FirebasePushParserTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushParserTest.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.firebase +package io.element.android.libraries.pushproviders.firebase import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID -import io.element.android.libraries.push.providers.api.PushData +import io.element.android.libraries.pushproviders.api.PushData import io.element.android.tests.testutils.assertThrowsInDebug import org.junit.Test diff --git a/libraries/pushproviders/unifiedpush/build.gradle.kts b/libraries/pushproviders/unifiedpush/build.gradle.kts index a18baf633c..3feede0dc1 100644 --- a/libraries/pushproviders/unifiedpush/build.gradle.kts +++ b/libraries/pushproviders/unifiedpush/build.gradle.kts @@ -20,7 +20,7 @@ plugins { } android { - namespace = "io.element.android.libraries.push.providers.unifiedpush" + namespace = "io.element.android.libraries.pushproviders.unifiedpush" } anvil { diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/GuardServiceStarter.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/GuardServiceStarter.kt similarity index 93% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/GuardServiceStarter.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/GuardServiceStarter.kt index f92468d047..cd807b6d55 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/GuardServiceStarter.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/GuardServiceStarter.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush +package io.element.android.libraries.pushproviders.unifiedpush import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/KeepInternalDistributor.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/KeepInternalDistributor.kt similarity index 94% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/KeepInternalDistributor.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/KeepInternalDistributor.kt index d2e0713f74..8e90b53077 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/KeepInternalDistributor.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/KeepInternalDistributor.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush +package io.element.android.libraries.pushproviders.unifiedpush import android.content.BroadcastReceiver import android.content.Context diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/PushDataUnifiedPush.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/PushDataUnifiedPush.kt similarity index 93% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/PushDataUnifiedPush.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/PushDataUnifiedPush.kt index 44ea4704f5..f092d0167c 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/PushDataUnifiedPush.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/PushDataUnifiedPush.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush +package io.element.android.libraries.pushproviders.unifiedpush import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.push.providers.api.PushData +import io.element.android.libraries.pushproviders.api.PushData import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/RegisterUnifiedPushUseCase.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt similarity index 93% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/RegisterUnifiedPushUseCase.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt index bff6b06876..d42405ef9c 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/RegisterUnifiedPushUseCase.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt @@ -14,13 +14,13 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush +package io.element.android.libraries.pushproviders.unifiedpush import android.content.Context import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.push.providers.api.Distributor -import io.element.android.libraries.push.providers.api.PusherSubscriber +import io.element.android.libraries.pushproviders.api.Distributor +import io.element.android.libraries.pushproviders.api.PusherSubscriber import org.unifiedpush.android.connector.UnifiedPush import javax.inject.Inject diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushConfig.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt similarity index 93% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushConfig.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt index 21b4ca9a76..aad00c5bd7 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushConfig.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush +package io.element.android.libraries.pushproviders.unifiedpush object UnifiedPushConfig { /** diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushGatewayResolver.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt similarity index 93% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushGatewayResolver.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt index ff243ade21..3b2abaf38a 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushGatewayResolver.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush +package io.element.android.libraries.pushproviders.unifiedpush import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.network.RetrofitFactory -import io.element.android.libraries.push.providers.unifiedpush.network.UnifiedPushApi +import io.element.android.libraries.pushproviders.unifiedpush.network.UnifiedPushApi import kotlinx.coroutines.withContext import timber.log.Timber import java.net.URL diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushNewGatewayHandler.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt similarity index 94% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushNewGatewayHandler.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt index 3c9833010e..1a6cdb90c0 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushNewGatewayHandler.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush +package io.element.android.libraries.pushproviders.unifiedpush import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService -import io.element.android.libraries.push.providers.api.PusherSubscriber +import io.element.android.libraries.pushproviders.api.PusherSubscriber import io.element.android.libraries.pushstore.api.UserPushStoreFactory import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret import timber.log.Timber diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushParser.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParser.kt similarity index 89% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushParser.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParser.kt index 6169e1f8eb..f68cd8542b 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushParser.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParser.kt @@ -14,10 +14,10 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush +package io.element.android.libraries.pushproviders.unifiedpush import io.element.android.libraries.core.data.tryOrNull -import io.element.android.libraries.push.providers.api.PushData +import io.element.android.libraries.pushproviders.api.PushData import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import javax.inject.Inject diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushProvider.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt similarity index 92% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushProvider.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt index 6ab21e281b..6f74986ae5 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushProvider.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush +package io.element.android.libraries.pushproviders.unifiedpush import android.content.Context import com.squareup.anvil.annotations.ContributesMultibinding @@ -22,8 +22,8 @@ import io.element.android.libraries.androidutils.system.getApplicationLabel import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.push.providers.api.Distributor -import io.element.android.libraries.push.providers.api.PushProvider +import io.element.android.libraries.pushproviders.api.Distributor +import io.element.android.libraries.pushproviders.api.PushProvider import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret import org.unifiedpush.android.connector.UnifiedPush import javax.inject.Inject diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushStore.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushStore.kt similarity index 97% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushStore.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushStore.kt index 3883c3348c..d063dfce3e 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushStore.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushStore.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush +package io.element.android.libraries.pushproviders.unifiedpush import android.content.Context import android.content.SharedPreferences diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnregisterUnifiedPushUseCase.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt similarity index 96% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnregisterUnifiedPushUseCase.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt index e6eb778f7f..4efaacbf3a 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnregisterUnifiedPushUseCase.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush +package io.element.android.libraries.pushproviders.unifiedpush import android.content.Context import io.element.android.libraries.di.ApplicationContext diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/VectorUnifiedPushMessagingReceiver.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt similarity index 97% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/VectorUnifiedPushMessagingReceiver.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt index 0f065acc52..e2006f61cc 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/VectorUnifiedPushMessagingReceiver.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt @@ -14,13 +14,13 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush +package io.element.android.libraries.pushproviders.unifiedpush import android.content.Context import android.content.Intent import io.element.android.libraries.architecture.bindings import io.element.android.libraries.core.log.logger.LoggerTag -import io.element.android.libraries.push.providers.api.PushHandler +import io.element.android.libraries.pushproviders.api.PushHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/VectorUnifiedPushMessagingReceiverBindings.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverBindings.kt similarity index 93% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/VectorUnifiedPushMessagingReceiverBindings.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverBindings.kt index 603e297c6b..f7c6394d49 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/VectorUnifiedPushMessagingReceiverBindings.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverBindings.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush +package io.element.android.libraries.pushproviders.unifiedpush import com.squareup.anvil.annotations.ContributesTo import io.element.android.libraries.di.AppScope diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/network/DiscoveryResponse.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/DiscoveryResponse.kt similarity index 91% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/network/DiscoveryResponse.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/DiscoveryResponse.kt index b961da1285..4669ddc821 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/network/DiscoveryResponse.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/DiscoveryResponse.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush.network +package io.element.android.libraries.pushproviders.unifiedpush.network import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/network/DiscoveryUnifiedPush.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/DiscoveryUnifiedPush.kt similarity index 91% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/network/DiscoveryUnifiedPush.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/DiscoveryUnifiedPush.kt index b4c7345fd7..3370ad48dc 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/network/DiscoveryUnifiedPush.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/DiscoveryUnifiedPush.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush.network +package io.element.android.libraries.pushproviders.unifiedpush.network import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/network/UnifiedPushApi.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/UnifiedPushApi.kt similarity index 91% rename from libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/network/UnifiedPushApi.kt rename to libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/UnifiedPushApi.kt index e384b8353b..cd6cd7440e 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/network/UnifiedPushApi.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/UnifiedPushApi.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush.network +package io.element.android.libraries.pushproviders.unifiedpush.network import retrofit2.http.GET diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushParserTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParserTest.kt similarity index 95% rename from libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushParserTest.kt rename to libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParserTest.kt index 9996339c44..bbccc92581 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushParserTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParserTest.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package io.element.android.libraries.push.providers.unifiedpush +package io.element.android.libraries.pushproviders.unifiedpush import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID -import io.element.android.libraries.push.providers.api.PushData +import io.element.android.libraries.pushproviders.api.PushData import io.element.android.tests.testutils.assertThrowsInDebug import org.junit.Test diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 2a4558f756..5900da1416 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -134,13 +134,6 @@ "Dies ist der Anfang von %1$s." "Dies ist der Beginn dieser Konversation." "Neu" - "Wir erfassen und analysieren ""keine"" Account-Daten" - "Helfen Sie uns, Probleme zu identifizieren und %1$s zu verbessern, indem Sie anonyme Nutzungsdaten weitergeben." - "Sie können alle unsere Nutzerbedingungen %1$s lesen." - "hier" - "Sie können die Analyse jederzeit in den Einstellungen deaktivieren" - "Wir geben ""keine"" Informationen an Dritte weiter" - "Helfen Sie %1$s zu verbessern" "Teile Analyse-Daten" "Medienauswahl fehlgeschlagen, bitte versuche es erneut." "Prüfe, ob du alle aktuellen und zukünftigen Nachrichten dieses Benutzers ausblenden möchtest" diff --git a/libraries/ui-strings/src/main/res/values-ro/translations.xml b/libraries/ui-strings/src/main/res/values-ro/translations.xml index d3e67a98ef..57a783eac2 100644 --- a/libraries/ui-strings/src/main/res/values-ro/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ro/translations.xml @@ -141,13 +141,6 @@ "Acesta este începutul conversației %1$s." "Acesta este începutul acestei conversații." "Nou" - "Nu"" înregistrăm sau profilăm datele contului" - "Ajutați-ne să identificăm problemele și să îmbunătățim %1$s prin partajarea datelor de utilizare anonime." - "Puteți citi toate condițiile noastre %1$s." - "aici" - "Puteți dezactiva această opțiune oricând din setări" - "Nu"" împărtășim informații cu terți" - "Ajutați la îmbunătățirea %1$s" "Partajați datele analitice" "Selectarea fișierelor media a eșuat, încercați din nou." "Procesarea datelor media a eșuat, vă rugăm să încercați din nou." diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 2276b383ab..98b04d778b 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -145,13 +145,6 @@ "This is the beginning of %1$s." "This is the beginning of this conversation." "New" - "We ""don\'t"" record or profile any account data" - "Help us identify issues and improve %1$s by sharing anonymous usage data." - "You can read all our terms %1$s." - "here" - "You can turn this off anytime in settings" - "We ""don\'t"" share information with third parties" - "Help improve %1$s" "Share analytics data" "Failed selecting media, please try again." "Failed processing media to upload, please try again." diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index c5fc4dfd58..0a1b93acde 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -101,7 +101,8 @@ fun DependencyHandlerScope.allLibrariesImpl() { } fun DependencyHandlerScope.allServicesImpl() { - implementation(project(":services:analytics:noop")) + implementation(project(":services:analytics:impl")) + implementation(project(":services:analyticsproviders:posthog")) implementation(project(":services:apperror:impl")) implementation(project(":services:appnavstate:impl")) implementation(project(":services:toolbox:impl")) diff --git a/services/analytics/api/build.gradle.kts b/services/analytics/api/build.gradle.kts index 1f52887a11..0906ee9889 100644 --- a/services/analytics/api/build.gradle.kts +++ b/services/analytics/api/build.gradle.kts @@ -20,3 +20,8 @@ plugins { android { namespace = "io.element.android.services.analytics.api" } + +dependencies { + api(projects.services.analyticsproviders.api) + implementation(libs.coroutines.core) +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt new file mode 100644 index 0000000000..bf06542adb --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt @@ -0,0 +1,61 @@ +/* + * 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.api + +import io.element.android.services.analyticsproviders.api.AnalyticsProvider +import io.element.android.services.analyticsproviders.api.trackers.AnalyticsTracker +import io.element.android.services.analyticsproviders.api.trackers.ErrorTracker +import kotlinx.coroutines.flow.Flow + +interface AnalyticsService: AnalyticsTracker, ErrorTracker { + fun getAvailableAnalyticsProviders(): List + + /** + * Return a Flow of Boolean, true if the user has given their consent. + */ + fun getUserConsent(): Flow + + /** + * Update the user consent value. + */ + suspend fun setUserConsent(userConsent: Boolean) + + /** + * Return a Flow of Boolean, true if the user has been asked for their consent. + */ + fun didAskUserConsent(): Flow + + /** + * Store the fact that the user has been asked for their consent. + */ + suspend fun setDidAskUserConsent() + + /** + * Return a Flow of String, used for analytics Id. + */ + fun getAnalyticsId(): Flow + + /** + * Update analyticsId from the AccountData. + */ + suspend fun setAnalyticsId(analyticsId: String) + + /** + * To be called when a session is destroyed. + */ + suspend fun onSignOut() +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallEnded.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallEnded.kt deleted file mode 100644 index 63adbaff12..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallEnded.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsEvent - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered when a call has ended. - */ -data class CallEnded( - /** - * The duration of the call in milliseconds. - */ - val durationMs: Int, - /** - * Whether its a video call or not. - */ - val isVideo: Boolean, - /** - * Number of participants in the call. - */ - val numParticipants: Int, - /** - * Whether this user placed it. - */ - val placed: Boolean, -) : VectorAnalyticsEvent { - - override fun getName() = "CallEnded" - - override fun getProperties(): Map? { - return mutableMapOf().apply { - put("durationMs", durationMs) - put("isVideo", isVideo) - put("numParticipants", numParticipants) - put("placed", placed) - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallError.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallError.kt deleted file mode 100644 index 10225e25c2..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallError.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsEvent - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered when an error occurred in a call. - */ -data class CallError( - /** - * Whether its a video call or not. - */ - val isVideo: Boolean, - /** - * Number of participants in the call. - */ - val numParticipants: Int, - /** - * Whether this user placed it. - */ - val placed: Boolean, -) : VectorAnalyticsEvent { - - override fun getName() = "CallError" - - override fun getProperties(): Map? { - return mutableMapOf().apply { - put("isVideo", isVideo) - put("numParticipants", numParticipants) - put("placed", placed) - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallStarted.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallStarted.kt deleted file mode 100644 index 1ee9db0d1b..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallStarted.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsEvent - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered when a call is started. - */ -data class CallStarted( - /** - * Whether its a video call or not. - */ - val isVideo: Boolean, - /** - * Number of participants in the call. - */ - val numParticipants: Int, - /** - * Whether this user placed it. - */ - val placed: Boolean, -) : VectorAnalyticsEvent { - - override fun getName() = "CallStarted" - - override fun getProperties(): Map? { - return mutableMapOf().apply { - put("isVideo", isVideo) - put("numParticipants", numParticipants) - put("placed", placed) - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Composer.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Composer.kt deleted file mode 100644 index 00a827a166..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Composer.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsEvent - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered when the user sends a message via the composer. - */ -data class Composer( - /** - * Whether the user was using the composer inside of a thread. - */ - val inThread: Boolean, - /** - * Whether the user's composer interaction was editing a previously sent - * event. - */ - val isEditing: Boolean, - /** - * Whether the user's composer interaction was a reply to a previously - * sent event. - */ - val isReply: Boolean, - /** - * Whether this message begins a new thread or not. - */ - val startsThread: Boolean? = null, -) : VectorAnalyticsEvent { - - override fun getName() = "Composer" - - override fun getProperties(): Map? { - return mutableMapOf().apply { - put("inThread", inThread) - put("isEditing", isEditing) - put("isReply", isReply) - startsThread?.let { put("startsThread", it) } - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CreatedRoom.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CreatedRoom.kt deleted file mode 100644 index 1112e732ed..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CreatedRoom.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsEvent - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered when the user creates a room. - */ -data class CreatedRoom( - /** - * Whether the room is a DM. - */ - val isDM: Boolean, -) : VectorAnalyticsEvent { - - override fun getName() = "CreatedRoom" - - override fun getProperties(): Map? { - return mutableMapOf().apply { - put("isDM", isDM) - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Error.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Error.kt deleted file mode 100644 index a10fc46ced..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Error.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsEvent - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered when an error occurred. - */ -data class Error( - /** - * Context - client defined, can be used for debugging. - */ - val context: String? = null, - /** - * Which crypto module is the client currently using. - */ - val cryptoModule: CryptoModule? = null, - val domain: Domain, - val name: Name, -) : VectorAnalyticsEvent { - - enum class Domain { - E2EE, - TO_DEVICE, - VOIP, - } - - enum class Name { - OlmIndexError, - OlmKeysNotSentError, - OlmUnspecifiedError, - ToDeviceFailedToDecrypt, - UnknownError, - VoipIceFailed, - VoipIceTimeout, - VoipInviteTimeout, - VoipUserHangup, - VoipUserMediaFailed, - } - - enum class CryptoModule { - - /** - * Native / legacy crypto module specific to each platform. - */ - Native, - - /** - * Shared / cross-platform crypto module written in Rust. - */ - Rust, - } - - override fun getName() = "Error" - - override fun getProperties(): Map? { - return mutableMapOf().apply { - context?.let { put("context", it) } - cryptoModule?.let { put("cryptoModule", it.name) } - put("domain", domain.name) - put("name", name.name) - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Interaction.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Interaction.kt deleted file mode 100644 index f0d0a47d85..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Interaction.kt +++ /dev/null @@ -1,468 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsEvent - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered when the user clicks/taps/activates a UI element. - */ -data class Interaction( - /** - * The index of the element, if its in a list of elements. - */ - val index: Int? = null, - /** - * The manner with which the user activated the UI element. - */ - val interactionType: InteractionType? = null, - /** - * The unique name of this element. - */ - val name: Name, -) : VectorAnalyticsEvent { - - enum class Name { - /** - * User tapped the All filter in the All Chats filter tab. - */ - MobileAllChatsFilterAll, - - /** - * User tapped the Favourites filter in the All Chats filter tab. - */ - MobileAllChatsFilterFavourites, - - /** - * User tapped the People filter in the All Chats filter tab. - */ - MobileAllChatsFilterPeople, - - /** - * User tapped the Unreads filter in the All Chats filter tab. - */ - MobileAllChatsFilterUnreads, - - /** - * User disabled filters from the all chats layout settings. - */ - MobileAllChatsFiltersDisabled, - - /** - * User enabled filters from the all chats layout settings. - */ - MobileAllChatsFiltersEnabled, - - /** - * User disabled recents from the all chats layout settings. - */ - MobileAllChatsRecentsDisabled, - - /** - * User enabled recents from the all chats layout settings. - */ - MobileAllChatsRecentsEnabled, - - /** - * User tapped on Add to Home button on Room Details screen. - */ - MobileRoomAddHome, - - /** - * User tapped on Leave Room button on Room Details screen. - */ - MobileRoomLeave, - - /** - * User tapped on Threads button on Room screen. - */ - MobileRoomThreadListButton, - - /** - * User tapped on a thread summary item on Room screen. - */ - MobileRoomThreadSummaryItem, - - /** - * User validated the creation of a new space. - */ - MobileSpaceCreationValidated, - - /** - * User tapped on the filter button on ThreadList screen. - */ - MobileThreadListFilterItem, - - /** - * User selected a thread on ThreadList screen. - */ - MobileThreadListThreadItem, - - /** - * User tapped the already selected space from the space list. - */ - SpacePanelSelectedSpace, - - /** - * User tapped an unselected space from the space list -> space - * switching should occur. - */ - SpacePanelSwitchSpace, - - /** - * User tapped an unselected sub space from the space list -> space - * switching should occur. - */ - SpacePanelSwitchSubSpace, - - /** - * User clicked the create room button in the add existing room to space - * dialog in Element Web/Desktop. - */ - WebAddExistingToSpaceDialogCreateRoomButton, - - /** - * User clicked the create DM button in the home page of Element - * Web/Desktop. - */ - WebHomeCreateChatButton, - - /** - * User clicked the create room button in the home page of Element - * Web/Desktop. - */ - WebHomeCreateRoomButton, - - /** - * User clicked the explore rooms button in the home page of Element - * Web/Desktop. - */ - WebHomeExploreRoomsButton, - - /** - * User clicked on the mini avatar uploader in the home page of Element - * Web/Desktop. - */ - WebHomeMiniAvatarUploadButton, - - /** - * User clicked the explore rooms button next to the search field at the - * top of the left panel in Element Web/Desktop. - */ - WebLeftPanelExploreRoomsButton, - - /** - * User clicked on the avatar uploader in the profile settings of - * Element Web/Desktop. - */ - WebProfileSettingsAvatarUploadButton, - - /** - * User interacted with pin to sidebar checkboxes in the quick settings - * menu of Element Web/Desktop. - */ - WebQuickSettingsPinToSidebarCheckbox, - - /** - * User interacted with the theme dropdown in the quick settings menu of - * Element Web/Desktop. - */ - WebQuickSettingsThemeDropdown, - - /** - * User accessed the room invite flow using the button at the top of the - * room member list in the right panel of Element Web/Desktop. - */ - WebRightPanelMemberListInviteButton, - - /** - * User accessed room member list using the 'People' button in the right - * panel room summary card of Element Web/Desktop. - */ - WebRightPanelRoomInfoPeopleButton, - - /** - * User accessed room settings using the 'Settings' button in the right - * panel room summary card of Element Web/Desktop. - */ - WebRightPanelRoomInfoSettingsButton, - - /** - * User accessed room member list using the back button in the right - * panel user info card of Element Web/Desktop. - */ - WebRightPanelRoomUserInfoBackButton, - - /** - * User invited someone to room by clicking invite on the right panel - * user info card in Element Web/Desktop. - */ - WebRightPanelRoomUserInfoInviteButton, - - /** - * User clicked the threads 'show' filter dropdown in the threads panel - * in Element Web/Desktop. - */ - WebRightPanelThreadPanelFilterDropdown, - - /** - * User clicked the create room button in the room directory of Element - * Web/Desktop. - */ - WebRoomDirectoryCreateRoomButton, - - /** - * User clicked the Threads button in the top right of a room in Element - * Web/Desktop. - */ - WebRoomHeaderButtonsThreadsButton, - - /** - * User adjusted their favourites using the context menu on the header - * of a room in Element Web/Desktop. - */ - WebRoomHeaderContextMenuFavouriteToggle, - - /** - * User accessed the room invite flow using the context menu on the - * header of a room in Element Web/Desktop. - */ - WebRoomHeaderContextMenuInviteItem, - - /** - * User interacted with leave action in the context menu on the header - * of a room in Element Web/Desktop. - */ - WebRoomHeaderContextMenuLeaveItem, - - /** - * User accessed their room notification settings via the context menu - * on the header of a room in Element Web/Desktop. - */ - WebRoomHeaderContextMenuNotificationsItem, - - /** - * User accessed room member list using the context menu on the header - * of a room in Element Web/Desktop. - */ - WebRoomHeaderContextMenuPeopleItem, - - /** - * User accessed room settings using the context menu on the header of a - * room in Element Web/Desktop. - */ - WebRoomHeaderContextMenuSettingsItem, - - /** - * User clicked the create DM button in the + context menu of the room - * list header in Element Web/Desktop. - */ - WebRoomListHeaderPlusMenuCreateChatItem, - - /** - * User clicked the create room button in the + context menu of the room - * list header in Element Web/Desktop. - */ - WebRoomListHeaderPlusMenuCreateRoomItem, - - /** - * User clicked the explore rooms button in the + context menu of the - * room list header in Element Web/Desktop. - */ - WebRoomListHeaderPlusMenuExploreRoomsItem, - - /** - * User adjusted their favourites using the context menu on a room tile - * in the room list in Element Web/Desktop. - */ - WebRoomListRoomTileContextMenuFavouriteToggle, - - /** - * User accessed the room invite flow using the context menu on a room - * tile in the room list in Element Web/Desktop. - */ - WebRoomListRoomTileContextMenuInviteItem, - - /** - * User interacted with leave action in the context menu on a room tile - * in the room list in Element Web/Desktop. - */ - WebRoomListRoomTileContextMenuLeaveItem, - - /** - * User accessed room settings using the context menu on a room tile in - * the room list in Element Web/Desktop. - */ - WebRoomListRoomTileContextMenuSettingsItem, - - /** - * User accessed their room notification settings via the context menu - * on a room tile in the room list in Element Web/Desktop. - */ - WebRoomListRoomTileNotificationsMenu, - - /** - * User clicked the create DM button in the + context menu of the rooms - * sublist in Element Web/Desktop. - */ - WebRoomListRoomsSublistPlusMenuCreateChatItem, - - /** - * User clicked the create room button in the + context menu of the - * rooms sublist in Element Web/Desktop. - */ - WebRoomListRoomsSublistPlusMenuCreateRoomItem, - - /** - * User clicked the explore rooms button in the + context menu of the - * rooms sublist in Element Web/Desktop. - */ - WebRoomListRoomsSublistPlusMenuExploreRoomsItem, - - /** - * User clicked on the button to return to the user onboarding list in - * the room list in Element Web/Desktop. - */ - WebRoomListUserOnboardingButton, - - /** - * User clicked on the button to close the user onboarding button in the - * room list in Element Web/Desktop. - */ - WebRoomListUserOnboardingIgnoreButton, - - /** - * User interacted with leave action in the general tab of the room - * settings dialog in Element Web/Desktop. - */ - WebRoomSettingsLeaveButton, - - /** - * User interacted with the prompt to create a new room when adjusting - * security settings in an existing room in Element Web/Desktop. - */ - WebRoomSettingsSecurityTabCreateNewRoomButton, - - /** - * User clicked a thread summary in the timeline of a room in Element - * Web/Desktop. - */ - WebRoomTimelineThreadSummaryButton, - - /** - * User interacted with the theme radio selector in the Appearance tab - * of Settings in Element Web/Desktop. - */ - WebSettingsAppearanceTabThemeSelector, - - /** - * User interacted with the pre-built space checkboxes in the Sidebar - * tab of Settings in Element Web/Desktop. - */ - WebSettingsSidebarTabSpacesCheckbox, - - /** - * User clicked the explore rooms button in the context menu of a space - * in Element Web/Desktop. - */ - WebSpaceContextMenuExploreRoomsItem, - - /** - * User clicked the home button in the context menu of a space in - * Element Web/Desktop. - */ - WebSpaceContextMenuHomeItem, - - /** - * User clicked the new room button in the context menu of a space in - * Element Web/Desktop. - */ - WebSpaceContextMenuNewRoomItem, - - /** - * User clicked the new room button in the context menu on the space - * home in Element Web/Desktop. - */ - WebSpaceHomeCreateRoomButton, - - /** - * User clicked the back button on a Thread view going back to the - * Threads Panel of Element Web/Desktop. - */ - WebThreadViewBackButton, - - /** - * User selected a thread in the Threads panel in Element Web/Desktop. - */ - WebThreadsPanelThreadItem, - - /** - * User clicked the theme toggle button in the user menu of Element - * Web/Desktop. - */ - WebUserMenuThemeToggleButton, - - /** - * User clicked on the send DM CTA in the header of the new user - * onboarding page in Element Web/Desktop. - */ - WebUserOnboardingHeaderSendDm, - - /** - * User clicked on the action of the download apps task on the new user - * onboarding page in Element Web/Desktop. - */ - WebUserOnboardingTaskDownloadApps, - - /** - * User clicked on the action of the enable notifications task on the - * new user onboarding page in Element Web/Desktop. - */ - WebUserOnboardingTaskEnableNotifications, - - /** - * User clicked on the action of the find people task on the new user - * onboarding page in Element Web/Desktop. - */ - WebUserOnboardingTaskSendDm, - - /** - * User clicked on the action of the your profile task on the new user - * onboarding page in Element Web/Desktop. - */ - WebUserOnboardingTaskSetupProfile, - } - - enum class InteractionType { - Keyboard, - Pointer, - Touch, - } - - override fun getName() = "Interaction" - - override fun getProperties(): Map? { - return mutableMapOf().apply { - index?.let { put("index", it) } - interactionType?.let { put("interactionType", it.name) } - put("name", name.name) - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/JoinedRoom.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/JoinedRoom.kt deleted file mode 100644 index d7c86629c7..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/JoinedRoom.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsEvent - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered when the user joins a room. - */ -data class JoinedRoom( - /** - * Whether the room is a DM. - */ - val isDM: Boolean, - /** - * Whether the room is a Space. - */ - val isSpace: Boolean, - /** - * The size of the room. - */ - val roomSize: RoomSize, - /** - * The trigger for a room being joined if known. - */ - val trigger: Trigger? = null, -) : VectorAnalyticsEvent { - - enum class Trigger { - /** - * Room joined via an invite. - */ - Invite, - - /** - * Room joined via link. - */ - MobilePermalink, - - /** - * Room joined via a push/desktop notification. - */ - Notification, - - /** - * Room joined via the public rooms directory. - */ - RoomDirectory, - - /** - * Room joined via its preview. - */ - RoomPreview, - - /** - * Room joined via the /join slash command. - */ - SlashCommand, - - /** - * Room joined via the space hierarchy view. - */ - SpaceHierarchy, - - /** - * Room joined via a timeline pill or link in another room. - */ - Timeline, - } - - enum class RoomSize { - ElevenToOneHundred, - MoreThanAThousand, - One, - OneHundredAndOneToAThousand, - ThreeToTen, - Two, - } - - override fun getName() = "JoinedRoom" - - override fun getProperties(): Map? { - return mutableMapOf().apply { - put("isDM", isDM) - put("isSpace", isSpace) - put("roomSize", roomSize.name) - trigger?.let { put("trigger", it.name) } - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/MobileScreen.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/MobileScreen.kt deleted file mode 100644 index a474dd1faf..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/MobileScreen.kt +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsScreen - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered when the user changed screen on Element Android/iOS. - */ -data class MobileScreen( - /** - * How long the screen was displayed for in milliseconds. - */ - val durationMs: Int? = null, - val screenName: ScreenName, -) : VectorAnalyticsScreen { - - enum class ScreenName { - /** - * The screen that displays the user's breadcrumbs. - */ - Breadcrumbs, - - /** - * The screen shown to create a new (non-direct) room. - */ - CreateRoom, - - /** - * The screen shown to create a new space. - */ - CreateSpace, - - /** - * The confirmation screen shown before deactivating an account. - */ - DeactivateAccount, - - /** - * The tab on mobile that displays the dialpad. - */ - Dialpad, - - /** - * The Favourites tab on mobile that lists your favourite people/rooms. - */ - Favourites, - - /** - * The form for the forgot password use case. - */ - ForgotPassword, - - /** - * Legacy: The screen that shows information about a specific group. - */ - Group, - - /** - * The Home tab on iOS | possibly the same on Android? - */ - Home, - - /** - * The screen shown to share a link to download the app. - */ - InviteFriends, - - /** - * Room accessed via space bottom sheet list. - */ - Invites, - - /** - * The screen that displays the login flow (when the user already has an - * account). - */ - Login, - - /** - * Legacy: The screen that shows all groups/communities you have joined. - */ - MyGroups, - - /** - * The People tab on mobile that lists all the DM rooms you have joined. - */ - People, - - /** - * The screen that displays the registration flow (when the user wants - * to create an account). - */ - Register, - - /** - * The screen that displays the messages and events received in a room. - */ - Room, - - /** - * The room addresses screen shown from the Room Details screen. - */ - RoomAddresses, - - /** - * The screen shown when tapping the name of a room from the Room - * screen. - */ - RoomDetailss, - - /** - * The screen that lists public rooms for you to discover. - */ - RoomDirectory, - - /** - * The screen that lists all the user's rooms and let them filter the - * rooms. - */ - RoomFilter, - - /** - * The screen that displays the list of members that are part of a room. - */ - RoomMembers, - - /** - * The notifications settings screen shown from the Room Details screen. - */ - RoomNotifications, - - /** - * The roles permissions screen shown from the Room Details screen. - */ - RoomPermissions, - - /** - * Screen that displays room preview if user hasn't joined yet. - */ - RoomPreview, - - /** - * The screen that allows you to search for messages/files in a specific - * room. - */ - RoomSearch, - - /** - * The settings screen shown from the Room Details screen. - */ - RoomSettings, - - /** - * The screen that allows you to see all of the files sent in a specific - * room. - */ - RoomUploads, - - /** - * The Rooms tab on mobile that lists all the (non-direct) rooms you've - * joined. - */ - Rooms, - - /** - * The Files tab shown in the global search screen on Mobile. - */ - SearchFiles, - - /** - * The Messages tab shown in the global search screen on Mobile. - */ - SearchMessages, - - /** - * The People tab shown in the global search screen on Mobile. - */ - SearchPeople, - - /** - * The Rooms tab shown in the global search screen on Mobile. - */ - SearchRooms, - - /** - * The global settings screen shown in the app. - */ - Settings, - - /** - * The advanced settings screen (developer mode, rageshake, push - * notification rules). - */ - SettingsAdvanced, - - /** - * The settings screen to change the default notification options. - */ - SettingsDefaultNotifications, - - /** - * The settings screen with general profile settings. - */ - SettingsGeneral, - - /** - * The Help and About screen. - */ - SettingsHelp, - - /** - * The settings screen with list of the ignored users. - */ - SettingsIgnoredUsers, - - /** - * The experimental features settings screen. - */ - SettingsLabs, - - /** - * The settings screen with legals information. - */ - SettingsLegals, - - /** - * The settings screen to manage notification mentions and keywords. - */ - SettingsMentionsAndKeywords, - - /** - * The notifications settings screen. - */ - SettingsNotifications, - - /** - * The preferences screen (theme, language, editor preferences, etc. - */ - SettingsPreferences, - - /** - * The global security settings screen. - */ - SettingsSecurity, - - /** - * The calls settings screen. - */ - SettingsVoiceVideo, - - /** - * The sidebar shown on mobile with spaces, settings etc. - */ - Sidebar, - - /** - * Room accessed via space bottom sheet list. - */ - SpaceBottomSheet, - - /** - * Screen that displays the list of rooms and spaces of a space. - */ - SpaceExploreRooms, - - /** - * Screen that displays the list of members of a space. - */ - SpaceMembers, - - /** - * The bottom sheet that list all space options. - */ - SpaceMenu, - - /** - * The screen shown to create a new direct room. - */ - StartChat, - - /** - * The screen shown to select which room directory you'd like to use. - */ - SwitchDirectory, - - /** - * Screen that displays list of threads for a room. - */ - ThreadList, - - /** - * A screen that shows information about a room member. - */ - User, - - /** - * The splash screen. - */ - Welcome, - } - - override fun getName() = screenName.name - - override fun getProperties(): Map? { - return mutableMapOf().apply { - durationMs?.let { put("durationMs", it) } - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PerformanceTimer.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PerformanceTimer.kt deleted file mode 100644 index 8296ae783f..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PerformanceTimer.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsEvent - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered after timing an operation in the app. - */ -data class PerformanceTimer( - /** - * Client defined, can be used for debugging. - */ - val context: String? = null, - /** - * Client defined, an optional value to indicate how many items were - * handled during the operation. - */ - val itemCount: Int? = null, - /** - * The timer that is being reported. - */ - val name: Name, - /** - * The time reported by the timer in milliseconds. - */ - val timeMs: Int, -) : VectorAnalyticsEvent { - - enum class Name { - /** - * The time spent parsing the response from an initial /sync request. In - * this case, `itemCount` should contain the number of joined rooms. - */ - InitialSyncParsing, - - /** - * The time spent waiting for a response to an initial /sync request. In - * this case, `itemCount` should contain the number of joined rooms. - */ - InitialSyncRequest, - - /** - * The time taken to display an event in the timeline that was opened - * from a notification. - */ - NotificationsOpenEvent, - - /** - * The duration of a regular /sync request when resuming the app. In - * this case, `itemCount` should contain the number of joined rooms in - * the response. - */ - StartupIncrementalSync, - - /** - * The duration of an initial /sync request during startup (if the store - * has been wiped). In this case, `itemCount` should contain the number - * of joined rooms. - */ - StartupInitialSync, - - /** - * How long the app launch screen is displayed for. - */ - StartupLaunchScreen, - - /** - * The time to preload data in the MXStore on iOS. In this case, - * `itemCount` should contain the number of rooms in the store. - */ - StartupStorePreload, - - /** - * The time to load all data from the store (including - * StartupStorePreload time). In this case, `itemCount` should contain - * the number of rooms loaded into the session - */ - StartupStoreReady, - } - - override fun getName() = "PerformanceTimer" - - override fun getProperties(): Map? { - return mutableMapOf().apply { - context?.let { put("context", it) } - itemCount?.let { put("itemCount", it) } - put("name", name.name) - put("timeMs", timeMs) - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PermissionChanged.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PermissionChanged.kt deleted file mode 100644 index 9f93078f26..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PermissionChanged.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsEvent - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered when the user changes a permission status. - */ -data class PermissionChanged( - /** - * Whether the permission has been granted by the user. - */ - val granted: Boolean, - /** - * The name of the permission. - */ - val permission: Permission, -) : VectorAnalyticsEvent { - - enum class Permission { - /** - * Permissions related to sending notifications have changed. - */ - Notification, - } - - override fun getName() = "PermissionChanged" - - override fun getProperties(): Map? { - return mutableMapOf().apply { - put("granted", granted) - put("permission", permission.name) - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Signup.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Signup.kt deleted file mode 100644 index 00b7ef7b60..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Signup.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsEvent - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered once onboarding has completed, but only if the user registered a - * new account. - */ -data class Signup( - /** - * The type of authentication that was used to sign up. - */ - val authenticationType: AuthenticationType, -) : VectorAnalyticsEvent { - - enum class AuthenticationType { - /** - * Social login using Apple. - */ - Apple, - - /** - * Social login using Facebook. - */ - Facebook, - - /** - * Social login using GitHub. - */ - GitHub, - - /** - * Social login using GitLab. - */ - GitLab, - - /** - * Social login using Google. - */ - Google, - - /** - * Registration using some other mechanism such as fallback. - */ - Other, - - /** - * Registration with a username and password. - */ - Password, - - /** - * Registration using another SSO provider. - */ - SSO, - } - - override fun getName() = "Signup" - - override fun getProperties(): Map? { - return mutableMapOf().apply { - put("authenticationType", authenticationType.name) - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/SlashCommand.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/SlashCommand.kt deleted file mode 100644 index de0af607b0..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/SlashCommand.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsEvent - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered when the user runs a slash command in their composer. - */ -data class SlashCommand( - /** - * The name of this command. - */ - val command: Command, -) : VectorAnalyticsEvent { - - enum class Command { - Invite, - Part, - } - - override fun getName() = "SlashCommand" - - override fun getProperties(): Map? { - return mutableMapOf().apply { - put("command", command.name) - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UnauthenticatedError.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UnauthenticatedError.kt deleted file mode 100644 index e235fa994c..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UnauthenticatedError.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsEvent - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered when the user becomes unauthenticated without actually clicking - * sign out(E.g. Due to expiry of an access token without a way to refresh). - */ -data class UnauthenticatedError( - /** - * The error code as defined in matrix spec. The source of this error is - * from the homeserver. - */ - val errorCode: ErrorCode, - /** - * The reason for the error. The source of this error is from the - * homeserver, the reason can vary and is subject to change so there is - * no enum of possible values. - */ - val errorReason: String, - /** - * Whether the auth mechanism is refresh-token-based. - */ - val refreshTokenAuth: Boolean, - /** - * Whether a soft logout or hard logout was triggered. - */ - val softLogout: Boolean, -) : VectorAnalyticsEvent { - - enum class ErrorCode { - M_FORBIDDEN, - M_UNKNOWN, - M_UNKNOWN_TOKEN, - } - - override fun getName() = "UnauthenticatedError" - - override fun getProperties(): Map? { - return mutableMapOf().apply { - put("errorCode", errorCode.name) - put("errorReason", errorReason) - put("refreshTokenAuth", refreshTokenAuth) - put("softLogout", softLogout) - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UserProperties.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UserProperties.kt deleted file mode 100644 index cd72f05af1..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UserProperties.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.api.plan - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * The user properties to apply when identifying. This is not an event - * definition. These properties must all be device independent. - */ -data class UserProperties( - /** - * The active filter in the All Chats screen. - */ - val allChatsActiveFilter: AllChatsActiveFilter? = null, - /** - * The selected messaging use case during the onboarding flow. - */ - val ftueUseCaseSelection: FtueUseCaseSelection? = null, - /** - * Number of joined rooms the user has favourited. - */ - val numFavouriteRooms: Int? = null, - /** - * Number of spaces (and sub-spaces) the user is joined to. - */ - val numSpaces: Int? = null, -) { - - enum class FtueUseCaseSelection { - /** - * The third option, Communities. - */ - CommunityMessaging, - - /** - * The first option, Friends and family. - */ - PersonalMessaging, - - /** - * The footer option to skip the question. - */ - Skip, - - /** - * The second option, Teams. - */ - WorkMessaging, - } - - enum class AllChatsActiveFilter { - - /** - * Filters are activated and All is selected. - */ - All, - - /** - * Filters are activated and Favourites is selected. - */ - Favourites, - - /** - * Filters are activated and People is selected. - */ - People, - - /** - * Filters are activated and Unreads is selected. - */ - Unreads, - } - - fun getProperties(): Map? { - return mutableMapOf().apply { - allChatsActiveFilter?.let { put("allChatsActiveFilter", it.name) } - ftueUseCaseSelection?.let { put("ftueUseCaseSelection", it.name) } - numFavouriteRooms?.let { put("numFavouriteRooms", it) } - numSpaces?.let { put("numSpaces", it) } - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/ViewRoom.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/ViewRoom.kt deleted file mode 100644 index 7477b83b13..0000000000 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/ViewRoom.kt +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (c) 2021 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.api.plan - -import io.element.android.services.analytics.api.VectorAnalyticsEvent - -// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT -// https://github.com/matrix-org/matrix-analytics-events/ - -/** - * Triggered when the user changes rooms. - */ -data class ViewRoom( - /** - * active space when user navigated to the room. - */ - val activeSpace: ActiveSpace? = null, - /** - * Whether the room is a DM. - */ - val isDM: Boolean? = null, - /** - * Whether the room is a Space. - */ - val isSpace: Boolean? = null, - /** - * The reason for the room change if known. - */ - val trigger: Trigger? = null, - /** - * Whether the interaction was performed via the keyboard input. - */ - val viaKeyboard: Boolean? = null, -) : VectorAnalyticsEvent { - - enum class Trigger { - /** - * Room accessed due to being just created. - */ - Created, - - /** - * Room switched due to user interacting with a message search result. - */ - MessageSearch, - - /** - * Room switched due to user selecting a user to go to a DM with. - */ - MessageUser, - - /** - * Room accessed via space explore. - */ - MobileExploreRooms, - - /** - * Room switched due to user interacting with a file search result. - */ - MobileFileSearch, - - /** - * Room accessed via interacting with the incall screen. - */ - MobileInCall, - - /** - * Room accessed during external sharing. - */ - MobileLinkShare, - - /** - * Room accessed via link. - */ - MobilePermalink, - - /** - * Room accessed via interacting with direct chat item in the room - * contact detail screen. - */ - MobileRoomMemberDetail, - - /** - * Room accessed via preview. - */ - MobileRoomPreview, - - /** - * Room switched due to user interacting with a room search result. - */ - MobileRoomSearch, - - /** - * Room accessed via interacting with direct chat item in the search - * contact detail screen. - */ - MobileSearchContactDetail, - - /** - * Room accessed via space bottom sheet list. - */ - MobileSpaceBottomSheet, - - /** - * Room accessed via interacting with direct chat item in the space - * contact detail screen. - */ - MobileSpaceMemberDetail, - - /** - * Room accessed via space members list. - */ - MobileSpaceMembers, - - /** - * Space accessed via interacting with the space menu. - */ - MobileSpaceMenu, - - /** - * Space accessed via interacting with a space settings menu item. - */ - MobileSpaceSettings, - - /** - * Room accessed via a push/desktop notification. - */ - Notification, - - /** - * Room accessed via the predecessor link at the top of the upgraded - * room. - */ - Predecessor, - - /** - * Room accessed via the public rooms directory. - */ - RoomDirectory, - - /** - * Room accessed via the room list. - */ - RoomList, - - /** - * Room accessed via a shortcut. - */ - Shortcut, - - /** - * Room accessed via a slash command in Element Web/Desktop like /goto. - */ - SlashCommand, - - /** - * Room accessed via the space hierarchy view. - */ - SpaceHierarchy, - - /** - * Room accessed via a timeline pill or link in another room. - */ - Timeline, - - /** - * Room accessed via a tombstone at the bottom of a predecessor room. - */ - Tombstone, - - /** - * Room switched due to user interacting with incoming verification - * request. - */ - VerificationRequest, - - /** - * Room switched due to accepting a call in a different room in Element - * Web/Desktop. - */ - WebAcceptCall, - - /** - * Room switched due to making a call via the dial pad in Element - * Web/Desktop. - */ - WebDialPad, - - /** - * Room accessed via interacting with the floating call or Jitsi PIP in - * Element Web/Desktop. - */ - WebFloatingCallWindow, - - /** - * Room accessed via the shortcut in Element Web/Desktop's forward - * modal. - */ - WebForwardShortcut, - - /** - * Room accessed via the Element Web/Desktop horizontal breadcrumbs at - * the top of the room list. - */ - WebHorizontalBreadcrumbs, - - /** - * Room accessed via an Element Web/Desktop keyboard shortcut like go to - * next room with unread messages. - */ - WebKeyboardShortcut, - - /** - * Room accessed via Element Web/Desktop's notification panel. - */ - WebNotificationPanel, - - /** - * Room accessed via the predecessor link in Settings > Advanced in - * Element Web/Desktop. - */ - WebPredecessorSettings, - - /** - * Room accessed via clicking on a notifications badge on a room list - * sublist in Element Web/Desktop. - */ - WebRoomListNotificationBadge, - - /** - * Room switched due to the user changing space in Element Web/Desktop. - */ - WebSpaceContextSwitch, - - /** - * Room accessed via clicking on the notifications badge on the - * currently selected space in Element Web/Desktop. - */ - WebSpacePanelNotificationBadge, - - /** - * Room accessed via Element Web/Desktop's Unified Search modal. - */ - WebUnifiedSearch, - - /** - * Room accessed via the Element Web/Desktop vertical breadcrumb hover - * menu. - */ - WebVerticalBreadcrumbs, - - /** - * Room switched due to widget interaction. - */ - Widget, - } - - enum class ActiveSpace { - - /** - * Active space is Home. - */ - Home, - - /** - * Active space is a meta space. - */ - Meta, - - /** - * Active space is a private space. - */ - Private, - - /** - * Active space is a public space. - */ - Public, - } - - override fun getName() = "ViewRoom" - - override fun getProperties(): Map? { - return mutableMapOf().apply { - activeSpace?.let { put("activeSpace", it.name) } - isDM?.let { put("isDM", it) } - isSpace?.let { put("isSpace", it) } - trigger?.let { put("trigger", it.name) } - viaKeyboard?.let { put("viaKeyboard", it) } - }.takeIf { it.isNotEmpty() } - } -} diff --git a/services/analytics/impl/build.gradle.kts b/services/analytics/impl/build.gradle.kts new file mode 100644 index 0000000000..5dd72d77bd --- /dev/null +++ b/services/analytics/impl/build.gradle.kts @@ -0,0 +1,46 @@ +/* + * 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-compose-library") + alias(libs.plugins.anvil) + alias(libs.plugins.ksp) + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.services.analytics.impl" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + anvil(projects.anvilcodegen) + + implementation(projects.libraries.androidutils) + implementation(projects.libraries.core) + implementation(projects.libraries.architecture) + implementation(projects.libraries.sessionStorage.api) + + api(projects.services.analyticsproviders.api) + api(projects.services.analytics.api) + implementation(libs.androidx.datastore.preferences) + + testImplementation(libs.coroutines.test) + testImplementation(libs.test.mockk) +} diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt new file mode 100644 index 0000000000..6b783964c5 --- /dev/null +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt @@ -0,0 +1,158 @@ +/* + * 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.impl + +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.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.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 +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import timber.log.Timber +import javax.inject.Inject + +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class, boundType = AnalyticsService::class) +class DefaultAnalyticsService @Inject constructor( + private val analyticsProviders: Set<@JvmSuppressWildcards AnalyticsProvider>, + private val analyticsStore: AnalyticsStore, +// private val lateInitUserPropertiesFactory: LateInitUserPropertiesFactory, + private val coroutineScope: CoroutineScope, + private val sessionObserver: SessionObserver, +) : AnalyticsService, SessionListener { + // Cache for the store values + private var userConsent: Boolean? = null + + // Cache for the properties to send + private var pendingUserProperties: UserProperties? = null + + init { + observeUserConsent() + observeSessions() + } + + override fun getAvailableAnalyticsProviders(): List { + return analyticsProviders.sortedBy { it.index } + } + + override fun getUserConsent(): Flow { + return analyticsStore.userConsentFlow + } + + override suspend fun setUserConsent(userConsent: Boolean) { + Timber.tag(analyticsTag.value).d("setUserConsent($userConsent)") + analyticsStore.setUserConsent(userConsent) + } + + override fun didAskUserConsent(): Flow { + return analyticsStore.didAskUserConsentFlow + } + + override suspend fun setDidAskUserConsent() { + Timber.tag(analyticsTag.value).d("setDidAskUserConsent()") + analyticsStore.setDidAskUserConsent() + } + + override fun getAnalyticsId(): Flow { + return analyticsStore.analyticsIdFlow + } + + override suspend fun setAnalyticsId(analyticsId: String) { + Timber.tag(analyticsTag.value).d("setAnalyticsId($analyticsId)") + analyticsStore.setAnalyticsId(analyticsId) + } + + override suspend fun onSignOut() { + // stop all providers + analyticsProviders.onEach { it.stop() } + } + + override suspend fun onSessionCreated(userId: String) { + // Nothing to do + } + + override suspend fun onSessionDeleted(userId: String) { + // Delete the store + analyticsStore.reset() + } + + private fun observeUserConsent() { + getUserConsent() + .onEach { consent -> + Timber.tag(analyticsTag.value).d("User consent updated to $consent") + userConsent = consent + initOrStop() + } + .launchIn(coroutineScope) + } + + private fun observeSessions() { + sessionObserver.addListener(this) + } + + private fun initOrStop() { + userConsent?.let { _userConsent -> + when (_userConsent) { + true -> { + pendingUserProperties?.let { + analyticsProviders.onEach { provider -> provider.updateUserProperties(it) } + pendingUserProperties = null + } + } + false -> {} + } + } + } + + override fun capture(event: VectorAnalyticsEvent) { + Timber.tag(analyticsTag.value).d("capture($event)") + if (userConsent == true) { + analyticsProviders.onEach { it.capture(event) } + } + } + + override fun screen(screen: VectorAnalyticsScreen) { + Timber.tag(analyticsTag.value).d("screen($screen)") + if (userConsent == true) { + analyticsProviders.onEach { it.screen(screen) } + } + } + + override fun updateUserProperties(userProperties: UserProperties) { + if (userConsent == true) { + analyticsProviders.onEach { it.updateUserProperties(userProperties) } + } else { + pendingUserProperties = userProperties + } + } + + override fun trackError(throwable: Throwable) { + if (userConsent == true) { + analyticsProviders.onEach { it.trackError(throwable) } + } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsEvent.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/log/AnalyticsLoggerTag.kt similarity index 79% rename from services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsEvent.kt rename to services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/log/AnalyticsLoggerTag.kt index 49534505c5..f323ac0a79 100644 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsEvent.kt +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/log/AnalyticsLoggerTag.kt @@ -14,9 +14,8 @@ * limitations under the License. */ -package io.element.android.services.analytics.api +package io.element.android.services.analytics.impl.log -interface VectorAnalyticsEvent { - fun getName(): String - fun getProperties(): Map? -} +import io.element.android.libraries.core.log.logger.LoggerTag + +val analyticsTag = LoggerTag("Analytics") diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/store/AnalyticsStore.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/store/AnalyticsStore.kt new file mode 100644 index 0000000000..476fd2a38a --- /dev/null +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/store/AnalyticsStore.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021 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.impl.store + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.core.bool.orFalse +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +/** + * Also accessed via reflection by the instrumentation tests @see [im.vector.app.ClearCurrentSessionRule]. + */ +private val Context.dataStore: DataStore by preferencesDataStore(name = "vector_analytics") + +/** + * Local storage for: + * - user consent (Boolean); + * - did ask user consent (Boolean); + * - analytics Id (String). + */ +class AnalyticsStore @Inject constructor( + @ApplicationContext private val context: Context +) { + private val userConsent = booleanPreferencesKey("user_consent") + private val didAskUserConsent = booleanPreferencesKey("did_ask_user_consent") + private val analyticsId = stringPreferencesKey("analytics_id") + + val userConsentFlow: Flow = context.dataStore.data + .map { preferences -> preferences[userConsent].orFalse() } + .distinctUntilChanged() + + val didAskUserConsentFlow: Flow = context.dataStore.data + .map { preferences -> preferences[didAskUserConsent].orFalse() } + .distinctUntilChanged() + + val analyticsIdFlow: Flow = context.dataStore.data + .map { preferences -> preferences[analyticsId].orEmpty() } + .distinctUntilChanged() + + suspend fun setUserConsent(newUserConsent: Boolean) { + context.dataStore.edit { settings -> + settings[userConsent] = newUserConsent + } + } + + suspend fun setDidAskUserConsent(newValue: Boolean = true) { + context.dataStore.edit { settings -> + settings[didAskUserConsent] = newValue + } + } + + suspend fun setAnalyticsId(newAnalyticsId: String) { + context.dataStore.edit { settings -> + settings[analyticsId] = newAnalyticsId + } + } + + suspend fun reset() { + context.dataStore.edit { + it.clear() + } + } +} diff --git a/services/analyticsproviders/api/build.gradle.kts b/services/analyticsproviders/api/build.gradle.kts new file mode 100644 index 0000000000..40657e9a77 --- /dev/null +++ b/services/analyticsproviders/api/build.gradle.kts @@ -0,0 +1,26 @@ +/* + * 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") +} + +android { + namespace = "io.element.android.services.analyticsproviders.api" +} + +dependencies { + api(libs.matrix.analytics.events) +} diff --git a/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/AnalyticsProvider.kt b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/AnalyticsProvider.kt new file mode 100644 index 0000000000..026d4cdd6e --- /dev/null +++ b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/AnalyticsProvider.kt @@ -0,0 +1,36 @@ +/* + * 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.api + +import io.element.android.services.analyticsproviders.api.trackers.AnalyticsTracker +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. + */ + val name: String + + suspend fun init() + + fun stop() +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsTracker.kt b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/AnalyticsTracker.kt similarity index 78% rename from services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsTracker.kt rename to services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/AnalyticsTracker.kt index 90c2b5cfd4..e37053fbf7 100644 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsTracker.kt +++ b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/AnalyticsTracker.kt @@ -14,9 +14,11 @@ * limitations under the License. */ -package io.element.android.services.analytics.api +package io.element.android.services.analyticsproviders.api.trackers -import io.element.android.services.analytics.api.plan.UserProperties +import im.vector.app.features.analytics.itf.VectorAnalyticsEvent +import im.vector.app.features.analytics.itf.VectorAnalyticsScreen +import im.vector.app.features.analytics.plan.UserProperties interface AnalyticsTracker { /** diff --git a/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/ErrorTracker.kt b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/ErrorTracker.kt new file mode 100644 index 0000000000..fb1ffe79a8 --- /dev/null +++ b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/ErrorTracker.kt @@ -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.api.trackers + +interface ErrorTracker { + fun trackError(throwable: Throwable) +} diff --git a/services/analyticsproviders/posthog/build.gradle.kts b/services/analyticsproviders/posthog/build.gradle.kts new file mode 100644 index 0000000000..c9049af237 --- /dev/null +++ b/services/analyticsproviders/posthog/build.gradle.kts @@ -0,0 +1,37 @@ +/* + * 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.posthog" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(libs.dagger) + implementation(libs.posthog) { + exclude("com.android.support", "support-annotations") + } + implementation(projects.libraries.core) + implementation(projects.libraries.di) + implementation(projects.services.analyticsproviders.api) +} diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt new file mode 100644 index 0000000000..b4fd6dcd18 --- /dev/null +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt @@ -0,0 +1,55 @@ +/* + * 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.posthog + +import android.content.Context +import com.posthog.android.PostHog +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.di.ApplicationContext +import javax.inject.Inject + +class PostHogFactory @Inject constructor( + @ApplicationContext private val context: Context, + private val buildMeta: BuildMeta, +) { + + fun createPosthog(): PostHog { + return PostHog.Builder(context, PosthogConfig.postHogApiKey, PosthogConfig.postHogHost) + // Record certain application events automatically! (off/false by default) + // .captureApplicationLifecycleEvents() + // Record screen views automatically! (off/false by default) + // .recordScreenViews() + // Capture deep links as part of the screen call. (off by default) + // .captureDeepLinks() + // Maximum number of events to keep in queue before flushing (default 20) + // .flushQueueSize(20) + // Max delay before flushing the queue (30 seconds) + // .flushInterval(30, TimeUnit.SECONDS) + // Enable or disable collection of ANDROID_ID (true) + .collectDeviceId(false) + .logLevel(getLogLevel()) + .build() + } + + private fun getLogLevel(): PostHog.LogLevel { + return if (buildMeta.isDebuggable) { + PostHog.LogLevel.DEBUG + } else { + PostHog.LogLevel.INFO + } + } +} diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt new file mode 100644 index 0000000000..2499c87e9f --- /dev/null +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt @@ -0,0 +1,109 @@ +/* + * 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.posthog + +import com.posthog.android.Options +import com.posthog.android.PostHog +import com.posthog.android.Properties +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.di.AppScope +import io.element.android.services.analyticsproviders.api.AnalyticsProvider +import io.element.android.services.analyticsproviders.posthog.log.analyticsTag +import timber.log.Timber +import javax.inject.Inject + +private val REUSE_EXISTING_ID: String? = null +private val IGNORED_OPTIONS: Options? = null + +@ContributesMultibinding(AppScope::class) +class PosthogAnalyticsProvider @Inject constructor( + private val postHogFactory: PostHogFactory, +) : AnalyticsProvider { + override val index = PosthogConfig.index + override val name = PosthogConfig.name + + private var posthog: PostHog? = null + private var analyticsId: String? = null + + override suspend fun init() { + posthog = createPosthog() + posthog?.optOut(false) + identifyPostHog() + } + + override fun stop() { + // When opting out, ensure that the queue is flushed first, or it will be flushed later (after user has revoked consent) + posthog?.flush() + posthog?.optOut(true) + posthog?.shutdown() + posthog = null + analyticsId = null + } + + override fun capture(event: VectorAnalyticsEvent) { + posthog?.capture(event.getName(), event.getProperties()?.toPostHogProperties()) + } + + override fun screen(screen: VectorAnalyticsScreen) { + posthog?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties()) + } + + override fun updateUserProperties(userProperties: UserProperties) { + posthog?.identify( + REUSE_EXISTING_ID, userProperties.getProperties()?.toPostHogUserProperties(), + IGNORED_OPTIONS + ) + } + + override fun trackError(throwable: Throwable) { + TODO("Not yet implemented") + } + + private fun createPosthog(): PostHog = postHogFactory.createPosthog() + + private fun identifyPostHog() { + val id = analyticsId ?: return + if (id.isEmpty()) { + Timber.tag(analyticsTag.value).d("reset") + posthog?.reset() + } else { + Timber.tag(analyticsTag.value).d("identify") +// posthog?.identify(id, lateInitUserPropertiesFactory.createUserProperties()?.getProperties()?.toPostHogUserProperties(), IGNORED_OPTIONS) + } + } + + private fun Map?.toPostHogProperties(): Properties? { + if (this == null) return null + + return Properties().apply { + putAll(this@toPostHogProperties) + } + } + + /** + * We avoid sending nulls as part of the UserProperties as this will reset the values across all devices. + * The UserProperties event has nullable properties to allow for clients to opt in. + */ + private fun Map.toPostHogUserProperties(): Properties { + return Properties().apply { + putAll(this@toPostHogUserProperties.filter { it.value != null }) + } + } +} diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogConfig.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogConfig.kt new file mode 100644 index 0000000000..877fb7dc9a --- /dev/null +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogConfig.kt @@ -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.posthog + +object PosthogConfig { + const val index = 0 + const val name = "Posthog" + const val postHogHost = "https://posthog.element.dev" + const val postHogApiKey = "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN" +} diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/extensions/InteractionExt.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/extensions/InteractionExt.kt new file mode 100644 index 0000000000..2095c2e1d4 --- /dev/null +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/extensions/InteractionExt.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 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.posthog.extensions + +import im.vector.app.features.analytics.plan.Interaction + +fun Interaction.Name.toAnalyticsInteraction(interactionType: Interaction.InteractionType = Interaction.InteractionType.Touch) = + Interaction( + name = this, + interactionType = interactionType + ) diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsScreen.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/log/AnalyticsLoggerTag.kt similarity index 77% rename from services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsScreen.kt rename to services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/log/AnalyticsLoggerTag.kt index 7720158e20..8e64ca100d 100644 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsScreen.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/log/AnalyticsLoggerTag.kt @@ -14,9 +14,8 @@ * limitations under the License. */ -package io.element.android.services.analytics.api +package io.element.android.services.analyticsproviders.posthog.log -interface VectorAnalyticsScreen { - fun getName(): String - fun getProperties(): Map? -} +import io.element.android.libraries.core.log.logger.LoggerTag + +val analyticsTag = LoggerTag("Analytics") diff --git a/settings.gradle.kts b/settings.gradle.kts index 9b2d507b7e..da6c0affd5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,6 +33,7 @@ dependencyResolutionManagement { url = URI("https://www.jitpack.io") content { includeModule("com.github.UnifiedPush", "android-connector") + includeModule("com.github.matrix-org", "matrix-analytics-events") } } //noinspection JcenterRepositoryObsolete diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.api.preferences_null_DefaultGroup_AnalyticsPreferencesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.api.preferences_null_DefaultGroup_AnalyticsPreferencesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..b901fa8bd8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.api.preferences_null_DefaultGroup_AnalyticsPreferencesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:514254ebfcc01b99ae6b85108c327cd2cfbe930ec793764049cf617e0d3b3580 +size 29278 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.api.preferences_null_DefaultGroup_AnalyticsPreferencesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.api.preferences_null_DefaultGroup_AnalyticsPreferencesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..29f66e2bf4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.api.preferences_null_DefaultGroup_AnalyticsPreferencesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56d3de1fe930fa9c961265c2dc1c99ebecde06f3be27ab7d3cfb7299679456bb +size 28666 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..eaaa4b3a91 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88d5d2054bae36664b69a20853e11e4567bd6cb1e6a8f7ea0a6747ec534807f6 +size 47160 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewLightPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..cf5be1e386 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9774baeb4de35b8fc4f47d4e41eb38f856a098465ffce0cdea66c32e4cebf57 +size 46475 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview--1_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview--1_1_null_0,NEXUS_5,1.0,en].png index f3e4ce3aac..fcdaeaa1c9 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview--1_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview--1_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4da371d30e79682f1b62d613684a65bda3e685e36e9d85881f8fa0ea976037f5 -size 45433 +oid sha256:9002796dbd6074f2cf6a2351dc5d65e772c1e7abedaf637dd81a30c11da11a3f +size 61210 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview--1_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview--1_1_null_1,NEXUS_5,1.0,en].png index caff2f7ac3..0ea551a0b7 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview--1_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewDarkPreview--1_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77db5c9af4eda9dda35e93f488f9732adc5fdbc7deb0b958c9e059911b0d7897 -size 48716 +oid sha256:3a6e702e87522cd2cf8fbb72b243f0d8e4f5426b04d8060a6a4b265c3de74d8b +size 64012 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview--0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview--0_0_null_0,NEXUS_5,1.0,en].png index 042f141b08..098af44de0 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview--0_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview--0_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0816cd40ef1317530c1ce5b52b00555b72dc3bdcbf17d9333b470143816b201 -size 44108 +oid sha256:d9010c13c864e8c6cf675a8335e9653387482108c897b6ffbc62ada1711aaeb9 +size 59639 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview--0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview--0_0_null_1,NEXUS_5,1.0,en].png index 921b3ba256..21ac080032 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview--0_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.root_null_DefaultGroup_PreferencesRootViewLightPreview--0_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:707ec6660e60ced6e176c9d326ed8859f3365db0b5b9a2efb57e9d1e1bd2e899 -size 47290 +oid sha256:1592a00820108d4883a1ddaad265722653d7e4d5eabd99ab4b648672fd75850d +size 62606 diff --git a/tools/localazy/config.json b/tools/localazy/config.json index ef7110a160..a23d652cd8 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -103,6 +103,12 @@ "screen_room_member.*", "screen_dm_.*" ] + }, + { + "name": ":features:analytics:impl", + "includeRegex": [ + "screen_analytics_prompt.*" + ] } ] }