From cb024f34d13e5d5fac16156e4195fa4827a094c6 Mon Sep 17 00:00:00 2001 From: Vincent TE Date: Tue, 29 Oct 2024 15:49:07 +0100 Subject: [PATCH 1/5] feat: Add Matomo module --- Core2/Matomo/build.gradle.kts | 44 +++++++ Core2/Matomo/consumer-rules.pro | 0 Core2/Matomo/proguard-rules.pro | 21 ++++ .../main/java/com/infomaniak/matomo/Matomo.kt | 110 ++++++++++++++++++ Core2/gradle/core2.versions.toml | 6 + app/build.gradle.kts | 1 + .../swisstransfer/di/ApplicationModule.kt | 16 +++ .../swisstransfer/ui/MatomoSwissTransfer.kt | 28 +++++ settings.gradle.kts | 1 + 9 files changed, 227 insertions(+) create mode 100644 Core2/Matomo/build.gradle.kts create mode 100644 Core2/Matomo/consumer-rules.pro create mode 100644 Core2/Matomo/proguard-rules.pro create mode 100644 Core2/Matomo/src/main/java/com/infomaniak/matomo/Matomo.kt create mode 100644 app/src/main/java/com/infomaniak/swisstransfer/ui/MatomoSwissTransfer.kt diff --git a/Core2/Matomo/build.gradle.kts b/Core2/Matomo/build.gradle.kts new file mode 100644 index 000000000..40b286133 --- /dev/null +++ b/Core2/Matomo/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "com.infomaniak.matomo" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + + buildConfigField("String", "MATOMO_URL", "\"https://analytics.infomaniak.com/matomo.php\"") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig = true + } + + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + api(core2.matomo) + api(core2.androidx.core) + api(core2.appcompat) +} diff --git a/Core2/Matomo/consumer-rules.pro b/Core2/Matomo/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/Core2/Matomo/proguard-rules.pro b/Core2/Matomo/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/Core2/Matomo/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/Core2/Matomo/src/main/java/com/infomaniak/matomo/Matomo.kt b/Core2/Matomo/src/main/java/com/infomaniak/matomo/Matomo.kt new file mode 100644 index 000000000..d7d2f74d8 --- /dev/null +++ b/Core2/Matomo/src/main/java/com/infomaniak/matomo/Matomo.kt @@ -0,0 +1,110 @@ +/* + * Infomaniak SwissTransfer - Android + * Copyright (C) 2024 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.matomo + +import android.app.Activity +import android.content.Context +import android.util.Log +import org.matomo.sdk.Matomo +import org.matomo.sdk.Tracker +import org.matomo.sdk.TrackerBuilder +import org.matomo.sdk.extra.DownloadTracker +import org.matomo.sdk.extra.TrackHelper + +interface Matomo { + + val tracker: Tracker + val siteId: Int + + fun Context.buildTracker(shouldOptOut: Boolean = false): Tracker { + return TrackerBuilder(BuildConfig.MATOMO_URL, siteId, "AndroidTracker").build(Matomo.getInstance(this)).also { + // Put a tracker on app installs to have statistics on the number of times the app is installed or updated + TrackHelper.track().download().identifier(DownloadTracker.Extra.ApkChecksum(this)).with(it) + it.isOptOut = shouldOptOut + } + } + + fun Context.addTrackingCallbackForDebugLog() { + if (BuildConfig.DEBUG) { + tracker.addTrackingCallback { trackMe -> + trackMe.also { + it.toMap().forEach { entry -> + when (entry.key) { + "action_name" -> Log.d("TrackerScreen", entry.value) + "e_c" -> Log.d("TrackerEvent", "Category : ${entry.value}") + "e_n" -> Log.d("TrackerEvent", "Name : ${entry.value}") + "e_a" -> Log.d("TrackerEvent", "Action : ${entry.value}") + "e_v" -> Log.d("TrackerEvent", "Value : ${entry.value}") + } + } + } + } + } + } + + fun Context.trackUserId(userId: Int) { + tracker.userId = userId.toString() + } + + //region Track screens + + fun Activity.trackScreen() { + TrackHelper.track().screen(this).title(this::class.java.simpleName).with(tracker) + } + + fun Context.trackScreen(path: String, title: String) { + TrackHelper.track().screen(path).title(title).with(tracker) + } + + //endregion + + //region Track events + + fun Context.trackEvent(category: String, name: String, action: TrackerAction = TrackerAction.CLICK, value: Float? = null) { + TrackHelper.track() + .event(category, action.toString()) + .name(name) + .let { if (value != null) it.value(value) else it } + .with(tracker) + } + + fun Context.trackAccountEvent(name: String, action: TrackerAction = TrackerAction.CLICK, value: Float? = null) { + trackEvent("account", name, action, value) + } + //endregion + + fun Boolean.toFloat() = if (this) 1.0f else 0.0f + + fun shouldOptOut(context: Context, shouldOptOut: Boolean) { + tracker.isOptOut = shouldOptOut + } + + enum class TrackerAction { + CLICK, + DATA, + DRAG, + INPUT, + LONG_PRESS; + + override fun toString() = name.lowercase().snakeToCamelCase() + + private fun String.snakeToCamelCase(): String { + return replace("_[a-zA-Z]".toRegex()) { it.value.replace("_", "").uppercase() } + } + } +} diff --git a/Core2/gradle/core2.versions.toml b/Core2/gradle/core2.versions.toml index d11dbef7c..bfdb76f55 100644 --- a/Core2/gradle/core2.versions.toml +++ b/Core2/gradle/core2.versions.toml @@ -1,5 +1,11 @@ [versions] sentry-android = "7.15.0" +matomo = "4.1.4" +androidx-core = "1.13.1" +appcompatVersion = "1.7.0" [libraries] sentry-android = { module = "io.sentry:sentry-android", version.ref = "sentry-android" } +matomo = { module = "com.github.matomo-org:matomo-sdk-android", version.ref = "matomo" } +androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } +appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompatVersion" } diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 750d67140..b10844a1f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -82,6 +82,7 @@ sentry { dependencies { implementation(project(":Core2")) implementation(project(":Core2:Sentry")) + implementation(project(":Core2:Matomo")) implementation(project(":FileTypes")) implementation(kotlin("reflect")) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt b/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt index cea0b5379..fcb542d0f 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt @@ -17,10 +17,15 @@ */ package com.infomaniak.swisstransfer.di +import android.app.Activity import android.app.Application import android.content.Context +import com.infomaniak.matomo.Matomo +import com.infomaniak.multiplatform_swisstransfer.SwissTransferInjection +import com.infomaniak.swisstransfer.ui.MainActivity import androidx.work.WorkManager import com.infomaniak.swisstransfer.ui.MainApplication +import com.infomaniak.swisstransfer.ui.MatomoSwissTransfer import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -34,6 +39,9 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) object ApplicationModule { + @Provides + fun providesApplicationContext(@ApplicationContext context: Context) = context + @Provides fun providesMainApplication(application: Application) = application as MainApplication @@ -43,6 +51,14 @@ object ApplicationModule { return CoroutineScope(defaultDispatcher) } + @Provides + @Singleton + fun provideMatomoSwissTransfer(application: MainApplication) = MatomoSwissTransfer(application) + + @Provides + @Singleton + fun provideMatomoTracker(matomo: MatomoSwissTransfer) = matomo.tracker + @Provides @Singleton fun providesWorkManager(@ApplicationContext context: Context) = WorkManager.getInstance(context) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/MatomoSwissTransfer.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/MatomoSwissTransfer.kt new file mode 100644 index 000000000..7dd6bd78b --- /dev/null +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/MatomoSwissTransfer.kt @@ -0,0 +1,28 @@ +/* + * Infomaniak SwissTransfer - Android + * Copyright (C) 2024 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.swisstransfer.ui + +import android.content.Context +import com.infomaniak.matomo.Matomo +import org.matomo.sdk.Tracker + +class MatomoSwissTransfer(private val context: Context) : Matomo { + + override val tracker: Tracker by lazy { context.buildTracker() } + override val siteId: Int = -1 //TODO replace with the right site ID when we have one +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 4a8de3dc0..8c3abc6e3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,4 +31,5 @@ rootProject.name = "android-SwissTransfer" include(":app") include(":Core2") include(":Core2:Sentry") +include(":Core2:Matomo") include(":FileTypes") From b992cd08b86502ca381511840ef64409e66cf637 Mon Sep 17 00:00:00 2001 From: Vincent TE Date: Tue, 29 Oct 2024 15:50:35 +0100 Subject: [PATCH 2/5] chore: Remove unused import --- .../java/com/infomaniak/swisstransfer/di/ApplicationModule.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt b/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt index fcb542d0f..6077806ee 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt @@ -17,10 +17,8 @@ */ package com.infomaniak.swisstransfer.di -import android.app.Activity import android.app.Application import android.content.Context -import com.infomaniak.matomo.Matomo import com.infomaniak.multiplatform_swisstransfer.SwissTransferInjection import com.infomaniak.swisstransfer.ui.MainActivity import androidx.work.WorkManager From 5dac5d8d2792e5aca80abbd22fd3c19aee8c8565 Mon Sep 17 00:00:00 2001 From: Vincent TE Date: Tue, 29 Oct 2024 16:05:00 +0100 Subject: [PATCH 3/5] chore: Remove unnecessary injections --- .../com/infomaniak/swisstransfer/di/ApplicationModule.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt b/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt index 6077806ee..9d9a46035 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt @@ -18,7 +18,6 @@ package com.infomaniak.swisstransfer.di import android.app.Application -import android.content.Context import com.infomaniak.multiplatform_swisstransfer.SwissTransferInjection import com.infomaniak.swisstransfer.ui.MainActivity import androidx.work.WorkManager @@ -27,7 +26,6 @@ import com.infomaniak.swisstransfer.ui.MatomoSwissTransfer import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -37,9 +35,6 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) object ApplicationModule { - @Provides - fun providesApplicationContext(@ApplicationContext context: Context) = context - @Provides fun providesMainApplication(application: Application) = application as MainApplication From eef205a1f1de48e527e34665047eea9471fe1acf Mon Sep 17 00:00:00 2001 From: Vincent TE Date: Tue, 29 Oct 2024 16:05:09 +0100 Subject: [PATCH 4/5] chore: Add empty line --- Core2/Matomo/proguard-rules.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core2/Matomo/proguard-rules.pro b/Core2/Matomo/proguard-rules.pro index 481bb4348..f1b424510 100644 --- a/Core2/Matomo/proguard-rules.pro +++ b/Core2/Matomo/proguard-rules.pro @@ -18,4 +18,4 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile From b3631ef2154db40e23f32e261536eafd2ece2f3e Mon Sep 17 00:00:00 2001 From: Vincent TE Date: Tue, 29 Oct 2024 16:34:53 +0100 Subject: [PATCH 5/5] feat: Use an object instead of injection to use extension --- Core2/Matomo/build.gradle.kts | 2 -- .../src/main/java/com/infomaniak/matomo/Matomo.kt | 7 ++----- Core2/gradle/core2.versions.toml | 4 ---- .../swisstransfer/di/ApplicationModule.kt | 13 ++----------- .../com/infomaniak/swisstransfer/ui/MainActivity.kt | 2 ++ .../swisstransfer/ui/MatomoSwissTransfer.kt | 4 ++-- .../swisstransfer/ui/NewTransferActivity.kt | 2 ++ 7 files changed, 10 insertions(+), 24 deletions(-) diff --git a/Core2/Matomo/build.gradle.kts b/Core2/Matomo/build.gradle.kts index 40b286133..7784304a9 100644 --- a/Core2/Matomo/build.gradle.kts +++ b/Core2/Matomo/build.gradle.kts @@ -39,6 +39,4 @@ android { dependencies { api(core2.matomo) - api(core2.androidx.core) - api(core2.appcompat) } diff --git a/Core2/Matomo/src/main/java/com/infomaniak/matomo/Matomo.kt b/Core2/Matomo/src/main/java/com/infomaniak/matomo/Matomo.kt index d7d2f74d8..ca4ec78b4 100644 --- a/Core2/Matomo/src/main/java/com/infomaniak/matomo/Matomo.kt +++ b/Core2/Matomo/src/main/java/com/infomaniak/matomo/Matomo.kt @@ -28,7 +28,7 @@ import org.matomo.sdk.extra.TrackHelper interface Matomo { - val tracker: Tracker + val Context.tracker: Tracker val siteId: Int fun Context.buildTracker(shouldOptOut: Boolean = false): Tracker { @@ -62,7 +62,6 @@ interface Matomo { } //region Track screens - fun Activity.trackScreen() { TrackHelper.track().screen(this).title(this::class.java.simpleName).with(tracker) } @@ -70,11 +69,9 @@ interface Matomo { fun Context.trackScreen(path: String, title: String) { TrackHelper.track().screen(path).title(title).with(tracker) } - //endregion //region Track events - fun Context.trackEvent(category: String, name: String, action: TrackerAction = TrackerAction.CLICK, value: Float? = null) { TrackHelper.track() .event(category, action.toString()) @@ -91,7 +88,7 @@ interface Matomo { fun Boolean.toFloat() = if (this) 1.0f else 0.0f fun shouldOptOut(context: Context, shouldOptOut: Boolean) { - tracker.isOptOut = shouldOptOut + context.tracker.isOptOut = shouldOptOut } enum class TrackerAction { diff --git a/Core2/gradle/core2.versions.toml b/Core2/gradle/core2.versions.toml index bfdb76f55..1738542a1 100644 --- a/Core2/gradle/core2.versions.toml +++ b/Core2/gradle/core2.versions.toml @@ -1,11 +1,7 @@ [versions] sentry-android = "7.15.0" matomo = "4.1.4" -androidx-core = "1.13.1" -appcompatVersion = "1.7.0" [libraries] sentry-android = { module = "io.sentry:sentry-android", version.ref = "sentry-android" } matomo = { module = "com.github.matomo-org:matomo-sdk-android", version.ref = "matomo" } -androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } -appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompatVersion" } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt b/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt index 9d9a46035..cea0b5379 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/di/ApplicationModule.kt @@ -18,14 +18,13 @@ package com.infomaniak.swisstransfer.di import android.app.Application -import com.infomaniak.multiplatform_swisstransfer.SwissTransferInjection -import com.infomaniak.swisstransfer.ui.MainActivity +import android.content.Context import androidx.work.WorkManager import com.infomaniak.swisstransfer.ui.MainApplication -import com.infomaniak.swisstransfer.ui.MatomoSwissTransfer import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -44,14 +43,6 @@ object ApplicationModule { return CoroutineScope(defaultDispatcher) } - @Provides - @Singleton - fun provideMatomoSwissTransfer(application: MainApplication) = MatomoSwissTransfer(application) - - @Provides - @Singleton - fun provideMatomoTracker(matomo: MatomoSwissTransfer) = matomo.tracker - @Provides @Singleton fun providesWorkManager(@ApplicationContext context: Context) = WorkManager.getInstance(context) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/MainActivity.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/MainActivity.kt index 25df5f2ec..c2145d13b 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/MainActivity.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/MainActivity.kt @@ -28,6 +28,7 @@ import androidx.compose.runtime.getValue import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.infomaniak.multiplatform_swisstransfer.common.models.Theme +import com.infomaniak.swisstransfer.ui.MatomoSwissTransfer.trackScreen import com.infomaniak.swisstransfer.ui.screen.main.MainScreen import com.infomaniak.swisstransfer.ui.screen.main.settings.SettingsViewModel import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme @@ -48,6 +49,7 @@ class MainActivity : ComponentActivity() { MainScreen() } } + trackScreen() } } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/MatomoSwissTransfer.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/MatomoSwissTransfer.kt index 7dd6bd78b..9da166052 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/MatomoSwissTransfer.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/MatomoSwissTransfer.kt @@ -21,8 +21,8 @@ import android.content.Context import com.infomaniak.matomo.Matomo import org.matomo.sdk.Tracker -class MatomoSwissTransfer(private val context: Context) : Matomo { +object MatomoSwissTransfer : Matomo { - override val tracker: Tracker by lazy { context.buildTracker() } + override val Context.tracker: Tracker get() = (this as MainActivity).buildTracker() //TODO fetch appSettings for opt out override val siteId: Int = -1 //TODO replace with the right site ID when we have one } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/NewTransferActivity.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/NewTransferActivity.kt index 7621d7333..97c971267 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/NewTransferActivity.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/NewTransferActivity.kt @@ -21,6 +21,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import com.infomaniak.swisstransfer.ui.MatomoSwissTransfer.trackScreen import com.infomaniak.swisstransfer.ui.screen.newtransfer.NewTransferScreen import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme import dagger.hilt.android.AndroidEntryPoint @@ -36,5 +37,6 @@ class NewTransferActivity : ComponentActivity() { NewTransferScreen(closeActivity = { finish() }) } } + trackScreen() } }