diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index b54955ce9..2f24a2bc6 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -16,7 +16,7 @@ val localProps = Properties().apply { val file = rootProject.file("local.properties") if (file.exists()) file.inputStream().use { this.load(it) } } -val localGithubClientId = (localProps.getProperty("GITHUB_CLIENT_ID") ?: "").trim() +val localGithubClientId = (localProps.getProperty("GITHUB_CLIENT_ID") ?: "Ov23linTY28VFpFjFiI9").trim() // Generate BuildConfig for JVM (Configuration Cache Compatible) val generateJvmBuildConfig = tasks.register("generateJvmBuildConfig") { @@ -103,6 +103,8 @@ kotlin { implementation(libs.androidx.datastore) implementation(libs.androidx.datastore.preferences) + + implementation(libs.liquid) } commonTest.dependencies { implementation(libs.kotlin.test) diff --git a/composeApp/release/baselineProfiles/0/composeApp-release.dm b/composeApp/release/baselineProfiles/0/composeApp-release.dm index 43948524f..f898ab9f6 100644 Binary files a/composeApp/release/baselineProfiles/0/composeApp-release.dm and b/composeApp/release/baselineProfiles/0/composeApp-release.dm differ diff --git a/composeApp/release/baselineProfiles/1/composeApp-release.dm b/composeApp/release/baselineProfiles/1/composeApp-release.dm index 264b94116..ee6e7b23e 100644 Binary files a/composeApp/release/baselineProfiles/1/composeApp-release.dm and b/composeApp/release/baselineProfiles/1/composeApp-release.dm differ diff --git a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/auth/data/repository/AuthRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/auth/data/repository/AuthRepositoryImpl.kt index e10acb696..3ea9ce4f8 100644 --- a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/auth/data/repository/AuthRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/auth/data/repository/AuthRepositoryImpl.kt @@ -50,7 +50,7 @@ class AuthRepositoryImpl( var remainingMs = timeoutMs var intervalMs = (start.intervalSec.coerceAtLeast(5)) * 1000L var consecutiveErrors = 0 - val maxConsecutiveErrors = 3 + val maxConsecutiveErrors = 5 Logger.d { "⏱️ Starting token polling. Expires in: ${start.expiresInSec}s, Interval: ${start.intervalSec}s" } @@ -61,14 +61,16 @@ class AuthRepositoryImpl( if (success != null) { Logger.d { "✅ Token received successfully!" } - tokenDataSource.save(success) + withRetry(maxAttempts = 3) { + tokenDataSource.save(success) + } return@withContext success } val error = res.exceptionOrNull() val msg = (error?.message ?: "").lowercase() - Logger.d { "📡 Poll response: $msg" } + Logger.d { "📡 Poll response: $msg (errors: $consecutiveErrors/$maxConsecutiveErrors)" } when { "authorization_pending" in msg -> { @@ -95,27 +97,37 @@ class AuthRepositoryImpl( ) } - "unable to resolve" in msg || "no address" in msg -> { + "unable to resolve" in msg || + "no address" in msg || + "failed to connect" in msg || + "connection refused" in msg || + "network is unreachable" in msg -> { consecutiveErrors++ + Logger.d { "⚠️ Network error, retrying... ($consecutiveErrors/$maxConsecutiveErrors)" } + + val backoffDelay = intervalMs * (1 + consecutiveErrors) + if (consecutiveErrors >= maxConsecutiveErrors) { throw Exception( - "Network connection lost during authentication. " + + "Network connection unstable during authentication. " + "Please check your connection and try again." ) } - Logger.d { "⚠️ Network error, retrying... ($consecutiveErrors/$maxConsecutiveErrors)" } - delay(intervalMs) - remainingMs -= intervalMs + delay(backoffDelay) + remainingMs -= backoffDelay } else -> { consecutiveErrors++ + Logger.d { "⚠️ Error: $msg (attempt $consecutiveErrors/$maxConsecutiveErrors)" } + if (consecutiveErrors >= maxConsecutiveErrors) { throw Exception("Authentication failed: $msg") } - Logger.d { "⚠️ Unknown error, retrying... ($consecutiveErrors/$maxConsecutiveErrors)" } - delay(intervalMs) - remainingMs -= intervalMs + + val backoffDelay = intervalMs * 2 + delay(backoffDelay) + remainingMs -= backoffDelay } } @@ -123,16 +135,20 @@ class AuthRepositoryImpl( throw e } catch (e: Exception) { Logger.d { "❌ Poll error: ${e.message}" } + Logger.d { "❌ Error type: ${e::class.simpleName}" } consecutiveErrors++ + if (consecutiveErrors >= maxConsecutiveErrors) { throw Exception( "Authentication failed after multiple attempts. " + - "Please try again.", + "Error: ${e.message}", e ) } - delay(intervalMs) - remainingMs -= intervalMs + + val backoffDelay = intervalMs * (1 + consecutiveErrors) + delay(backoffDelay) + remainingMs -= backoffDelay } } @@ -141,6 +157,22 @@ class AuthRepositoryImpl( ) } + private suspend fun withRetry( + maxAttempts: Int = 3, + initialDelay: Long = 1000, + block: suspend () -> T + ): T { + repeat(maxAttempts - 1) { attempt -> + try { + return block() + } catch (e: Exception) { + Logger.d { "⚠️ Retry attempt ${attempt + 1} failed: ${e.message}" } + delay(initialDelay * (attempt + 1)) + } + } + return block() + } + override suspend fun logout() { tokenDataSource.clear() } diff --git a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/DetailsRoot.kt b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/DetailsRoot.kt index 08bc8494b..e4517edbb 100644 --- a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/DetailsRoot.kt +++ b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/DetailsRoot.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CutCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.OpenInBrowser @@ -19,11 +20,16 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import io.github.fletchmckee.liquid.liquefiable +import io.github.fletchmckee.liquid.liquid +import io.github.fletchmckee.liquid.rememberLiquidState import org.jetbrains.compose.ui.tooling.preview.Preview import org.koin.compose.viewmodel.koinViewModel import zed.rainxch.githubstore.core.presentation.theme.GithubStoreTheme @@ -35,6 +41,7 @@ import zed.rainxch.githubstore.feature.details.presentation.components.sections. import zed.rainxch.githubstore.feature.details.presentation.components.sections.stats import zed.rainxch.githubstore.feature.details.presentation.components.sections.whatsNew import zed.rainxch.githubstore.feature.details.presentation.components.states.ErrorState +import zed.rainxch.githubstore.feature.details.presentation.utils.LocalTopbarLiquidState @Composable fun DetailsRoot( @@ -78,93 +85,106 @@ fun DetailsScreen( state: DetailsState, onAction: (DetailsAction) -> Unit, ) { - Scaffold( - topBar = { - TopAppBar( - title = { }, - navigationIcon = { - IconButton( - onClick = { - onAction(DetailsAction.OnNavigateBackClick) - } - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Navigate Back", - modifier = Modifier.size(24.dp) - ) - } - }, - actions = { - state.repository?.htmlUrl?.let { + val liquidTopbarState = rememberLiquidState() + + CompositionLocalProvider( + value = LocalTopbarLiquidState provides liquidTopbarState + ) { + Scaffold( + topBar = { + TopAppBar( + title = { }, + navigationIcon = { IconButton( onClick = { - onAction(DetailsAction.OpenRepoInBrowser) - }, + onAction(DetailsAction.OnNavigateBackClick) + } ) { Icon( - imageVector = Icons.Default.OpenInBrowser, - contentDescription = "Open repository", + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Navigate Back", modifier = Modifier.size(24.dp) ) } + }, + actions = { + state.repository?.htmlUrl?.let { + IconButton( + onClick = { + onAction(DetailsAction.OpenRepoInBrowser) + }, + ) { + Icon( + imageVector = Icons.Default.OpenInBrowser, + contentDescription = "Open repository", + modifier = Modifier.size(24.dp) + ) + } + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Transparent + ), + modifier = Modifier.liquid(liquidTopbarState) { + this.shape = CutCornerShape(0.dp) + this.frost = 20.dp } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.background ) - ) - }, - containerColor = MaterialTheme.colorScheme.background - ) { innerPadding -> - - if (state.isLoading) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() + }, + containerColor = MaterialTheme.colorScheme.background + ) { innerPadding -> + + if (state.isLoading) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + + return@Scaffold } - return@Scaffold - } + if (state.errorMessage != null) { + ErrorState(state.errorMessage, onAction) - if (state.errorMessage != null) { - ErrorState(state.errorMessage, onAction) + return@Scaffold + } - return@Scaffold - } + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + header( + state = state, + onAction = onAction, + ) - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(innerPadding), - contentPadding = PaddingValues(16.dp), - verticalArrangement = Arrangement.spacedBy(24.dp) - ) { - header(state, onAction) - - state.stats?.let { stats -> - stats(repoStats = stats) - } + state.stats?.let { stats -> + stats(repoStats = stats) + } - state.readmeMarkdown?.let { readmeMarkdown -> - about(readmeMarkdown) - } + state.readmeMarkdown?.let { readmeMarkdown -> + about(readmeMarkdown) + } - state.latestRelease?.let { latestRelease -> - whatsNew(latestRelease) - } + state.latestRelease?.let { latestRelease -> + whatsNew(latestRelease) + } - state.userProfile?.let { userProfile -> - author( - author = userProfile, - onAction = onAction - ) - } + state.userProfile?.let { userProfile -> + author( + author = userProfile, + onAction = onAction + ) + } - if (state.installLogs.isNotEmpty()) { - logs(state) + if (state.installLogs.isNotEmpty()) { + logs(state) + } } } } diff --git a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/SmartInstallButton.kt b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/SmartInstallButton.kt index a343ce423..1ada40e75 100644 --- a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/SmartInstallButton.kt +++ b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/SmartInstallButton.kt @@ -38,12 +38,14 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import io.github.fletchmckee.liquid.liquefiable import org.jetbrains.compose.ui.tooling.preview.Preview import zed.rainxch.githubstore.core.domain.model.Architecture import zed.rainxch.githubstore.core.domain.model.GithubAsset import zed.rainxch.githubstore.feature.details.presentation.DetailsAction import zed.rainxch.githubstore.feature.details.presentation.DetailsState import zed.rainxch.githubstore.feature.details.presentation.DownloadStage +import zed.rainxch.githubstore.feature.details.presentation.utils.LocalTopbarLiquidState import zed.rainxch.githubstore.feature.details.presentation.utils.extractArchitectureFromName import zed.rainxch.githubstore.feature.details.presentation.utils.isExactArchitectureMatch @@ -57,6 +59,8 @@ fun SmartInstallButton( modifier: Modifier = Modifier, state: DetailsState ) { + val liquidState = LocalTopbarLiquidState.current + val enabled = remember(primaryAsset, isDownloading, isInstalling) { primaryAsset != null && !isDownloading && !isInstalling } @@ -86,7 +90,8 @@ fun SmartInstallButton( onClick = { onAction(DetailsAction.InstallPrimary) } - ), + ) + .liquefiable(liquidState), colors = CardDefaults.elevatedCardColors( containerColor = if (enabled) { MaterialTheme.colorScheme.primary diff --git a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/sections/About.kt b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/sections/About.kt index 577cca98a..ee05386c6 100644 --- a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/sections/About.kt +++ b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/sections/About.kt @@ -16,12 +16,16 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.mikepenz.markdown.coil3.Coil3ImageTransformerImpl import com.mikepenz.markdown.compose.Markdown +import io.github.fletchmckee.liquid.liquefiable import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor +import zed.rainxch.githubstore.feature.details.presentation.utils.LocalTopbarLiquidState import zed.rainxch.githubstore.feature.details.presentation.utils.rememberMarkdownColors import zed.rainxch.githubstore.feature.details.presentation.utils.rememberMarkdownTypography fun LazyListScope.about(readmeMarkdown: String) { item { + val liquidState = LocalTopbarLiquidState.current + HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant) Spacer(Modifier.height(16.dp)) @@ -31,11 +35,15 @@ fun LazyListScope.about(readmeMarkdown: String) { style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.onBackground, fontWeight = FontWeight.Bold, - modifier = Modifier.padding(bottom = 8.dp) + modifier = Modifier + .padding(bottom = 8.dp) + .liquefiable(liquidState) ) } item { + val liquidState = LocalTopbarLiquidState.current + Surface( color = Color.Transparent, contentColor = MaterialTheme.colorScheme.onBackground @@ -50,7 +58,9 @@ fun LazyListScope.about(readmeMarkdown: String) { typography = typography, flavour = flavour, imageTransformer = Coil3ImageTransformerImpl, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .liquefiable(liquidState), ) } } diff --git a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/sections/Header.kt b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/sections/Header.kt index fc1aa1329..3d135bbf7 100644 --- a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/sections/Header.kt +++ b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/sections/Header.kt @@ -15,26 +15,34 @@ import androidx.compose.material3.Text import androidx.compose.ui.Modifier import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp +import io.github.fletchmckee.liquid.LiquidState +import io.github.fletchmckee.liquid.liquefiable import zed.rainxch.githubstore.feature.details.presentation.DetailsAction import zed.rainxch.githubstore.feature.details.presentation.DetailsState import zed.rainxch.githubstore.feature.details.presentation.components.AppHeader import zed.rainxch.githubstore.feature.details.presentation.components.SmartInstallButton +import zed.rainxch.githubstore.feature.details.presentation.utils.LocalTopbarLiquidState fun LazyListScope.header( state: DetailsState, - onAction: (DetailsAction) -> Unit + onAction: (DetailsAction) -> Unit, ) { item { + val liquidState = LocalTopbarLiquidState.current + if (state.repository != null) { AppHeader( author = state.userProfile, release = state.latestRelease, - repository = state.repository + repository = state.repository, + modifier = Modifier.liquefiable(liquidState) ) } } item { + val liquidState = LocalTopbarLiquidState.current + Box( modifier = Modifier.fillMaxWidth(), ) { @@ -79,6 +87,7 @@ fun LazyListScope.header( modifier = Modifier.size(24.dp) ) }, + modifier = Modifier.liquefiable(liquidState) ) } } diff --git a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/sections/Stats.kt b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/sections/Stats.kt index d3e826db3..4b5358453 100644 --- a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/sections/Stats.kt +++ b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/components/sections/Stats.kt @@ -8,13 +8,17 @@ import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import io.github.fletchmckee.liquid.liquefiable import zed.rainxch.githubstore.feature.details.domain.model.RepoStats import zed.rainxch.githubstore.feature.details.presentation.components.StatItem +import zed.rainxch.githubstore.feature.details.presentation.utils.LocalTopbarLiquidState fun LazyListScope.stats( repoStats: RepoStats, ) { item { + val liquidState = LocalTopbarLiquidState.current + Spacer(Modifier.height(16.dp)) Row( @@ -24,19 +28,25 @@ fun LazyListScope.stats( StatItem( label = "Forks", stat = repoStats.forks, - modifier = Modifier.weight(1.5f) + modifier = Modifier + .weight(1.5f) + .liquefiable(liquidState) ) StatItem( label = "Stars", stat = repoStats.stars, - modifier = Modifier.weight(2f) + modifier = Modifier + .weight(2f) + .liquefiable(liquidState) ) StatItem( label = "Issues", stat = repoStats.openIssues, - modifier = Modifier.weight(1f) + modifier = Modifier + .weight(1f) + .liquefiable(liquidState) ) } } diff --git a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/utils/LocalTopbarLiquidState.kt b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/utils/LocalTopbarLiquidState.kt new file mode 100644 index 000000000..9e3fae17b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/details/presentation/utils/LocalTopbarLiquidState.kt @@ -0,0 +1,8 @@ +package zed.rainxch.githubstore.feature.details.presentation.utils + +import androidx.compose.runtime.compositionLocalOf +import io.github.fletchmckee.liquid.LiquidState + +internal val LocalTopbarLiquidState = compositionLocalOf { + error("State not declared") +} \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 000000000..19c43c55e --- /dev/null +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1,14 @@ +Github Store is a "play store" for GitHub releases that helps you discover and install apps directly from GitHub repositories. + +The app automatically discovers repositories that ship installable binaries (APK, EXE, DMG, etc.) and lets you install the latest release with one click. + +FEATURES + +- Smart discovery with Popular, Recently Updated, and New sections +- Platform-aware filtering - only shows apps compatible with your device +- Always installs from the latest published release +- Rich app details with README, changelog, and statistics +- GitHub login support for higher API rate limits +- Material 3 design with dark mode support + +Built with Kotlin Multiplatform and Compose Multiplatform. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/images/featureGraphic.png b/fastlane/metadata/android/en-US/images/featureGraphic.png new file mode 100644 index 000000000..005290d8e Binary files /dev/null and b/fastlane/metadata/android/en-US/images/featureGraphic.png differ diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png new file mode 100644 index 000000000..d8fb13c83 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/icon.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/details.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/details.png new file mode 100644 index 000000000..707aa6632 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/details.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/details_installing.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/details_installing.png new file mode 100644 index 000000000..139fa2f4e Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/details_installing.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/home.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/home.png new file mode 100644 index 000000000..3c899c263 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/home.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/search.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/search.png new file mode 100644 index 000000000..f45b69bdd Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/search.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/settings.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/settings.png new file mode 100644 index 000000000..decf30bf9 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/settings.png differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 000000000..920d53005 --- /dev/null +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +App store for GitHub releases - discover and install apps with one click \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt new file mode 100644 index 000000000..f251bb1e5 --- /dev/null +++ b/fastlane/metadata/android/en-US/title.txt @@ -0,0 +1 @@ +Github Store \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8d2b23701..495b4f248 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,6 +23,7 @@ kotlinx-serialization = "1.9.0" androidx-security-crypto = "1.1.0" koin = "4.1.1" koinComposeMultiplatform = "4.1.1" +liquid = "1.1.0" multiplatformMarkdownRenderer = "0.38.1" navigationCompose = "2.9.1" datastore = "1.2.0" @@ -66,6 +67,7 @@ koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" } # Navigation +liquid = { module = "io.github.fletchmckee.liquid:liquid", version.ref = "liquid" } navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" } # Markdown