Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ plugins {
id "com.google.devtools.ksp"
id "org.sonarqube" version "4.2.1.3168"
id 'jacoco'
id 'com.google.firebase.crashlytics'
}

def getPropertiesFile = { path ->
Expand Down Expand Up @@ -70,6 +71,10 @@ android {
getPropertiesFile('app/config/uat.properties').each { p ->
buildConfigField 'String', p.key, p.value
}
// Désactiver l'upload des mapping files en debug
firebaseCrashlytics {
mappingFileUploadEnabled false
}
}
release {
minifyEnabled false
Expand All @@ -78,6 +83,11 @@ android {
getPropertiesFile('app/config/prod.properties').each { p ->
buildConfigField 'String', p.key, p.value
}
// Activer l'upload des mapping files en release pour un meilleur débogage
firebaseCrashlytics {
mappingFileUploadEnabled true
nativeSymbolUploadEnabled true
}
}
}
compileOptions {
Expand Down Expand Up @@ -191,14 +201,15 @@ dependencies {
debugImplementation "androidx.compose.ui:ui-test-manifest:1.9.2"

// Import the BoM for the Firebase platform
implementation platform('com.google.firebase:firebase-bom:34.3.0')
implementation platform('com.google.firebase:firebase-bom:34.5.0')

// Firebase
implementation("com.google.firebase:firebase-auth-ktx:22.3.0")
implementation 'com.google.firebase:firebase-firestore-ktx:25.1.4'
implementation 'com.google.firebase:firebase-messaging:25.0.1'
implementation 'com.google.firebase:firebase-storage-ktx:21.0.2'
implementation "com.google.firebase:firebase-analytics"
implementation 'com.google.firebase:firebase-crashlytics-ndk'

// Navigation
implementation 'androidx.navigation:navigation-compose:2.9.5'
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/com/xpeho/xpeapp/XpeApp.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.xpeho.xpeapp

import android.app.Application
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.xpeho.xpeapp.di.AppModule
import com.xpeho.xpeapp.di.MainAppModule

Expand All @@ -11,6 +12,10 @@ class XpeApp : Application() {

override fun onCreate() {
super.onCreate()

// Initialiser Firebase Crashlytics
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true)

appModule = MainAppModule(appContext = this)
}
}
33 changes: 31 additions & 2 deletions app/src/main/java/com/xpeho/xpeapp/data/service/FirebaseService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,38 @@ import com.xpeho.xpeapp.data.NEWSLETTERS_COLLECTION
import com.xpeho.xpeapp.data.model.FeatureFlipping
import com.xpeho.xpeapp.data.model.Newsletter
import com.xpeho.xpeapp.data.model.toFeatureFlipping
import com.xpeho.xpeapp.utils.CrashlyticsUtils
import kotlinx.coroutines.tasks.await
import java.time.ZoneId

class FirebaseService {
suspend fun authenticate() {
FirebaseAuth.getInstance().signInAnonymously().await()
try {
CrashlyticsUtils.logEvent("Firebase: Tentative d'authentification anonyme")
FirebaseAuth.getInstance().signInAnonymously().await()
CrashlyticsUtils.logEvent("Firebase: Authentification anonyme réussie")
} catch (e: FirebaseException) {
CrashlyticsUtils.logEvent("Firebase: Erreur d'authentification anonyme")
CrashlyticsUtils.recordException(e)
throw e
}
}

fun isAuthenticated() = FirebaseAuth.getInstance().currentUser != null

fun signOut() = FirebaseAuth.getInstance().signOut()
fun signOut() {
try {
CrashlyticsUtils.logEvent("Firebase: Déconnexion")
FirebaseAuth.getInstance().signOut()
} catch (e: FirebaseException) {
CrashlyticsUtils.recordException(e)
throw e
}
}

suspend fun fetchFeatureFlipping(): List<FeatureFlipping> {
try {
CrashlyticsUtils.logEvent("Firebase: Récupération des feature flags")
val db = FirebaseFirestore.getInstance()
val document = db.collection(FEATURE_FLIPPING_COLLECTION)
.get()
Expand All @@ -37,9 +55,14 @@ class FirebaseService {
featureFlippingList[featureFlippingList.indexOf(featureFlipping)] = featureFlipping
}
}
CrashlyticsUtils.logEvent("Firebase: Feature flags " +
"récupérés avec succès (${featureFlippingList.size} éléments)")
return featureFlippingList
} catch (firebaseException: FirebaseException) {
Log.e("fetchFeatureFlipping", "Error getting documents: $firebaseException")
CrashlyticsUtils.logEvent("Firebase: Erreur lors " +
"de la récupération des feature flags")
CrashlyticsUtils.recordException(firebaseException)
return emptyList()
}
}
Expand All @@ -50,6 +73,7 @@ class FirebaseService {
val defaultSystemOfZone = ZoneId.systemDefault()

try {
CrashlyticsUtils.logEvent("Firebase: Récupération des newsletters")
db.collection(NEWSLETTERS_COLLECTION)
.get()
.await()
Expand All @@ -73,8 +97,13 @@ class FirebaseService {
)
newslettersList.add(newsletter)
}
CrashlyticsUtils.logEvent("Firebase: Newsletters " +
"récupérées avec succès (${newslettersList.size} éléments)")
} catch (firebaseException: FirebaseException) {
Log.d("fetchNewsletters", "Error getting documents: ", firebaseException)
CrashlyticsUtils.logEvent("Firebase: Erreur lors de " +
"la récupération des newsletters")
CrashlyticsUtils.recordException(firebaseException)
}

return newslettersList.sortedByDescending { it.date }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.xpeho.xpeapp.data.model.qvst.QvstCampaign
import com.xpeho.xpeapp.data.model.qvst.QvstProgress
import com.xpeho.xpeapp.data.model.qvst.QvstQuestion
import com.xpeho.xpeapp.data.model.user.UpdatePasswordResult
import com.xpeho.xpeapp.utils.CrashlyticsUtils
import retrofit2.HttpException
import java.net.ConnectException
import java.net.SocketTimeoutException
Expand Down Expand Up @@ -126,6 +127,8 @@ class WordpressRepository(
username: String,
onlyActive: Boolean = false
): List<QvstCampaignEntity>? {
CrashlyticsUtils.setCurrentFeature("qvst")
CrashlyticsUtils.setCustomKey("qvst_only_active", onlyActive.toString())
handleServiceExceptions(
tryBody = {
val campaigns = if (onlyActive) {
Expand Down Expand Up @@ -403,11 +406,25 @@ class WordpressRepository(
catchBody: (Exception) -> T
): T {
return try {
tryBody()
val result = tryBody()
// Crashlytics : Log de succès pour les appels API
CrashlyticsUtils.logEvent("API WordPress: Appel réussi")
CrashlyticsUtils.setCurrentFeature("api_wordpress")
result
} catch (e: Exception) {
if (isNetworkError(e)) {
// Crashlytics : Log des erreurs réseau
CrashlyticsUtils.logEvent("API WordPress: Erreur réseau - ${e.javaClass.simpleName}")
CrashlyticsUtils.recordException(e)
CrashlyticsUtils.setCustomKey("last_api_error", e.message ?: "Erreur inconnue")
CrashlyticsUtils.setCustomKey("last_api_error_time", System.currentTimeMillis().toString())
CrashlyticsUtils.setCurrentFeature("api_wordpress_error")
catchBody(e)
} else {
// Crashlytics : Log des erreurs non-réseau (généralement plus graves)
CrashlyticsUtils.logEvent("API WordPress: Erreur critique - ${e.javaClass.simpleName}")
CrashlyticsUtils.recordException(e)
CrashlyticsUtils.setCurrentFeature("api_wordpress_critical")
throw e
}
}
Expand Down
48 changes: 46 additions & 2 deletions app/src/main/java/com/xpeho/xpeapp/domain/AuthenticationManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.xpeho.xpeapp.data.model.WordpressToken
import com.xpeho.xpeapp.data.service.FirebaseService
import com.xpeho.xpeapp.data.service.WordpressRepository
import com.xpeho.xpeapp.di.TokenProvider
import com.xpeho.xpeapp.utils.CrashlyticsUtils
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -92,6 +93,15 @@ class AuthenticationManager(
}

suspend fun login(username: String, password: String): AuthResult<WordpressToken> = coroutineScope {
// Crashlytics : Contexte de connexion
CrashlyticsUtils.setCurrentScreen("login")
CrashlyticsUtils.setCurrentFeature("authentication")
CrashlyticsUtils.setUserContext(isLoggedIn = false)

// Crashlytics : Log de la tentative de connexion
CrashlyticsUtils.logEvent("Tentative de connexion pour: $username")
CrashlyticsUtils.setCustomKey("last_login_attempt", System.currentTimeMillis().toString())

val wpDefRes = async {
wordpressRepo.authenticate(AuthentificationBody(username, password))
}
Expand All @@ -103,14 +113,25 @@ class AuthenticationManager(
},
catchBody = { e ->
Log.e("AuthenticationManager: login", "Network error: ${e.message}")
// Crashlytics : Enregistrer l'erreur réseau
CrashlyticsUtils.recordException(e)
return@async AuthResult.NetworkError
}
)
}

val result = when (val wpRes = wpDefRes.await()) {
is AuthResult.NetworkError -> AuthResult.NetworkError
is AuthResult.Unauthorized -> AuthResult.Unauthorized
is AuthResult.NetworkError -> {
// Crashlytics : Log erreur réseau
CrashlyticsUtils.logEvent("Erreur réseau lors de la connexion pour: $username")
AuthResult.NetworkError
}
is AuthResult.Unauthorized -> {
// Crashlytics : Log erreur d'authentification
CrashlyticsUtils.logEvent("Erreur d'authentification pour: $username")
CrashlyticsUtils.setCustomKey("last_failed_login", username)
AuthResult.Unauthorized
}
else -> {
val fbRes = fbDefRes.await()
when (fbRes) {
Expand All @@ -121,6 +142,15 @@ class AuthenticationManager(
writeAuthentication(authData)
_authState.value = AuthState.Authenticated(authData)
tokenProvider.set("Bearer ${authData.token.token}")

// Crashlytics : Log connexion réussie
CrashlyticsUtils.logEvent("Connexion réussie pour: $username")
CrashlyticsUtils.setUserId(authData.token.userEmail)
CrashlyticsUtils.setCustomKey("user_email", authData.token.userEmail)
CrashlyticsUtils.setCustomKey("last_successful_login", System.currentTimeMillis().toString())
CrashlyticsUtils.setUserContext(isLoggedIn = true)
CrashlyticsUtils.setCurrentScreen("home")

wpRes
}
}
Expand All @@ -140,10 +170,24 @@ class AuthenticationManager(
}

suspend fun logout() {
// Crashlytics : Contexte de déconnexion
CrashlyticsUtils.setCurrentFeature("authentication")
CrashlyticsUtils.setCurrentScreen("logout")

// Crashlytics : Log de la déconnexion
val currentUser = getAuthData()?.token?.userEmail ?: "utilisateur_inconnu"
CrashlyticsUtils.logEvent("Déconnexion de l'utilisateur: $currentUser")

firebaseService.signOut()
datastorePref.clearAuthData()
datastorePref.setWasConnectedLastTime(false)
_authState.value = AuthState.Unauthenticated

// Crashlytics : Nettoyer les infos utilisateur
CrashlyticsUtils.setUserId("")
CrashlyticsUtils.setCustomKey("user_email", "")
CrashlyticsUtils.setUserContext(isLoggedIn = false)
CrashlyticsUtils.setCurrentScreen("login")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.google.firebase.FirebaseException
import com.xpeho.xpeapp.BuildConfig
import com.xpeho.xpeapp.data.FeatureFlippingEnum
import com.xpeho.xpeapp.data.service.FirebaseService
import com.xpeho.xpeapp.utils.CrashlyticsUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -41,12 +42,18 @@ class FeatureFlippingManager(
}

private suspend fun fetchData() {
CrashlyticsUtils.logEvent("FeatureFlipping: " +
"Récupération des feature flags")
val featureFlippingList = try {
firebaseService.fetchFeatureFlipping()
} catch (e: IOException) {
CrashlyticsUtils.logEvent("FeatureFlipping: Erreur réseau")
CrashlyticsUtils.recordException(e)
_featuresState.value = FeatureFlippingState.ERROR("Network error: ${e.message}")
return
} catch (e: FirebaseException) {
CrashlyticsUtils.logEvent("FeatureFlipping: Erreur Firebase")
CrashlyticsUtils.recordException(e)
_featuresState.value = FeatureFlippingState.ERROR("Firebase error: ${e.message}")
return
}
Expand All @@ -61,6 +68,9 @@ class FeatureFlippingManager(
}
}

CrashlyticsUtils.logEvent("FeatureFlipping: Configuration " +
"chargée avec succès (${featureEnabled.size} features)")
CrashlyticsUtils.setCustomKey("feature_flags_loaded", featureEnabled.size.toString())
_featuresState.value = FeatureFlippingState.SUCCESS(featureEnabled.toMap())
}
}
Expand Down
59 changes: 59 additions & 0 deletions app/src/main/java/com/xpeho/xpeapp/utils/CrashlyticsUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.xpeho.xpeapp.utils

import com.google.firebase.crashlytics.FirebaseCrashlytics

object CrashlyticsUtils {

/**
* Enregistre un événement personnalisé dans Crashlytics
*/
fun logEvent(message: String) {
FirebaseCrashlytics.getInstance().log(message)
}

/**
* Enregistre une exception non fatale dans Crashlytics
*/
fun recordException(exception: Throwable) {
FirebaseCrashlytics.getInstance().recordException(exception)
}

/**
* Définit un identifiant utilisateur pour les crash reports
*/
fun setUserId(userId: String) {
FirebaseCrashlytics.getInstance().setUserId(userId)
}

/**
* Ajoute une clé-valeur personnalisée aux crash reports
*/
fun setCustomKey(key: String, value: String) {
FirebaseCrashlytics.getInstance().setCustomKey(key, value)
}

/**
* Définit le screen/écran actuel pour contextualiser les erreurs
*/
fun setCurrentScreen(screenName: String) {
FirebaseCrashlytics.getInstance().setCustomKey("screen", screenName)
logEvent("Navigation vers: $screenName")
}

/**
* Définit la feature/fonctionnalité actuelle
*/
fun setCurrentFeature(featureName: String) {
FirebaseCrashlytics.getInstance().setCustomKey("feature", featureName)
}

/**
* Définit le contexte utilisateur (connecté/déconnecté)
*/
fun setUserContext(isLoggedIn: Boolean, userRole: String = "") {
FirebaseCrashlytics.getInstance().setCustomKey("user_logged_in", isLoggedIn.toString())
if (userRole.isNotEmpty()) {
FirebaseCrashlytics.getInstance().setCustomKey("user_role", userRole)
}
}
}
Loading
Loading