diff --git a/.configure b/.configure index 6d696dca..626e2924 100644 --- a/.configure +++ b/.configure @@ -1,7 +1,7 @@ { "project_name": "Gravatar-Android", "branch": "trunk", - "pinned_hash": "8045b892223ecaf5d62dd0cd2dc3165c6ffd44d7", + "pinned_hash": "3c8238c6aa7b0098bd4ccf24e0a7b6178e3ee2a7", "files_to_copy": [ { "file": "android/Gravatar-Android/secrets.properties", diff --git a/.configure-files/secrets.properties.enc b/.configure-files/secrets.properties.enc index d490c69f..0b858af6 100644 Binary files a/.configure-files/secrets.properties.enc and b/.configure-files/secrets.properties.enc differ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 503a88fc..34bf5349 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { implementation(project(":clock")) implementation(project(":design")) implementation(project(":networkMonitor")) + implementation(project(":crashlogging")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) diff --git a/app/src/main/java/com/gravatar/app/GravatarApplication.kt b/app/src/main/java/com/gravatar/app/GravatarApplication.kt index 5b28f6df..d594021f 100644 --- a/app/src/main/java/com/gravatar/app/GravatarApplication.kt +++ b/app/src/main/java/com/gravatar/app/GravatarApplication.kt @@ -1,7 +1,9 @@ package com.gravatar.app import android.app.Application +import com.automattic.android.tracks.crashlogging.CrashLogging import com.gravatar.app.di.appModule +import org.koin.android.ext.android.get import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin @@ -14,5 +16,8 @@ class GravatarApplication : Application() { androidContext(this@GravatarApplication) modules(appModule) } + + val crashLogging: CrashLogging = get() + crashLogging.initialize() } } diff --git a/app/src/main/java/com/gravatar/app/di/AppModule.kt b/app/src/main/java/com/gravatar/app/di/AppModule.kt index 7396acbe..2c872745 100644 --- a/app/src/main/java/com/gravatar/app/di/AppModule.kt +++ b/app/src/main/java/com/gravatar/app/di/AppModule.kt @@ -5,6 +5,7 @@ import com.gravatar.app.clock.di.clockModule import com.gravatar.app.homeUi.di.homeUiModule import com.gravatar.app.loginUi.di.loginUiModule import com.gravatar.app.networkmonitor.di.networkMonitorModule +import com.gravatar.crashlogging.di.crashLoggingModule import org.koin.dsl.module val appModule = module { @@ -16,5 +17,6 @@ val appModule = module { clockModule, networkMonitorModule, buildConfigModule, + crashLoggingModule, ) } diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index 76fb5c45..b17fc4ce 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { compileOnly(libs.compose.compiler.gradlePlugin) compileOnly(libs.detekt.gradlePlugin) implementation(libs.roborazzi.gradlePlugin) + implementation(libs.sentry.gradlePlugin) } tasks { diff --git a/build-logic/convention/src/main/kotlin/GravatarAndroidApplicationConventionPlugin.kt b/build-logic/convention/src/main/kotlin/GravatarAndroidApplicationConventionPlugin.kt index 6ef1175b..ccaeb728 100644 --- a/build-logic/convention/src/main/kotlin/GravatarAndroidApplicationConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/GravatarAndroidApplicationConventionPlugin.kt @@ -2,6 +2,8 @@ import com.android.build.api.dsl.ApplicationExtension import com.android.build.gradle.AppPlugin import com.gravatar.app.configureDetekt import com.gravatar.app.configureKotlinAndroid +import io.sentry.android.gradle.SentryPlugin +import io.sentry.android.gradle.extensions.SentryPluginExtension import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.apply @@ -18,6 +20,7 @@ class GravatarAndroidApplicationConventionPlugin : Plugin { with(target) { apply() apply() + apply() extensions.configure { configureKotlinAndroid(this) @@ -35,6 +38,8 @@ class GravatarAndroidApplicationConventionPlugin : Plugin { checkDependencies = true } } + + configureSentry() configureDetekt() } } @@ -76,4 +81,18 @@ class GravatarAndroidApplicationConventionPlugin : Plugin { } } } + + private fun Project.configureSentry() { + configure { + org.set("a8c") + projectName.set("gravatar-android") + authToken.set(System.getenv("SENTRY_AUTH_TOKEN")) + + includeProguardMapping.set(System.getenv("CI").toBoolean()) + includeSourceContext.set(System.getenv("CI").toBoolean()) + + autoInstallation.enabled.set(false) + ignoredBuildTypes.set(listOf("debug")) + } + } } diff --git a/crashlogging/build.gradle.kts b/crashlogging/build.gradle.kts new file mode 100644 index 00000000..4d20c4e6 --- /dev/null +++ b/crashlogging/build.gradle.kts @@ -0,0 +1,34 @@ +import java.util.Properties + +plugins { + alias(libs.plugins.gravatar.android.library) + alias(libs.plugins.kotlin.android) +} + +fun secretsProperties(): Properties { + return rootProject.extra["secretsProperties"] as Properties +} + +android { + namespace = "com.gravatar.crashlogging" + buildFeatures.buildConfig = true + + defaultConfig { + buildConfigField( + "String", + "SENTRY_DSN", + "\"${secretsProperties()["sentryDsn"]?.toString() ?: ""}\"", + ) + } +} + +dependencies { + implementation(project(":userComponent")) + + implementation(libs.koin.core) + implementation(libs.koin.android) + implementation(libs.kotlinx.coroutines) + implementation(libs.androidx.core.ktx) + + api(libs.automattic.crashlogging) +} diff --git a/crashlogging/src/main/java/com/gravatar/crashlogging/ContextBasedLocaleProvider.kt b/crashlogging/src/main/java/com/gravatar/crashlogging/ContextBasedLocaleProvider.kt new file mode 100644 index 00000000..599f8ac9 --- /dev/null +++ b/crashlogging/src/main/java/com/gravatar/crashlogging/ContextBasedLocaleProvider.kt @@ -0,0 +1,13 @@ +package com.gravatar.crashlogging + +import android.app.Application +import androidx.core.os.ConfigurationCompat +import java.util.Locale + +internal class ContextBasedLocaleProvider( + private val context: Application, +) : LocaleProvider { + override fun provideLocale(): Locale? { + return ConfigurationCompat.getLocales(context.resources.configuration)[0] + } +} diff --git a/crashlogging/src/main/java/com/gravatar/crashlogging/GravatarCrashLoggingDataProvider.kt b/crashlogging/src/main/java/com/gravatar/crashlogging/GravatarCrashLoggingDataProvider.kt new file mode 100644 index 00000000..8fbf2f91 --- /dev/null +++ b/crashlogging/src/main/java/com/gravatar/crashlogging/GravatarCrashLoggingDataProvider.kt @@ -0,0 +1,62 @@ +package com.gravatar.crashlogging + +import com.automattic.android.tracks.crashlogging.CrashLoggingDataProvider +import com.automattic.android.tracks.crashlogging.CrashLoggingUser +import com.automattic.android.tracks.crashlogging.EventLevel +import com.automattic.android.tracks.crashlogging.ExtraKnownKey +import com.automattic.android.tracks.crashlogging.PerformanceMonitoringConfig +import com.automattic.android.tracks.crashlogging.ReleaseName +import com.gravatar.app.usercomponent.domain.repository.UserRepository +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.map + +internal class GravatarCrashLoggingDataProvider( + localeProvider: LocaleProvider, + userRepository: UserRepository +) : CrashLoggingDataProvider { + + override val applicationContextProvider = emptyFlow>() + + override val buildType = BuildConfig.BUILD_TYPE + + override val enableCrashLoggingLogs = BuildConfig.DEBUG + + override val locale = localeProvider.provideLocale() + + override val performanceMonitoringConfig = PerformanceMonitoringConfig.Disabled + + override val releaseName = if (BuildConfig.DEBUG) { + ReleaseName.SetByApplication(DEBUG_RELEASE_NAME) + } else { + ReleaseName.SetByTracksLibrary + } + + override val sentryDSN = BuildConfig.SENTRY_DSN + + override val user = userRepository.getProfile().map { profile -> + CrashLoggingUser( + userID = profile?.userId?.toString(), + email = profile?.hash, + username = profile?.displayName, + ) + } + + override fun crashLoggingEnabled() = false + + override fun extraKnownKeys() = emptyList() + + override fun provideExtrasForEvent( + currentExtras: Map, + eventLevel: EventLevel + ) = emptyMap() + + override fun shouldDropWrappingException( + module: String, + type: String, + value: String + ) = false + + companion object { + const val DEBUG_RELEASE_NAME = "debug" + } +} diff --git a/crashlogging/src/main/java/com/gravatar/crashlogging/LocaleProvider.kt b/crashlogging/src/main/java/com/gravatar/crashlogging/LocaleProvider.kt new file mode 100644 index 00000000..ddb3e3d6 --- /dev/null +++ b/crashlogging/src/main/java/com/gravatar/crashlogging/LocaleProvider.kt @@ -0,0 +1,7 @@ +package com.gravatar.crashlogging + +import java.util.Locale + +internal fun interface LocaleProvider { + fun provideLocale(): Locale? +} diff --git a/crashlogging/src/main/java/com/gravatar/crashlogging/di/CrashLoggingModule.kt b/crashlogging/src/main/java/com/gravatar/crashlogging/di/CrashLoggingModule.kt new file mode 100644 index 00000000..73412d52 --- /dev/null +++ b/crashlogging/src/main/java/com/gravatar/crashlogging/di/CrashLoggingModule.kt @@ -0,0 +1,29 @@ +package com.gravatar.crashlogging.di + +import com.automattic.android.tracks.crashlogging.CrashLoggingDataProvider +import com.automattic.android.tracks.crashlogging.CrashLoggingProvider +import com.gravatar.crashlogging.ContextBasedLocaleProvider +import com.gravatar.crashlogging.GravatarCrashLoggingDataProvider +import com.gravatar.crashlogging.LocaleProvider +import org.koin.android.ext.koin.androidApplication +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.factoryOf +import org.koin.dsl.module + +val crashLoggingModule = module { + factory { + ContextBasedLocaleProvider( + context = androidApplication() + ) + } + factoryOf(::GravatarCrashLoggingDataProvider) { + bind() + } + single { + CrashLoggingProvider.createInstance( + context = get(), + dataProvider = get(), + appScope = get() + ) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 99639ff2..b89fd9e1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ retrofit = "3.0.0" roborazzi = "1.45.1" robolectric = "4.14.1" room = "2.7.2" -tracks = "6.0.3" +tracks = "6.0.5" turbine = "1.2.1" browser = "1.8.0" ktor = "3.1.3" @@ -29,6 +29,7 @@ androidxTestCore = "1.6.1" constraintLayout = "1.1.0" qrose= "1.0.1" moshi = "1.15.2" +sentry = "5.9.0" [libraries] kotlinx-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinCoroutines" } @@ -42,6 +43,7 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3" androidx-constraintLayout-compose = { group = "androidx.constraintlayout", name = "constraintlayout-compose", version.ref = "constraintLayout" } androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidxTestCore" } # Tracks - Analytics and Crash Reporting +automattic-crashlogging = { module = "com.automattic.tracks:crashlogging", version.ref = "tracks" } automattic-tracks = { module = "com.automattic:Automattic-Tracks-Android", version.ref = "tracks" } androidx-ui = { group = "androidx.compose.ui", name = "ui" } androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } @@ -90,6 +92,7 @@ kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-pl compose-compiler-gradlePlugin = { group = "org.jetbrains.kotlin.plugin.compose", name = "org.jetbrains.kotlin.plugin.compose.gradle.plugin", version.ref = "kotlin" } detekt-gradlePlugin = { group = "io.gitlab.arturbosch.detekt", name = "io.gitlab.arturbosch.detekt.gradle.plugin", version.ref = "detekt" } roborazzi-gradlePlugin = { group = "io.github.takahirom.roborazzi", name = "io.github.takahirom.roborazzi.gradle.plugin", version.ref = "roborazzi" } +sentry-gradlePlugin = { group = "io.sentry", name = "sentry-android-gradle-plugin", version.ref = "sentry" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } diff --git a/loginUi/build.gradle.kts b/loginUi/build.gradle.kts index 99c0aa69..41748bfc 100644 --- a/loginUi/build.gradle.kts +++ b/loginUi/build.gradle.kts @@ -1,4 +1,3 @@ -import java.io.FileInputStream import java.util.Properties plugins { @@ -7,7 +6,7 @@ plugins { alias(libs.plugins.gravatar.android.compose) } -fun localProperties(): Properties { +fun secretsProperties(): Properties { return rootProject.extra["secretsProperties"] as Properties } @@ -16,7 +15,7 @@ android { buildFeatures.buildConfig = true defaultConfig { - localProperties().let { properties -> + secretsProperties().let { properties -> buildConfigField( "String", "OAUTH_CLIENT_ID", diff --git a/settings.gradle.kts b/settings.gradle.kts index 2046cba4..f9774c22 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -40,3 +40,4 @@ include(":clock") include(":design") include(":networkMonitor") include(":api") +include(":crashlogging") diff --git a/userComponent/build.gradle.kts b/userComponent/build.gradle.kts index 80e94584..7b6f2f4b 100644 --- a/userComponent/build.gradle.kts +++ b/userComponent/build.gradle.kts @@ -30,7 +30,7 @@ dependencies { implementation(libs.ktor.okhttp) implementation(libs.ktor.content.negotiation) implementation(libs.ktor.serialization.json) - implementation(libs.gravatar.core) + api(libs.gravatar.core) // Room implementation(libs.room.runtime)