feat(app-check): stable debug token via BuildConfig; feat(firestore): indexes for questions + bucket_list
This commit is contained in:
parent
6977db7600
commit
e5c13b6b6d
|
|
@ -57,7 +57,14 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
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 {
|
release {
|
||||||
|
buildConfigField("String", "APP_CHECK_DEBUG_TOKEN", "\"\"")
|
||||||
signingConfig = signingConfigs.getByName("release")
|
signingConfig = signingConfigs.getByName("release")
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
isShrinkResources = true
|
isShrinkResources = true
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,25 @@
|
||||||
package app.closer.core.firebase
|
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.FirebaseAppCheck
|
||||||
import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory
|
import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class FirebaseInitializer @Inject constructor() {
|
class FirebaseInitializer @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context
|
||||||
|
) {
|
||||||
|
|
||||||
fun initialize() {
|
fun initialize() {
|
||||||
val appCheck = FirebaseAppCheck.getInstance()
|
val appCheck = FirebaseAppCheck.getInstance()
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
// DebugAppCheckProviderFactory is in the debug artifact only;
|
// Pre-seed the stable registered debug token so every install (emulator or device)
|
||||||
// referenced by name to avoid a compile-time dep in release.
|
// uses the same token without needing re-registration in Firebase Console.
|
||||||
|
seedDebugToken()
|
||||||
try {
|
try {
|
||||||
val cls = Class.forName(
|
val cls = Class.forName(
|
||||||
"com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory"
|
"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.
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,22 @@
|
||||||
{ "fieldPath": "status", "order": "ASCENDING" },
|
{ "fieldPath": "status", "order": "ASCENDING" },
|
||||||
{ "fieldPath": "unlockAt", "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": [
|
"fieldOverrides": [
|
||||||
|
|
|
||||||
|
|
@ -3,32 +3,38 @@ import SwiftUI
|
||||||
// MARK: - Theme
|
// MARK: - Theme
|
||||||
|
|
||||||
extension Color {
|
extension Color {
|
||||||
|
private static func adaptive(light: String, dark: String) -> Color {
|
||||||
|
Color(UIColor { traitCollection in
|
||||||
|
UIColor(hex: traitCollection.userInterfaceStyle == .dark ? dark : light)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Primary palette
|
// Primary palette
|
||||||
static let closerPrimary = Color(hex: "B98AF4")
|
static let closerPrimary = Color.adaptive(light: "B98AF4", dark: "CFA7FF")
|
||||||
static let closerSecondary = Color(hex: "E7A2D1")
|
static let closerSecondary = Color.adaptive(light: "E7A2D1", dark: "FFAFD9")
|
||||||
static let closerBackground = Color(hex: "FFFBFE")
|
static let closerBackground = Color.adaptive(light: "FFFBFE", dark: "18111E")
|
||||||
static let closerSurface = Color(hex: "F5F0FF")
|
static let closerSurface = Color.adaptive(light: "F5F0FF", dark: "211729")
|
||||||
static let closerOnPrimary = Color.white
|
static let closerOnPrimary = Color.white
|
||||||
static let closerText = Color(hex: "1C1B1F")
|
static let closerText = Color.adaptive(light: "1C1B1F", dark: "F2E8F6")
|
||||||
static let closerTextSecondary = Color(hex: "49454F")
|
static let closerTextSecondary = Color.adaptive(light: "49454F", dark: "D9C8E2")
|
||||||
static let closerDivider = Color(hex: "E6E0E9")
|
static let closerDivider = Color.adaptive(light: "E6E0E9", dark: "5A4666")
|
||||||
|
|
||||||
// Semantic
|
// Semantic
|
||||||
static let closerSuccess = Color(hex: "4CAF50")
|
static let closerSuccess = Color.adaptive(light: "4CAF50", dark: "8DD99B")
|
||||||
static let closerWarning = Color(hex: "FF9800")
|
static let closerWarning = Color.adaptive(light: "FF9800", dark: "FFC46B")
|
||||||
static let closerDanger = Color(hex: "F44336")
|
static let closerDanger = Color.adaptive(light: "F44336", dark: "FFB3BA")
|
||||||
static let closerGold = Color(hex: "FFD700")
|
static let closerGold = Color.adaptive(light: "FFD700", dark: "FFE680")
|
||||||
|
|
||||||
// Category colors
|
// Category colors
|
||||||
static let categoryCommunication = Color(hex: "B98AF4")
|
static let categoryCommunication = Color.adaptive(light: "B98AF4", dark: "CFA7FF")
|
||||||
static let categoryIntimacy = Color(hex: "E7A2D1")
|
static let categoryIntimacy = Color.adaptive(light: "E7A2D1", dark: "FFAFD9")
|
||||||
static let categoryFun = Color(hex: "FFB74D")
|
static let categoryFun = Color.adaptive(light: "FFB74D", dark: "FFD38A")
|
||||||
static let categoryGoals = Color(hex: "81C784")
|
static let categoryGoals = Color.adaptive(light: "81C784", dark: "A6DFA8")
|
||||||
static let categoryAdventure = Color(hex: "64B5F6")
|
static let categoryAdventure = Color.adaptive(light: "64B5F6", dark: "9AD1FF")
|
||||||
|
|
||||||
// Streak
|
// Streak
|
||||||
static let streakActive = Color(hex: "FF6B6B")
|
static let streakActive = Color.adaptive(light: "FF6B6B", dark: "FF9A9A")
|
||||||
static let streakInactive = Color(hex: "E0E0E0")
|
static let streakInactive = Color.adaptive(light: "E0E0E0", dark: "5A4666")
|
||||||
|
|
||||||
init(hex: String) {
|
init(hex: String) {
|
||||||
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
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
|
// MARK: - Typography
|
||||||
|
|
||||||
enum CloserFont {
|
enum CloserFont {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue