Skip to content

Commit

Permalink
Conferences Desktop (#1274)
Browse files Browse the repository at this point in the history
* Enable conferences desktop app with firebase

* Include runtime dependencies

* Apply title to window

* Apply lint style fixes

---------

Co-authored-by: Ashley Davies <[email protected]>
  • Loading branch information
ashdavies and ashdavies authored Oct 26, 2024
1 parent 30e5f28 commit 18fd036
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ private fun findExplicitProjectNumber(): String? = BuildConfig.FIREBASE_ANDROID_
?.let { it.split(":")[1] }
?: fetchProjectNumber()

private inline fun <reified T : GoogleCredentials, R> googleCredentials(transform: (T) -> R): R? = (GoogleCredentials.getApplicationDefault() as? T)?.let(transform)
private inline fun <reified T : GoogleCredentials, R> googleCredentials(transform: (T) -> R): R? {
return (GoogleCredentials.getApplicationDefault() as? T)?.let(transform)
}

private fun fetchProjectId(): String? = fetchComputeMetadataBlocking(path = "project/project-id")

Expand Down
24 changes: 23 additions & 1 deletion conferences-app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat

plugins {
id("com.android.application")
id("com.google.gms.google-services")
Expand Down Expand Up @@ -78,10 +80,24 @@ android {
buildConfig {
val androidApiKey by stringPropertyWithTag(::buildConfigField)
val androidStrictMode by booleanPropertyWithTag(::buildConfigField)
val browserApiKey by stringPropertyWithTag(::buildConfigField)

packageName.set(android.namespace)
}

compose.desktop {
application {
mainClass = "io.ashdavies.party.MainKt"

nativeDistributions {
targetFormats(TargetFormat.Deb, TargetFormat.Dmg)

packageName = "ConferencesApp"
packageVersion = "1.0.0"
}
}
}

kotlin {
sourceSets {
commonMain.dependencies {
Expand Down Expand Up @@ -111,7 +127,6 @@ kotlin {
implementation(libs.androidx.paging.common)
implementation(libs.coil.compose)
implementation(libs.coil.network)
implementation(libs.google.android.material)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization.core)
Expand All @@ -134,6 +149,7 @@ kotlin {

androidMain.dependencies {
implementation(libs.androidx.activity.compose)
implementation(libs.google.android.material)
}

val androidDebug by registering {
Expand All @@ -148,7 +164,13 @@ kotlin {
}

jvmMain.dependencies {
implementation(projects.keyNavigation)
implementation(compose.desktop.currentOs)
implementation(libs.gitlive.firebase.app)
implementation(libs.gitlive.firebase.config)

runtimeOnly(libs.kotlinx.coroutines.swing)
runtimeOnly(libs.slf4j.simple)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ internal class MainActivity : ComponentActivity() {
}

setContent {
LauncherApp()
ConferencesApp()
}
}
}

@Composable
private fun LauncherApp(context: Context = LocalContext.current) {
private fun ConferencesApp(context: Context = LocalContext.current) {
ProvideHttpClient(
config = {
install(DefaultRequest) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.ashdavies.party.config
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
import io.ashdavies.config.LocalRemoteConfig
import io.ashdavies.config.RemoteConfig
import io.ashdavies.config.getBoolean

Expand All @@ -12,7 +13,7 @@ internal suspend fun RemoteConfig.isHomeEnabled() = getBoolean("home_enabled")

@Composable
internal fun booleanConfigAsState(
remoteConfig: RemoteConfig = RemoteConfig,
remoteConfig: RemoteConfig = LocalRemoteConfig.current,
initialValue: Boolean = false,
factory: suspend RemoteConfig.() -> Boolean,
): State<Boolean> = produceState(initialValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.paging.PagingConfig
import app.cash.sqldelight.Transacter
import io.ashdavies.aggregator.AsgConference
import io.ashdavies.aggregator.UpcomingConferencesCallable
import io.ashdavies.config.LocalRemoteConfig
import io.ashdavies.config.RemoteConfig
import io.ashdavies.config.getBoolean
import io.ashdavies.http.LocalHttpClient
Expand Down Expand Up @@ -59,7 +60,7 @@ internal fun rememberEventPager(
@Composable
private fun rememberUpcomingEventsCallable(
httpClient: HttpClient = LocalHttpClient.current,
remoteConfig: RemoteConfig = RemoteConfig,
remoteConfig: RemoteConfig = LocalRemoteConfig.current,
): PagedUpcomingEventsCallable {
val pagedCallable by lazy { PagedUpcomingEventsCallable(httpClient, PLAYGROUND_BASE_URL) }
val asgCallable by lazy { UpcomingConferencesCallable(httpClient) }
Expand Down
111 changes: 111 additions & 0 deletions conferences-app/src/jvmMain/kotlin/io/ashdavies/party/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package io.ashdavies.party

import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import com.slack.circuit.backstack.rememberSaveableBackStack
import com.slack.circuit.foundation.CircuitCompositionLocals
import com.slack.circuit.foundation.NavigableCircuitContent
import com.slack.circuit.foundation.rememberCircuitNavigator
import com.slack.circuit.overlay.ContentWithOverlays
import io.ashdavies.analytics.LocalAnalytics
import io.ashdavies.analytics.RemoteAnalytics
import io.ashdavies.check.ProvideAppCheckToken
import io.ashdavies.config.LocalConfigValue
import io.ashdavies.config.LocalRemoteConfig
import io.ashdavies.config.RemoteConfig
import io.ashdavies.content.PlatformContext
import io.ashdavies.http.ProvideHttpClient
import io.ashdavies.http.publicStorage
import io.ashdavies.io.resolveCacheDir
import io.ashdavies.material.dynamicColorScheme
import io.ashdavies.party.config.rememberCircuit
import io.ashdavies.party.firebase.EmptyLocalConfigValue
import io.ashdavies.party.home.HomeScreen
import io.ashdavies.playground.BuildConfig
import io.ashdavies.playground.KeyNavigationDecoration
import io.ashdavies.playground.PlaygroundDatabase
import io.ashdavies.sql.ProvideTransacter
import io.ashdavies.sql.rememberTransacter
import io.ktor.client.plugins.DefaultRequest
import io.ktor.client.plugins.cache.HttpCache
import io.ktor.client.request.header

public fun main() {
application {
Window(
onCloseRequest = ::exitApplication,
title = "Conferences",
) {
ConferencesApp(
context = PlatformContext.Default,
onClose = ::exitApplication,
)
}
}
}

@Composable
private fun ConferencesApp(
context: PlatformContext,
onClose: () -> Unit,
) {
ProvideHttpClient(
config = {
install(DefaultRequest) {
header("User-Agent", System.getProperty("os.name"))
header("X-API-Key", BuildConfig.BROWSER_API_KEY)
}

install(HttpCache) {
publicStorage(context.resolveCacheDir())
}
},
) {
ProvideRemoteLocals {
ProvideAppCheckToken {
val transacter = rememberTransacter(
schema = PlaygroundDatabase.Schema,
context = context,
) { PlaygroundDatabase(it) }

ProvideTransacter(transacter) {
MaterialTheme(dynamicColorScheme()) {
val circuit = rememberCircuit(context)

CircuitCompositionLocals(circuit) {
ContentWithOverlays {
val backStack = rememberSaveableBackStack(HomeScreen)

NavigableCircuitContent(
navigator = rememberCircuitNavigator(backStack) { onClose() },
backStack = backStack,
decoration = KeyNavigationDecoration(
decoration = circuit.defaultNavDecoration,
onBackInvoked = backStack::pop,
),
)
}
}
}
}
}
}
}
}

@Composable
private fun ProvideRemoteLocals(content: @Composable () -> Unit) {
CompositionLocalProvider(
LocalAnalytics provides RemoteAnalytics { _, _ -> },
LocalRemoteConfig provides object : RemoteConfig {
override suspend fun <T : Any> getValue(
key: String,
transform: (LocalConfigValue) -> T,
): T = transform(EmptyLocalConfigValue)
},
content = content,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.ashdavies.party.firebase

import io.ashdavies.config.LocalConfigValue

internal object EmptyLocalConfigValue : LocalConfigValue {
override fun asLong() = 0L
override fun asDouble() = 0.0
override fun asString() = ""
override fun asByteArray() = byteArrayOf()
override fun asBoolean() = false
}
1 change: 1 addition & 0 deletions remote-config/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
plugins {
id("io.ashdavies.compose")
id("io.ashdavies.default")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.ashdavies.config

import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.remoteconfig.FirebaseRemoteConfig
import dev.gitlive.firebase.remoteconfig.FirebaseRemoteConfigValue
import dev.gitlive.firebase.remoteconfig.remoteConfig

private val FirebaseRemoteConfig by lazy { Firebase.remoteConfig }

internal class LazyRemoteConfig : RemoteConfig {
override suspend fun <T : Any> getValue(key: String, transform: (LocalConfigValue) -> T): T {
return transform(LocalConfigValue(FirebaseRemoteConfig.getInitializedValue(key)))
}
}

private suspend fun FirebaseRemoteConfig.getInitializedValue(key: String): FirebaseRemoteConfigValue {
ensureInitialized()
return getValue(key)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.ashdavies.config

import dev.gitlive.firebase.remoteconfig.FirebaseRemoteConfigValue

public interface LocalConfigValue {
public fun asLong(): Long
public fun asDouble(): Double
public fun asString(): String
public fun asByteArray(): ByteArray
public fun asBoolean(): Boolean
}

internal fun LocalConfigValue(value: FirebaseRemoteConfigValue): LocalConfigValue {
return object : LocalConfigValue {
override fun asLong(): Long = value.asLong()
override fun asDouble(): Double = value.asDouble()
override fun asString(): String = value.asString()
override fun asByteArray(): ByteArray = value.asByteArray()
override fun asBoolean(): Boolean = value.asBoolean()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.ashdavies.config

import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.staticCompositionLocalOf

public val LocalRemoteConfig: ProvidableCompositionLocal<RemoteConfig> = staticCompositionLocalOf {
LazyRemoteConfig()
}
Original file line number Diff line number Diff line change
@@ -1,42 +1,7 @@
package io.ashdavies.config

import com.google.firebase.remoteconfig.ConfigUpdate
import com.google.firebase.remoteconfig.ConfigUpdateListener
import com.google.firebase.remoteconfig.FirebaseRemoteConfigException
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.remoteconfig.FirebaseRemoteConfigValue
import dev.gitlive.firebase.remoteconfig.android
import dev.gitlive.firebase.remoteconfig.get
import dev.gitlive.firebase.remoteconfig.remoteConfig

public interface RemoteConfig {

public suspend fun <T : Any> getValue(
key: String,
transform: (FirebaseRemoteConfigValue) -> T,
): T

public companion object Default : RemoteConfig {

private val remoteConfig = Firebase.remoteConfig

init {
remoteConfig.android.addOnConfigUpdateListener(
object : ConfigUpdateListener {
override fun onUpdate(configUpdate: ConfigUpdate) = Unit
override fun onError(error: FirebaseRemoteConfigException) = Unit
},
)
}

override suspend fun <T : Any> getValue(
key: String,
transform: (FirebaseRemoteConfigValue) -> T,
): T {
remoteConfig.ensureInitialized()
return transform(remoteConfig[key])
}
}
public suspend fun <T : Any> getValue(key: String, transform: (LocalConfigValue) -> T): T
}

public suspend fun RemoteConfig.getBoolean(key: String): Boolean {
Expand Down

0 comments on commit 18fd036

Please sign in to comment.