diff --git a/Core2/Matomo/build.gradle.kts b/Core2/Matomo/build.gradle.kts new file mode 100644 index 000000000..7784304a9 --- /dev/null +++ b/Core2/Matomo/build.gradle.kts @@ -0,0 +1,42 @@ +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) +} 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..f1b424510 --- /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 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..ca4ec78b4 --- /dev/null +++ b/Core2/Matomo/src/main/java/com/infomaniak/matomo/Matomo.kt @@ -0,0 +1,107 @@ +/* + * 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 Context.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) { + context.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..1738542a1 100644 --- a/Core2/gradle/core2.versions.toml +++ b/Core2/gradle/core2.versions.toml @@ -1,5 +1,7 @@ [versions] sentry-android = "7.15.0" +matomo = "4.1.4" [libraries] sentry-android = { module = "io.sentry:sentry-android", version.ref = "sentry-android" } +matomo = { module = "com.github.matomo-org:matomo-sdk-android", version.ref = "matomo" } 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/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 new file mode 100644 index 000000000..9da166052 --- /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 + +object MatomoSwissTransfer : Matomo { + + 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() } } 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")