feat(app-check): stable debug token via BuildConfig; feat(firestore): indexes for questions + bucket_list

This commit is contained in:
null 2026-06-23 12:17:17 -05:00
parent 6977db7600
commit e5c13b6b6d
4 changed files with 95 additions and 22 deletions

View File

@ -57,7 +57,14 @@ android {
}
buildTypes {
debug {
// Stable debug token registered in Firebase Console > App Check.
// Pre-seeded into SharedPreferences by FirebaseInitializer so all installs
// use the same token without manual re-registration.
buildConfigField("String", "APP_CHECK_DEBUG_TOKEN", "\"e2dc8256-403f-449b-846e-76614a7297cc\"")
}
release {
buildConfigField("String", "APP_CHECK_DEBUG_TOKEN", "\"\"")
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = true
isShrinkResources = true

View File

@ -1,19 +1,25 @@
package app.closer.core.firebase
import app.closer.BuildConfig // generated by buildFeatures { buildConfig = true }
import android.content.Context
import app.closer.BuildConfig
import com.google.firebase.FirebaseApp
import com.google.firebase.appcheck.FirebaseAppCheck
import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class FirebaseInitializer @Inject constructor() {
class FirebaseInitializer @Inject constructor(
@ApplicationContext private val context: Context
) {
fun initialize() {
val appCheck = FirebaseAppCheck.getInstance()
if (BuildConfig.DEBUG) {
// DebugAppCheckProviderFactory is in the debug artifact only;
// referenced by name to avoid a compile-time dep in release.
// Pre-seed the stable registered debug token so every install (emulator or device)
// uses the same token without needing re-registration in Firebase Console.
seedDebugToken()
try {
val cls = Class.forName(
"com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory"
@ -32,4 +38,19 @@ class FirebaseInitializer @Inject constructor() {
)
}
}
private fun seedDebugToken() {
val token = BuildConfig.APP_CHECK_DEBUG_TOKEN
if (token.isBlank()) return
try {
val persistenceKey = FirebaseApp.getInstance().persistenceKey
val prefsFile = "com.google.firebase.appcheck.debug.store.$persistenceKey"
context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE)
.edit()
.putString("com.google.firebase.appcheck.debug.DEBUG_SECRET", token)
.commit()
} catch (_: Exception) {
// Best effort — debug provider will generate and log a new token if this fails.
}
}
}

View File

@ -15,6 +15,22 @@
{ "fieldPath": "status", "order": "ASCENDING" },
{ "fieldPath": "unlockAt", "order": "ASCENDING" }
]
},
{
"collectionGroup": "questions",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "active", "order": "ASCENDING" },
{ "fieldPath": "isPremium", "order": "ASCENDING" }
]
},
{
"collectionGroup": "bucket_list",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "category", "order": "ASCENDING" },
{ "fieldPath": "addedAt", "order": "DESCENDING" }
]
}
],
"fieldOverrides": [

View File

@ -3,32 +3,38 @@ import SwiftUI
// MARK: - Theme
extension Color {
private static func adaptive(light: String, dark: String) -> Color {
Color(UIColor { traitCollection in
UIColor(hex: traitCollection.userInterfaceStyle == .dark ? dark : light)
})
}
// Primary palette
static let closerPrimary = Color(hex: "B98AF4")
static let closerSecondary = Color(hex: "E7A2D1")
static let closerBackground = Color(hex: "FFFBFE")
static let closerSurface = Color(hex: "F5F0FF")
static let closerPrimary = Color.adaptive(light: "B98AF4", dark: "CFA7FF")
static let closerSecondary = Color.adaptive(light: "E7A2D1", dark: "FFAFD9")
static let closerBackground = Color.adaptive(light: "FFFBFE", dark: "18111E")
static let closerSurface = Color.adaptive(light: "F5F0FF", dark: "211729")
static let closerOnPrimary = Color.white
static let closerText = Color(hex: "1C1B1F")
static let closerTextSecondary = Color(hex: "49454F")
static let closerDivider = Color(hex: "E6E0E9")
static let closerText = Color.adaptive(light: "1C1B1F", dark: "F2E8F6")
static let closerTextSecondary = Color.adaptive(light: "49454F", dark: "D9C8E2")
static let closerDivider = Color.adaptive(light: "E6E0E9", dark: "5A4666")
// Semantic
static let closerSuccess = Color(hex: "4CAF50")
static let closerWarning = Color(hex: "FF9800")
static let closerDanger = Color(hex: "F44336")
static let closerGold = Color(hex: "FFD700")
static let closerSuccess = Color.adaptive(light: "4CAF50", dark: "8DD99B")
static let closerWarning = Color.adaptive(light: "FF9800", dark: "FFC46B")
static let closerDanger = Color.adaptive(light: "F44336", dark: "FFB3BA")
static let closerGold = Color.adaptive(light: "FFD700", dark: "FFE680")
// Category colors
static let categoryCommunication = Color(hex: "B98AF4")
static let categoryIntimacy = Color(hex: "E7A2D1")
static let categoryFun = Color(hex: "FFB74D")
static let categoryGoals = Color(hex: "81C784")
static let categoryAdventure = Color(hex: "64B5F6")
static let categoryCommunication = Color.adaptive(light: "B98AF4", dark: "CFA7FF")
static let categoryIntimacy = Color.adaptive(light: "E7A2D1", dark: "FFAFD9")
static let categoryFun = Color.adaptive(light: "FFB74D", dark: "FFD38A")
static let categoryGoals = Color.adaptive(light: "81C784", dark: "A6DFA8")
static let categoryAdventure = Color.adaptive(light: "64B5F6", dark: "9AD1FF")
// Streak
static let streakActive = Color(hex: "FF6B6B")
static let streakInactive = Color(hex: "E0E0E0")
static let streakActive = Color.adaptive(light: "FF6B6B", dark: "FF9A9A")
static let streakInactive = Color.adaptive(light: "E0E0E0", dark: "5A4666")
init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
@ -53,6 +59,29 @@ extension Color {
}
}
private extension UIColor {
convenience init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int: UInt64 = 0
Scanner(string: hex).scanHexInt64(&int)
let a, r, g, b: UInt64
switch hex.count {
case 6:
(a, r, g, b) = (255, (int >> 16) & 0xFF, (int >> 8) & 0xFF, int & 0xFF)
case 8:
(a, r, g, b) = ((int >> 24) & 0xFF, (int >> 16) & 0xFF, (int >> 8) & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (255, 0, 0, 0)
}
self.init(
red: Double(r) / 255,
green: Double(g) / 255,
blue: Double(b) / 255,
alpha: Double(a) / 255
)
}
}
// MARK: - Typography
enum CloserFont {