82 lines
3.6 KiB
Kotlin
82 lines
3.6 KiB
Kotlin
package com.jttools.datacontrol.remote
|
|
|
|
import android.content.Context
|
|
import com.google.firebase.FirebaseApp
|
|
import com.google.firebase.FirebaseOptions
|
|
import com.google.firebase.auth.FirebaseAuth
|
|
import com.google.firebase.firestore.FirebaseFirestore
|
|
import com.jttools.datacontrol.BuildConfig
|
|
import com.jttools.datacontrol.log.AppLog
|
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
import kotlinx.coroutines.flow.asStateFlow
|
|
import kotlinx.coroutines.tasks.await
|
|
import javax.inject.Inject
|
|
import javax.inject.Singleton
|
|
|
|
/** État du lien Firebase : [generation] change à chaque (ré)initialisation, pour relancer les écoutes. */
|
|
data class GatewayState(val generation: Int = 0, val enabled: Boolean = false, val projectId: String? = null)
|
|
|
|
/**
|
|
* Accès à Firebase (Auth anonyme + Firestore). La configuration se saisit dans l'application
|
|
* (Réglages → Contrôle parental) et peut changer à tout moment ; à défaut, on retombe sur
|
|
* `firebase.properties` (compilation). Sans configuration, [enabled] est faux et tout reste inerte.
|
|
*/
|
|
@Singleton
|
|
class FirebaseGateway @Inject constructor(
|
|
@ApplicationContext private val context: Context
|
|
) {
|
|
private val _state = MutableStateFlow(GatewayState())
|
|
val state: StateFlow<GatewayState> = _state.asStateFlow()
|
|
val enabled: Boolean get() = _state.value.enabled
|
|
|
|
@Volatile private var app: FirebaseApp? = null
|
|
private var currentKey: String? = null
|
|
|
|
private fun buildConfigFallback(): FirebaseConfig? =
|
|
if (BuildConfig.FIREBASE_API_KEY.isNotBlank() && BuildConfig.FIREBASE_APP_ID.isNotBlank() &&
|
|
BuildConfig.FIREBASE_PROJECT_ID.isNotBlank()
|
|
) FirebaseConfig(BuildConfig.FIREBASE_API_KEY, BuildConfig.FIREBASE_APP_ID, BuildConfig.FIREBASE_PROJECT_ID)
|
|
else null
|
|
|
|
/** Applique la configuration saisie [saved] (ou, à défaut, celle de compilation). Sans effet si elle n'a pas changé. */
|
|
@Synchronized
|
|
fun apply(saved: FirebaseConfig?) {
|
|
val cfg = saved ?: buildConfigFallback()
|
|
val key = cfg?.let { "${it.apiKey}|${it.appId}|${it.projectId}" }
|
|
if (key == currentKey && (key == null || app != null)) return
|
|
|
|
app?.let { runCatching { it.delete() } }
|
|
app = null
|
|
currentKey = key
|
|
if (cfg != null) {
|
|
app = runCatching {
|
|
FirebaseApp.initializeApp(
|
|
context,
|
|
FirebaseOptions.Builder()
|
|
.setApiKey(cfg.apiKey)
|
|
.setApplicationId(cfg.appId)
|
|
.setProjectId(cfg.projectId)
|
|
.build()
|
|
)
|
|
}.onFailure { AppLog.e("Firebase", "Initialisation impossible", it) }.getOrNull()
|
|
}
|
|
val ok = app != null
|
|
AppLog.i("Firebase", if (ok) "Configuré (projet ${cfg?.projectId})" else "Non configuré")
|
|
_state.value = GatewayState(_state.value.generation + 1, ok, cfg?.projectId?.takeIf { ok })
|
|
}
|
|
|
|
val db: FirebaseFirestore? get() = app?.let { FirebaseFirestore.getInstance(it) }
|
|
private val auth: FirebaseAuth? get() = app?.let { FirebaseAuth.getInstance(it) }
|
|
|
|
/** Identifiant de l'appareil côté Firebase (connexion anonyme, persistante). Null si indisponible. */
|
|
suspend fun ensureSignedIn(): String? {
|
|
val a = auth ?: return null
|
|
a.currentUser?.let { return it.uid }
|
|
return runCatching { a.signInAnonymously().await().user?.uid }
|
|
.onFailure { AppLog.w("Firebase", "Connexion anonyme impossible : ${it.message}") }
|
|
.getOrNull()
|
|
}
|
|
}
|