diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 675db2ca8..8a22c6a70 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,7 @@ on: jobs: danger-pr: runs-on: ubuntu-latest + if: ${{ github.event_name == 'pull_request' }} name: "danger-pr" steps: - uses: actions/checkout@v1 @@ -100,6 +101,7 @@ jobs: -x :androidApp:bundle - name: Danger Lint + if: ${{ github.event_name == 'pull_request' }} uses: docker://ghcr.io/danger/danger-kotlin:1.2.0 with: args: --failOnErrors --no-publish-check diff --git a/.idea/kotlinScripting.xml b/.idea/kotlinScripting.xml index 2293d9308..424122b55 100644 --- a/.idea/kotlinScripting.xml +++ b/.idea/kotlinScripting.xml @@ -6,7 +6,6 @@ 2147483647 - true \ No newline at end of file diff --git a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Colorless.kt b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Colorless.kt index f9c9a77b0..d1fdc4fe8 100644 --- a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Colorless.kt +++ b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Colorless.kt @@ -52,24 +52,6 @@ val DeckBoxIcons.Types.Colorless: ImageVector curveTo(10.8635f, 7.1708f, 11.5584f, 5.1805f, 12.138f, 1.8127f) close() } - path( - fill = SolidColor(Color(0xFF000000)), - fillAlpha = 1.0f, - stroke = null, - strokeAlpha = 1.0f, - strokeLineWidth = 1.0f, - strokeLineCap = StrokeCap.Butt, - strokeLineJoin = StrokeJoin.Miter, - strokeLineMiter = 1.0f, - pathFillType = PathFillType.NonZero, - ) { - moveTo(24f, 0f) - horizontalLineTo(-9.53674e-7f) - verticalLineTo(24f) - horizontalLineTo(24f) - verticalLineTo(0f) - close() - } } }.build() return _colorless!! diff --git a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Dark.kt b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Dark.kt index 01c0a3c9b..d5399225f 100644 --- a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Dark.kt +++ b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Dark.kt @@ -48,24 +48,6 @@ val DeckBoxIcons.Types.Dark: ImageVector curveTo(2.4f, 8.599f, 5.3505f, 5.3568f, 9.3631f, 4.3922f) close() } - path( - fill = SolidColor(Color(0xFF000000)), - fillAlpha = 1.0f, - stroke = null, - strokeAlpha = 1.0f, - strokeLineWidth = 1.0f, - strokeLineCap = StrokeCap.Butt, - strokeLineJoin = StrokeJoin.Miter, - strokeLineMiter = 1.0f, - pathFillType = PathFillType.NonZero, - ) { - moveTo(24f, 0f) - horizontalLineTo(0f) - verticalLineTo(24f) - horizontalLineTo(24f) - verticalLineTo(0f) - close() - } } }.build() return _dark!! diff --git a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Dragon.kt b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Dragon.kt index 8cde98cd8..0ce62193a 100644 --- a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Dragon.kt +++ b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Dragon.kt @@ -57,24 +57,6 @@ val DeckBoxIcons.Types.Dragon: ImageVector lineTo(13.2331f, 13.2976f) close() } - path( - fill = SolidColor(Color(0xFF000000)), - fillAlpha = 1.0f, - stroke = null, - strokeAlpha = 1.0f, - strokeLineWidth = 1.0f, - strokeLineCap = StrokeCap.Butt, - strokeLineJoin = StrokeJoin.Miter, - strokeLineMiter = 1.0f, - pathFillType = PathFillType.NonZero, - ) { - moveTo(24f, 0f) - horizontalLineTo(0f) - verticalLineTo(24f) - horizontalLineTo(24f) - verticalLineTo(0f) - close() - } } }.build() return _dragon!! diff --git a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Fairy.kt b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Fairy.kt index b907a2d2d..754782e6d 100644 --- a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Fairy.kt +++ b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Fairy.kt @@ -64,24 +64,6 @@ val DeckBoxIcons.Types.Fairy: ImageVector curveTo(10.8213f, 5.373f, 11.4493f, 4.2893f, 12.0798f, 2.5199f) close() } - path( - fill = SolidColor(Color(0xFF000000)), - fillAlpha = 1.0f, - stroke = null, - strokeAlpha = 1.0f, - strokeLineWidth = 1.0f, - strokeLineCap = StrokeCap.Butt, - strokeLineJoin = StrokeJoin.Miter, - strokeLineMiter = 1.0f, - pathFillType = PathFillType.NonZero, - ) { - moveTo(24f, 0f) - horizontalLineTo(0f) - verticalLineTo(24f) - horizontalLineTo(24f) - verticalLineTo(0f) - close() - } } }.build() return _fairy!! diff --git a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Fighting.kt b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Fighting.kt index a6a9499de..6cdbaed00 100644 --- a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Fighting.kt +++ b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Fighting.kt @@ -111,24 +111,6 @@ val DeckBoxIcons.Types.Fighting: ImageVector lineTo(14.1943f, 12.2197f) close() } - path( - fill = SolidColor(Color(0xFF000000)), - fillAlpha = 1.0f, - stroke = null, - strokeAlpha = 1.0f, - strokeLineWidth = 1.0f, - strokeLineCap = StrokeCap.Butt, - strokeLineJoin = StrokeJoin.Miter, - strokeLineMiter = 1.0f, - pathFillType = PathFillType.NonZero, - ) { - moveTo(24f, 0f) - horizontalLineTo(0f) - verticalLineTo(24f) - horizontalLineTo(24f) - verticalLineTo(0f) - close() - } } }.build() return _fighting!! diff --git a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Fire.kt b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Fire.kt index 1fe8c375d..244b62eaa 100644 --- a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Fire.kt +++ b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Fire.kt @@ -80,24 +80,6 @@ val DeckBoxIcons.Types.Fire: ImageVector curveTo(11.6283f, 13.6253f, 11.9254f, 13.1299f, 12.5197f, 12.5f) close() } - path( - fill = SolidColor(Color(0xFF000000)), - fillAlpha = 1.0f, - stroke = null, - strokeAlpha = 1.0f, - strokeLineWidth = 1.0f, - strokeLineCap = StrokeCap.Butt, - strokeLineJoin = StrokeJoin.Miter, - strokeLineMiter = 1.0f, - pathFillType = PathFillType.NonZero, - ) { - moveTo(24f, 0f) - horizontalLineTo(0f) - verticalLineTo(24f) - horizontalLineTo(24f) - verticalLineTo(0f) - close() - } } }.build() return _fire!! diff --git a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Grass.kt b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Grass.kt index 3c3212393..07ea556a6 100644 --- a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Grass.kt +++ b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Grass.kt @@ -85,24 +85,6 @@ val DeckBoxIcons.Types.Grass: ImageVector curveTo(13.177f, 6.1881f, 13.4991f, 5.3691f, 13.939f, 4.2752f) close() } - path( - fill = SolidColor(Color(0xFF000000)), - fillAlpha = 1.0f, - stroke = null, - strokeAlpha = 1.0f, - strokeLineWidth = 1.0f, - strokeLineCap = StrokeCap.Butt, - strokeLineJoin = StrokeJoin.Miter, - strokeLineMiter = 1.0f, - pathFillType = PathFillType.NonZero, - ) { - moveTo(24f, 0f) - horizontalLineTo(0f) - verticalLineTo(24f) - horizontalLineTo(24f) - verticalLineTo(0f) - close() - } } }.build() return _grass!! diff --git a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Lightning.kt b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Lightning.kt index 8a98ba9f1..7021e5e8e 100644 --- a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Lightning.kt +++ b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Lightning.kt @@ -46,24 +46,6 @@ val DeckBoxIcons.Types.Lightning: ImageVector lineTo(13.826f, 1.51495f) close() } - path( - fill = SolidColor(Color(0xFF000000)), - fillAlpha = 1.0f, - stroke = null, - strokeAlpha = 1.0f, - strokeLineWidth = 1.0f, - strokeLineCap = StrokeCap.Butt, - strokeLineJoin = StrokeJoin.Miter, - strokeLineMiter = 1.0f, - pathFillType = PathFillType.NonZero, - ) { - moveTo(24f, 0f) - horizontalLineTo(0f) - verticalLineTo(24f) - horizontalLineTo(24f) - verticalLineTo(0f) - close() - } } }.build() return _lightning!! diff --git a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Psychic.kt b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Psychic.kt index 2791ca6d3..e6a6c182b 100644 --- a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Psychic.kt +++ b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Psychic.kt @@ -63,24 +63,6 @@ val DeckBoxIcons.Types.Psychic: ImageVector curveTo(9.9498f, 12.65f, 9.6672f, 12.5744f, 9.4203f, 12.4412f) close() } - path( - fill = SolidColor(Color(0xFF000000)), - fillAlpha = 1.0f, - stroke = null, - strokeAlpha = 1.0f, - strokeLineWidth = 1.0f, - strokeLineCap = StrokeCap.Butt, - strokeLineJoin = StrokeJoin.Miter, - strokeLineMiter = 1.0f, - pathFillType = PathFillType.NonZero, - ) { - moveTo(24f, 0f) - horizontalLineTo(0f) - verticalLineTo(24f) - horizontalLineTo(24f) - verticalLineTo(0f) - close() - } } }.build() return _psychic!! diff --git a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Steel.kt b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Steel.kt index 0c13c5c9d..d7eebeae1 100644 --- a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Steel.kt +++ b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Steel.kt @@ -73,24 +73,6 @@ val DeckBoxIcons.Types.Steel: ImageVector lineTo(8.60269f, 17.1435f) close() } - path( - fill = SolidColor(Color(0xFF000000)), - fillAlpha = 1.0f, - stroke = null, - strokeAlpha = 1.0f, - strokeLineWidth = 1.0f, - strokeLineCap = StrokeCap.Butt, - strokeLineJoin = StrokeJoin.Miter, - strokeLineMiter = 1.0f, - pathFillType = PathFillType.NonZero, - ) { - moveTo(24f, 0f) - horizontalLineTo(0f) - verticalLineTo(24f) - horizontalLineTo(24f) - verticalLineTo(0f) - close() - } } }.build() return _steel!! diff --git a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Type.kt b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Type.kt index 7fb2109ed..61f833f3e 100644 --- a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Type.kt +++ b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Type.kt @@ -1,8 +1,18 @@ package app.deckbox.common.compose.icons.types import Dragon +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp import app.deckbox.common.compose.icons.DeckBoxIcons +import app.deckbox.common.compose.theme.PokemonTypeColor.toColor +import app.deckbox.common.compose.theme.PokemonTypeColor.toContentColor import app.deckbox.core.model.Type fun Type.asImageVector(): ImageVector = when (this) { @@ -19,3 +29,21 @@ fun Type.asImageVector(): ImageVector = when (this) { Type.WATER -> DeckBoxIcons.Types.Water Type.UNKNOWN -> DeckBoxIcons.Types.Colorless } + +@Composable +fun TypeIcon( + type: Type, + modifier: Modifier = Modifier, +) { + Image( + type.asImageVector(), + contentDescription = type.displayName, + modifier = modifier + .background( + color = type.toColor(), + shape = CircleShape, + ) + .padding(4.dp), + colorFilter = ColorFilter.tint(type.toContentColor(true)), + ) +} diff --git a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Water.kt b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Water.kt index 34dafed1d..bc9268fd2 100644 --- a/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Water.kt +++ b/common/compose/src/commonMain/kotlin/app/deckbox/common/compose/icons/types/Water.kt @@ -55,24 +55,6 @@ val DeckBoxIcons.Types.Water: ImageVector curveTo(6.7511f, 18.6564f, 8.3174f, 19.5975f, 9.1685f, 19.8892f) close() } - path( - fill = SolidColor(Color(0xFF000000)), - fillAlpha = 1.0f, - stroke = null, - strokeAlpha = 1.0f, - strokeLineWidth = 1.0f, - strokeLineCap = StrokeCap.Butt, - strokeLineJoin = StrokeJoin.Miter, - strokeLineMiter = 1.0f, - pathFillType = PathFillType.NonZero, - ) { - moveTo(24f, 0f) - horizontalLineTo(0f) - verticalLineTo(24f) - horizontalLineTo(24f) - verticalLineTo(0f) - close() - } } }.build() return _water!! diff --git a/common/resources/strings/build.gradle.kts b/common/resources/strings/build.gradle.kts index a45dfee11..6624b5f41 100644 --- a/common/resources/strings/build.gradle.kts +++ b/common/resources/strings/build.gradle.kts @@ -9,7 +9,7 @@ plugins { kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { api(libs.lyricist) api(compose.foundation) diff --git a/common/resources/strings/src/androidMain/kotlin/app/deckbox/common/resources/strings/CurrencyFormatter.kt b/common/resources/strings/src/androidMain/kotlin/app/deckbox/common/resources/strings/CurrencyFormatter.kt new file mode 100644 index 000000000..a9b1abb13 --- /dev/null +++ b/common/resources/strings/src/androidMain/kotlin/app/deckbox/common/resources/strings/CurrencyFormatter.kt @@ -0,0 +1,47 @@ +package app.deckbox.common.resources.strings + +import android.icu.number.Notation +import android.icu.number.NumberFormatter +import android.icu.number.Precision +import android.icu.text.NumberFormat +import android.icu.util.Currency +import android.os.Build +import androidx.annotation.RequiresApi +import java.util.Locale + +actual object CurrencyFormatter { + + actual fun format(value: Double, type: CurrencyType): String { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + formatAndroid30(value, type) + } else { + formatLegacy(value, type) + } + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun formatAndroid30(value: Double, type: CurrencyType): String { + return NumberFormatter.withLocale(Locale.getDefault()) + .notation(Notation.compactLong()) + .unit(type.asCurrency()) + .precision(Precision.maxSignificantDigits(2)) + .format(value) + .toString() + } + + private fun formatLegacy(value: Double, type: CurrencyType): String { + return NumberFormat.getInstance(Locale.getDefault()) + .apply { + currency = type.asCurrency() + maximumFractionDigits = 2 + } + .format(value) + } + + private fun CurrencyType.asCurrency(): Currency { + return when (this) { + CurrencyType.USD -> Currency.getInstance(Locale.US) + CurrencyType.EUR -> Currency.getInstance("EUR") + } + } +} diff --git a/common/resources/strings/src/commonMain/kotlin/app/deckbox/common/resources/strings/CurrencyFormatter.kt b/common/resources/strings/src/commonMain/kotlin/app/deckbox/common/resources/strings/CurrencyFormatter.kt new file mode 100644 index 000000000..9055a415d --- /dev/null +++ b/common/resources/strings/src/commonMain/kotlin/app/deckbox/common/resources/strings/CurrencyFormatter.kt @@ -0,0 +1,10 @@ +package app.deckbox.common.resources.strings + +expect object CurrencyFormatter { + fun format(value: Double, type: CurrencyType = CurrencyType.USD): String +} + +enum class CurrencyType { + USD, + EUR, +} diff --git a/common/resources/strings/src/commonMain/kotlin/app/deckbox/common/resources/strings/DeckBoxStrings.kt b/common/resources/strings/src/commonMain/kotlin/app/deckbox/common/resources/strings/DeckBoxStrings.kt index b7a89860c..24da7f301 100644 --- a/common/resources/strings/src/commonMain/kotlin/app/deckbox/common/resources/strings/DeckBoxStrings.kt +++ b/common/resources/strings/src/commonMain/kotlin/app/deckbox/common/resources/strings/DeckBoxStrings.kt @@ -10,6 +10,7 @@ data class DeckBoxStrings( val genericEmptyCardsMessage: String, val genericSearchEmpty: (query: String?) -> AnnotatedString, val cardPlaceholderContentDescription: String, + val refreshPricesContentDescription: String, // Decks val decks: String, @@ -36,6 +37,25 @@ data class DeckBoxStrings( val browseTabContentDescription: String, val browseSearchHint: String, + // Card Detail + val tcgPlayerNormal: String, + val tcgPlayerHolofoil: String, + val tcgPlayerReverseHolofoil: String, + val tcgPlayerFirstEditionHolofoil: String, + val tcgPlayerFirstEditionNormal: String, + + val priceMarket: String, + val priceLow: String, + val priceMid: String, + val priceHigh: String, + + val priceTrend: String, + val oneDayAvg: String, + val sevenDayAvg: String, + val thirtyDayAvg: String, + + val actionBuy: String, + // Settings val settings: String, val settingsTabContentDescription: String, diff --git a/common/resources/strings/src/commonMain/kotlin/app/deckbox/common/resources/strings/EnDeckBoxStrings.kt b/common/resources/strings/src/commonMain/kotlin/app/deckbox/common/resources/strings/EnDeckBoxStrings.kt index b5703fc64..ef1e7acc0 100644 --- a/common/resources/strings/src/commonMain/kotlin/app/deckbox/common/resources/strings/EnDeckBoxStrings.kt +++ b/common/resources/strings/src/commonMain/kotlin/app/deckbox/common/resources/strings/EnDeckBoxStrings.kt @@ -23,6 +23,7 @@ val EnDeckBoxStrings = DeckBoxStrings( } }, cardPlaceholderContentDescription = "Pokemon Card Placeholder", + refreshPricesContentDescription = "Refresh Prices", decks = "Decks", decksTabContentDescription = "List of saved decks", @@ -46,4 +47,22 @@ val EnDeckBoxStrings = DeckBoxStrings( browseSearchHint = "Search for any card", settings = "Settings", settingsTabContentDescription = "Change application settings", + + tcgPlayerNormal = "Normal", + tcgPlayerHolofoil = "Holo", + tcgPlayerReverseHolofoil = "Reverse Holo", + tcgPlayerFirstEditionHolofoil = "1st Edition Holo", + tcgPlayerFirstEditionNormal = "1st Edition", + + priceMarket = "Market", + priceLow = "Low", + priceMid = "Mid", + priceHigh = "High", + + priceTrend = "Price trend", + oneDayAvg = "1 day avg", + sevenDayAvg = "7 day avg", + thirtyDayAvg = "30 day avg", + + actionBuy = "Buy", ) diff --git a/common/resources/strings/src/iosMain/kotlin/app/deckbox/common/resources/strings/CurrencyFormatter.kt b/common/resources/strings/src/iosMain/kotlin/app/deckbox/common/resources/strings/CurrencyFormatter.kt new file mode 100644 index 000000000..89db266bc --- /dev/null +++ b/common/resources/strings/src/iosMain/kotlin/app/deckbox/common/resources/strings/CurrencyFormatter.kt @@ -0,0 +1,21 @@ +package app.deckbox.common.resources.strings + +import platform.Foundation.NSNumber +import platform.Foundation.NSNumberFormatter +import platform.Foundation.NSNumberFormatterCurrencyStyle + +actual object CurrencyFormatter { + actual fun format(value: Double, type: CurrencyType): String { + val currencyFormatter = NSNumberFormatter() + currencyFormatter.usesGroupingSeparator = true + currencyFormatter.numberStyle = NSNumberFormatterCurrencyStyle + currencyFormatter.setCurrencyCode( + when (type) { + CurrencyType.USD -> "usd" + CurrencyType.EUR -> "eur" + }, + ) + return currencyFormatter.stringFromNumber(NSNumber(value)) + ?: "$value" + } +} diff --git a/common/resources/strings/src/jvmMain/kotlin/app/deckbox/common/resources/strings/CurrencyFormatter.kt b/common/resources/strings/src/jvmMain/kotlin/app/deckbox/common/resources/strings/CurrencyFormatter.kt new file mode 100644 index 000000000..8a442c585 --- /dev/null +++ b/common/resources/strings/src/jvmMain/kotlin/app/deckbox/common/resources/strings/CurrencyFormatter.kt @@ -0,0 +1,17 @@ +package app.deckbox.common.resources.strings + +import java.text.NumberFormat +import java.util.Currency + +actual object CurrencyFormatter { + actual fun format(value: Double, type: CurrencyType): String { + return NumberFormat.getCurrencyInstance() + .apply { + currency = when (type) { + CurrencyType.USD -> Currency.getInstance("usd") + CurrencyType.EUR -> Currency.getInstance("eur") + } + } + .format(value) + } +} diff --git a/common/screens/build.gradle.kts b/common/screens/build.gradle.kts index 9bf01ebc8..9fd9f2d85 100644 --- a/common/screens/build.gradle.kts +++ b/common/screens/build.gradle.kts @@ -6,8 +6,9 @@ plugins { kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { + api(projects.core) api(libs.circuit.runtime) } } diff --git a/common/screens/src/commonMain/kotlin/app/deckbox/common/screens/Screens.kt b/common/screens/src/commonMain/kotlin/app/deckbox/common/screens/Screens.kt index 889db2d23..04d698423 100644 --- a/common/screens/src/commonMain/kotlin/app/deckbox/common/screens/Screens.kt +++ b/common/screens/src/commonMain/kotlin/app/deckbox/common/screens/Screens.kt @@ -1,5 +1,6 @@ package app.deckbox.common.screens +import app.deckbox.core.model.Card import com.slack.circuit.runtime.Screen /** @@ -37,7 +38,11 @@ class ExpansionDetailScreen( @CommonParcelize class CardDetailScreen( val cardId: String, + val cardName: String, + val cardImageLarge: String, ) : DeckBoxScreen(name = "CardDetail()") { + constructor(card: Card) : this(card.id, card.name, card.image.large) + override val arguments get() = mapOf("cardId" to cardId) } diff --git a/common/settings/build.gradle.kts b/common/settings/build.gradle.kts index 971dbeeb4..01b37014d 100644 --- a/common/settings/build.gradle.kts +++ b/common/settings/build.gradle.kts @@ -8,7 +8,7 @@ plugins { kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { implementation(projects.core) api(libs.multiplatformsettings.core) @@ -16,7 +16,7 @@ kotlin { } } - val androidMain by getting { + androidMain { dependencies { implementation(libs.androidx.preferences) } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 58b6afffa..aecf3e371 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -6,7 +6,7 @@ plugins { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { api(projects.di.kotlinInjectMergeAnnotations) api(libs.kotlinx.coroutines.core) @@ -15,7 +15,8 @@ kotlin { implementation(libs.kotlinx.serialization.json) } } - val commonTest by getting { + + commonTest { dependencies { implementation(libs.kotlin.test) } diff --git a/core/src/commonMain/kotlin/app/deckbox/core/model/Card.kt b/core/src/commonMain/kotlin/app/deckbox/core/model/Card.kt index 2814a49b1..fd34f4205 100644 --- a/core/src/commonMain/kotlin/app/deckbox/core/model/Card.kt +++ b/core/src/commonMain/kotlin/app/deckbox/core/model/Card.kt @@ -58,11 +58,18 @@ data class Card( data class TcgPlayer( val url: String, - val updatedAt: LocalDate?, + val updatedAt: LocalDate, val prices: Prices?, ) { - data class Prices( + val normal: Price?, + val holofoil: Price?, + val reverseHolofoil: Price?, + val firstEditionHolofoil: Price?, + val firstEditionNormal: Price?, + ) + + data class Price( val low: Double? = null, val mid: Double? = null, val high: Double? = null, @@ -73,7 +80,7 @@ data class Card( data class CardMarket( val url: String, - val updatedAt: LocalDate?, + val updatedAt: LocalDate, val prices: Prices?, ) { diff --git a/data/db/build.gradle.kts b/data/db/build.gradle.kts index 2cb76d95f..eba814cc7 100644 --- a/data/db/build.gradle.kts +++ b/data/db/build.gradle.kts @@ -19,7 +19,7 @@ kotlin { } sourceSets { - val commonMain by getting { + commonMain { dependencies { implementation(projects.core) @@ -30,19 +30,19 @@ kotlin { } } - val androidMain by getting { + androidMain { dependencies { implementation(libs.sqldelight.android) } } - val iosMain by getting { + iosMain { dependencies { implementation(libs.sqldelight.native) } } - val jvmMain by getting { + jvmMain { dependencies { implementation(libs.sqldelight.sqlite) } diff --git a/data/db/src/commonMain/kotlin/app/deckbox/db/DatabaseFactory.kt b/data/db/src/commonMain/kotlin/app/deckbox/db/DatabaseFactory.kt index fbd85c15b..85d16109d 100644 --- a/data/db/src/commonMain/kotlin/app/deckbox/db/DatabaseFactory.kt +++ b/data/db/src/commonMain/kotlin/app/deckbox/db/DatabaseFactory.kt @@ -6,8 +6,10 @@ import app.cash.sqldelight.db.SqlDriver import app.deckbox.DeckBoxDatabase import app.deckbox.core.model.SuperType import app.deckbox.sqldelight.Attacks +import app.deckbox.sqldelight.CardMarketPrices import app.deckbox.sqldelight.Cards import app.deckbox.sqldelight.Expansions +import app.deckbox.sqldelight.TcgPlayerPrices import me.tatarka.inject.annotations.Inject @Inject @@ -31,8 +33,6 @@ class DatabaseFactory( legalitiesUnlimitedAdapter = EnumColumnAdapter(), resistancesAdapter = CardEffectListAdapter, weaknessesAdapter = CardEffectListAdapter, - tcgPlayerUpdatedAtAdapter = LocalDateAdapter, - cardMarketUpdatedAtAdapter = LocalDateAdapter, ), expansionsAdapter = Expansions.Adapter( totalAdapter = IntColumnAdapter, @@ -47,5 +47,11 @@ class DatabaseFactory( costAdapter = TypeListAdapter, convertedEnergyCostAdapter = IntColumnAdapter, ), + tcgPlayerPricesAdapter = TcgPlayerPrices.Adapter( + updatedAtAdapter = LocalDateAdapter, + ), + cardMarketPricesAdapter = CardMarketPrices.Adapter( + updatedAtAdapter = LocalDateAdapter, + ), ) } diff --git a/data/db/src/commonMain/kotlin/app/deckbox/db/mapping/EntityMapper.kt b/data/db/src/commonMain/kotlin/app/deckbox/db/mapping/EntityMapper.kt index bca0e4796..7ef7b4b6f 100644 --- a/data/db/src/commonMain/kotlin/app/deckbox/db/mapping/EntityMapper.kt +++ b/data/db/src/commonMain/kotlin/app/deckbox/db/mapping/EntityMapper.kt @@ -5,13 +5,17 @@ import app.deckbox.core.model.Expansion import app.deckbox.core.model.Legalities import app.deckbox.sqldelight.Abilities import app.deckbox.sqldelight.Attacks +import app.deckbox.sqldelight.CardMarketPrices import app.deckbox.sqldelight.Cards import app.deckbox.sqldelight.Expansions +import app.deckbox.sqldelight.TcgPlayerPrices fun Cards.toModel( expansion: Expansions, abilities: List, attacks: List, + tcgPlayerPrices: TcgPlayerPrices?, + cardMarketPrices: CardMarketPrices?, ): Card { return Card( id = id, @@ -50,57 +54,125 @@ fun Cards.toModel( expanded = legalitiesExpanded, unlimited = legalitiesUnlimited, ), - tcgPlayer = toTcgPlayer(), - cardMarket = toCardMarket(), + tcgPlayer = tcgPlayerPrices?.toModel(), + cardMarket = cardMarketPrices?.toModel(), ) } -fun Cards.toTcgPlayer(): Card.TcgPlayer? { - return if (tcgPlayerUrl != null) { - Card.TcgPlayer( - url = tcgPlayerUrl, - updatedAt = tcgPlayerUpdatedAt, - prices = Card.TcgPlayer.Prices( - low = tcgPlayerLow, - mid = tcgPlayerMid, - high = tcgPlayerHigh, - market = tcgPlayerMarket, - directLow = tcgPlayerDirectLow, - ), +fun TcgPlayerPrices.toModel(): Card.TcgPlayer { + return Card.TcgPlayer( + url = url, + updatedAt = updatedAt, + prices = Card.TcgPlayer.Prices( + normal = toNormalPrice(), + holofoil = toHolofoil(), + reverseHolofoil = toReverseHolofoil(), + firstEditionHolofoil = to1stEditionHolofoil(), + firstEditionNormal = to1stEditionNormal(), + ), + ) +} + +fun TcgPlayerPrices.toNormalPrice(): Card.TcgPlayer.Price? { + return if (normalLow != null || normalMid != null || normalHigh != null || normalMarket != null) { + Card.TcgPlayer.Price( + low = normalLow, + mid = normalMid, + high = normalHigh, + market = normalMarket, + directLow = normalDirectLow, + ) + } else { + null + } +} + +fun TcgPlayerPrices.toHolofoil(): Card.TcgPlayer.Price? { + return if (holofoilLow != null || holofoilMid != null || holofoilHigh != null || holofoilMarket != null) { + Card.TcgPlayer.Price( + low = holofoilLow, + mid = holofoilMid, + high = holofoilHigh, + market = holofoilMarket, + directLow = holofoilDirectLow, + ) + } else { + null + } +} + +fun TcgPlayerPrices.toReverseHolofoil(): Card.TcgPlayer.Price? { + return if (reverseHolofoilLow != null || reverseHolofoilMid != null || + reverseHolofoilHigh != null || reverseHolofoilMarket != null + ) { + Card.TcgPlayer.Price( + low = reverseHolofoilLow, + mid = reverseHolofoilMid, + high = reverseHolofoilHigh, + market = reverseHolofoilMarket, + directLow = reverseHolofoilDirectLow, ) } else { null } } -fun Cards.toCardMarket(): Card.CardMarket? { - return if (cardMarketUrl != null) { - Card.CardMarket( - url = cardMarketUrl, - updatedAt = cardMarketUpdatedAt, - prices = Card.CardMarket.Prices( - averageSellPrice = cardMarketAverageSellPrice, - lowPrice = cardMarketLowPrice, - trendPrice = cardMarketTrendPrice, - germanProLow = cardMarketGermanProLow, - suggestedPrice = cardMarketSuggestedPrice, - reverseHoloSell = cardMarketReverseHoloSell, - reverseHoloLow = cardMarketReverseHoloLow, - reverseHoloTrend = cardMarketReverseHoloTrend, - lowPriceExPlus = cardMarketLowPriceExPlus, - avg1 = cardMarketAvg1, - avg7 = cardMarketAvg7, - avg30 = cardMarketAvg30, - reverseHoloAvg1 = cardMarketReverseHoloAvg1, - reverseHoloAvg7 = cardMarketReverseHoloAvg7, - reverseHoloAvg30 = cardMarketReverseHoloAvg30, - ), +fun TcgPlayerPrices.to1stEditionHolofoil(): Card.TcgPlayer.Price? { + return if (firstEditionHolofoilLow != null || firstEditionHolofoilMid != null || + firstEditionHolofoilHigh != null || firstEditionHolofoilMarket != null + ) { + Card.TcgPlayer.Price( + low = firstEditionHolofoilLow, + mid = firstEditionHolofoilMid, + high = firstEditionHolofoilHigh, + market = firstEditionHolofoilMarket, + directLow = firstEditionHolofoilDirectLow, ) } else { null } } +fun TcgPlayerPrices.to1stEditionNormal(): Card.TcgPlayer.Price? { + return if (firstEditionNormalLow != null || firstEditionNormalMid != null || + firstEditionNormalHigh != null || firstEditionNormalMarket != null + ) { + Card.TcgPlayer.Price( + low = firstEditionNormalLow, + mid = firstEditionNormalMid, + high = firstEditionNormalHigh, + market = firstEditionNormalMarket, + directLow = firstEditionNormalDirectLow, + ) + } else { + null + } +} + +fun CardMarketPrices.toModel(): Card.CardMarket { + return Card.CardMarket( + url = url, + updatedAt = updatedAt, + prices = Card.CardMarket.Prices( + averageSellPrice = averageSellPrice, + lowPrice = lowPrice, + trendPrice = trendPrice, + germanProLow = germanProLow, + suggestedPrice = suggestedPrice, + reverseHoloSell = reverseHoloSell, + reverseHoloLow = reverseHoloLow, + reverseHoloTrend = reverseHoloTrend, + lowPriceExPlus = lowPriceExPlus, + avg1 = avg1, + avg7 = avg7, + avg30 = avg30, + reverseHoloAvg1 = reverseHoloAvg1, + reverseHoloAvg7 = reverseHoloAvg7, + reverseHoloAvg30 = reverseHoloAvg30, + ), + ) +} + fun Abilities.toModel(): Card.Ability = Card.Ability( name = name, text = text, diff --git a/data/db/src/commonMain/kotlin/app/deckbox/db/mapping/ModelMapper.kt b/data/db/src/commonMain/kotlin/app/deckbox/db/mapping/ModelMapper.kt index 172fd9078..696198605 100644 --- a/data/db/src/commonMain/kotlin/app/deckbox/db/mapping/ModelMapper.kt +++ b/data/db/src/commonMain/kotlin/app/deckbox/db/mapping/ModelMapper.kt @@ -4,8 +4,10 @@ import app.deckbox.core.model.Card import app.deckbox.core.model.Expansion import app.deckbox.sqldelight.Abilities import app.deckbox.sqldelight.Attacks +import app.deckbox.sqldelight.CardMarketPrices import app.deckbox.sqldelight.Cards import app.deckbox.sqldelight.Expansions +import app.deckbox.sqldelight.TcgPlayerPrices fun Expansion.toEntity(): Expansions = Expansions( id = id, @@ -51,33 +53,70 @@ fun Card.toEntity(): Cards = Cards( legalitiesStandard = legalities?.standard, legalitiesExpanded = legalities?.expanded, legalitiesUnlimited = legalities?.unlimited, - tcgPlayerUrl = tcgPlayer?.url, - tcgPlayerUpdatedAt = tcgPlayer?.updatedAt, - tcgPlayerLow = tcgPlayer?.prices?.low, - tcgPlayerMid = tcgPlayer?.prices?.mid, - tcgPlayerHigh = tcgPlayer?.prices?.high, - tcgPlayerMarket = tcgPlayer?.prices?.market, - tcgPlayerDirectLow = tcgPlayer?.prices?.directLow, - cardMarketUrl = cardMarket?.url, - cardMarketUpdatedAt = cardMarket?.updatedAt, - cardMarketAverageSellPrice = cardMarket?.prices?.averageSellPrice, - cardMarketLowPrice = cardMarket?.prices?.lowPrice, - cardMarketTrendPrice = cardMarket?.prices?.trendPrice, - cardMarketGermanProLow = cardMarket?.prices?.germanProLow, - cardMarketSuggestedPrice = cardMarket?.prices?.suggestedPrice, - cardMarketReverseHoloSell = cardMarket?.prices?.reverseHoloSell, - cardMarketReverseHoloLow = cardMarket?.prices?.reverseHoloLow, - cardMarketReverseHoloTrend = cardMarket?.prices?.reverseHoloTrend, - cardMarketLowPriceExPlus = cardMarket?.prices?.lowPriceExPlus, - cardMarketAvg1 = cardMarket?.prices?.avg1, - cardMarketAvg7 = cardMarket?.prices?.avg7, - cardMarketAvg30 = cardMarket?.prices?.avg30, - cardMarketReverseHoloAvg1 = cardMarket?.prices?.reverseHoloAvg1, - cardMarketReverseHoloAvg7 = cardMarket?.prices?.reverseHoloAvg7, - cardMarketReverseHoloAvg30 = cardMarket?.prices?.reverseHoloAvg30, expansionId = expansion.id, ) +fun Card.TcgPlayer.toEntity(cardId: String): TcgPlayerPrices { + return TcgPlayerPrices( + url = url, + updatedAt = updatedAt, + cardId = cardId, + + normalLow = prices?.normal?.low, + normalMid = prices?.normal?.mid, + normalHigh = prices?.normal?.high, + normalMarket = prices?.normal?.market, + normalDirectLow = prices?.normal?.directLow, + + holofoilLow = prices?.holofoil?.low, + holofoilMid = prices?.holofoil?.mid, + holofoilHigh = prices?.holofoil?.high, + holofoilMarket = prices?.holofoil?.market, + holofoilDirectLow = prices?.holofoil?.directLow, + + reverseHolofoilLow = prices?.reverseHolofoil?.low, + reverseHolofoilMid = prices?.reverseHolofoil?.mid, + reverseHolofoilHigh = prices?.reverseHolofoil?.high, + reverseHolofoilMarket = prices?.reverseHolofoil?.market, + reverseHolofoilDirectLow = prices?.reverseHolofoil?.directLow, + + firstEditionHolofoilLow = prices?.firstEditionHolofoil?.low, + firstEditionHolofoilMid = prices?.firstEditionHolofoil?.mid, + firstEditionHolofoilHigh = prices?.firstEditionHolofoil?.high, + firstEditionHolofoilMarket = prices?.firstEditionHolofoil?.market, + firstEditionHolofoilDirectLow = prices?.firstEditionHolofoil?.directLow, + + firstEditionNormalLow = prices?.firstEditionNormal?.low, + firstEditionNormalMid = prices?.firstEditionNormal?.mid, + firstEditionNormalHigh = prices?.firstEditionNormal?.high, + firstEditionNormalMarket = prices?.firstEditionNormal?.market, + firstEditionNormalDirectLow = prices?.firstEditionNormal?.directLow, + ) +} + +fun Card.CardMarket.toEntity(cardId: String): CardMarketPrices { + return CardMarketPrices( + url = url, + updatedAt = updatedAt, + cardId = cardId, + averageSellPrice = prices?.averageSellPrice, + lowPrice = prices?.lowPrice, + trendPrice = prices?.trendPrice, + germanProLow = prices?.germanProLow, + suggestedPrice = prices?.suggestedPrice, + reverseHoloSell = prices?.reverseHoloSell, + reverseHoloLow = prices?.reverseHoloLow, + reverseHoloTrend = prices?.reverseHoloTrend, + lowPriceExPlus = prices?.lowPriceExPlus, + avg1 = prices?.avg1, + avg7 = prices?.avg7, + avg30 = prices?.avg30, + reverseHoloAvg1 = prices?.reverseHoloAvg1, + reverseHoloAvg7 = prices?.reverseHoloAvg7, + reverseHoloAvg30 = prices?.reverseHoloAvg30, + ) +} + fun Card.Ability.toEntity( cardId: String, ): Abilities = Abilities( diff --git a/data/db/src/commonMain/sqldelight/app/deckbox/sqldelight/Card.sq b/data/db/src/commonMain/sqldelight/app/deckbox/sqldelight/Card.sq index 4449b0960..4563534fe 100644 --- a/data/db/src/commonMain/sqldelight/app/deckbox/sqldelight/Card.sq +++ b/data/db/src/commonMain/sqldelight/app/deckbox/sqldelight/Card.sq @@ -5,7 +5,6 @@ import app.deckbox.core.model.Card.Effect; import kotlin.Int; import kotlin.String; import kotlin.collections.List; -import kotlinx.datetime.LocalDate; CREATE TABLE cards ( id TEXT NOT NULL PRIMARY KEY, @@ -37,32 +36,6 @@ CREATE TABLE cards ( legalitiesStandard TEXT AS Legality, legalitiesExpanded TEXT AS Legality, - tcgPlayerUrl TEXT, - tcgPlayerUpdatedAt TEXT AS LocalDate, - tcgPlayerLow REAL, - tcgPlayerMid REAL, - tcgPlayerHigh REAL, - tcgPlayerMarket REAL, - tcgPlayerDirectLow REAL, - - cardMarketUrl TEXT, - cardMarketUpdatedAt TEXT AS LocalDate, - cardMarketAverageSellPrice REAL, - cardMarketLowPrice REAL, - cardMarketTrendPrice REAL, - cardMarketGermanProLow REAL, - cardMarketSuggestedPrice REAL, - cardMarketReverseHoloSell REAL, - cardMarketReverseHoloLow REAL, - cardMarketReverseHoloTrend REAL, - cardMarketLowPriceExPlus REAL, - cardMarketAvg1 REAL, - cardMarketAvg7 REAL, - cardMarketAvg30 REAL, - cardMarketReverseHoloAvg1 REAL, - cardMarketReverseHoloAvg7 REAL, - cardMarketReverseHoloAvg30 REAL, - expansionId TEXT NOT NULL, FOREIGN KEY (expansionId) REFERENCES expansions(id) ); diff --git a/data/db/src/commonMain/sqldelight/app/deckbox/sqldelight/CardMarketPrices.sq b/data/db/src/commonMain/sqldelight/app/deckbox/sqldelight/CardMarketPrices.sq new file mode 100644 index 000000000..f32adf234 --- /dev/null +++ b/data/db/src/commonMain/sqldelight/app/deckbox/sqldelight/CardMarketPrices.sq @@ -0,0 +1,36 @@ +import kotlinx.datetime.LocalDate; + +CREATE TABLE cardMarketPrices ( + url TEXT NOT NULL, + updatedAt TEXT AS LocalDate NOT NULL, + averageSellPrice REAL, + lowPrice REAL, + trendPrice REAL, + germanProLow REAL, + suggestedPrice REAL, + reverseHoloSell REAL, + reverseHoloLow REAL, + reverseHoloTrend REAL, + lowPriceExPlus REAL, + avg1 REAL, + avg7 REAL, + avg30 REAL, + reverseHoloAvg1 REAL, + reverseHoloAvg7 REAL, + reverseHoloAvg30 REAL, + + cardId TEXT NOT NULL PRIMARY KEY, + FOREIGN KEY (cardId) REFERENCES cards(id) ON DELETE CASCADE +); + +insert: +INSERT OR REPLACE INTO cardMarketPrices +VALUES ?; + +getById: +SELECT * FROM cardMarketPrices +WHERE cardId = ?; + +getByIds: +SELECT * FROM cardMarketPrices +WHERE cardId IN ?; diff --git a/data/db/src/commonMain/sqldelight/app/deckbox/sqldelight/TcgPlayerPrices.sq b/data/db/src/commonMain/sqldelight/app/deckbox/sqldelight/TcgPlayerPrices.sq new file mode 100644 index 000000000..9f1bfaf09 --- /dev/null +++ b/data/db/src/commonMain/sqldelight/app/deckbox/sqldelight/TcgPlayerPrices.sq @@ -0,0 +1,51 @@ +import kotlinx.datetime.LocalDate; + +CREATE TABLE tcgPlayerPrices ( + url TEXT NOT NULL, + updatedAt TEXT AS LocalDate NOT NULL, + + normalLow REAL, + normalMid REAL, + normalHigh REAL, + normalMarket REAL, + normalDirectLow REAL, + + holofoilLow REAL, + holofoilMid REAL, + holofoilHigh REAL, + holofoilMarket REAL, + holofoilDirectLow REAL, + + reverseHolofoilLow REAL, + reverseHolofoilMid REAL, + reverseHolofoilHigh REAL, + reverseHolofoilMarket REAL, + reverseHolofoilDirectLow REAL, + + firstEditionHolofoilLow REAL, + firstEditionHolofoilMid REAL, + firstEditionHolofoilHigh REAL, + firstEditionHolofoilMarket REAL, + firstEditionHolofoilDirectLow REAL, + + firstEditionNormalLow REAL, + firstEditionNormalMid REAL, + firstEditionNormalHigh REAL, + firstEditionNormalMarket REAL, + firstEditionNormalDirectLow REAL, + + cardId TEXT NOT NULL PRIMARY KEY, + FOREIGN KEY (cardId) REFERENCES cards(id) ON DELETE CASCADE +); + +insert: +INSERT OR REPLACE INTO tcgPlayerPrices +VALUES ?; + +getById: +SELECT * FROM tcgPlayerPrices +WHERE cardId = ?; + +getByIds: +SELECT * FROM tcgPlayerPrices +WHERE cardId IN ?; diff --git a/data/network/impl/build.gradle.kts b/data/network/impl/build.gradle.kts index a055f4a3f..68cd2b1de 100644 --- a/data/network/impl/build.gradle.kts +++ b/data/network/impl/build.gradle.kts @@ -21,7 +21,7 @@ buildConfig { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { api(projects.data.network.public) implementation(projects.core) @@ -36,27 +36,27 @@ kotlin { } } - val commonTest by getting { + commonTest { dependencies { implementation(libs.kotlin.test) } } - val androidMain by getting { + androidMain { dependencies { api(libs.okhttp.okhttp) implementation(libs.ktor.client.okhttp) } } - val jvmMain by getting { + jvmMain { dependencies { api(libs.okhttp.okhttp) implementation(libs.ktor.client.okhttp) } } - val iosMain by getting { + iosMain { dependencies { implementation(libs.ktor.client.darwin) } diff --git a/data/network/impl/src/commonMain/kotlin/app/deckbox/network/KtorPokemonTcgApi.kt b/data/network/impl/src/commonMain/kotlin/app/deckbox/network/KtorPokemonTcgApi.kt index 567669ce5..b26b04e92 100644 --- a/data/network/impl/src/commonMain/kotlin/app/deckbox/network/KtorPokemonTcgApi.kt +++ b/data/network/impl/src/commonMain/kotlin/app/deckbox/network/KtorPokemonTcgApi.kt @@ -47,7 +47,7 @@ class KtorPokemonTcgApi( install(HttpCache) install(Logging) { - level = LogLevel.ALL + level = LogLevel.INFO logger = object : Logger { override fun log(message: String) { bark(INFO) { message } diff --git a/data/network/impl/src/commonMain/kotlin/app/deckbox/network/api/ModelMapper.kt b/data/network/impl/src/commonMain/kotlin/app/deckbox/network/api/ModelMapper.kt index d37b338b9..03c6d8f29 100644 --- a/data/network/impl/src/commonMain/kotlin/app/deckbox/network/api/ModelMapper.kt +++ b/data/network/impl/src/commonMain/kotlin/app/deckbox/network/api/ModelMapper.kt @@ -7,8 +7,10 @@ import app.deckbox.core.model.Legality import app.deckbox.core.model.SuperType import app.deckbox.core.model.Type import app.deckbox.network.PagedResponse +import kotlinx.datetime.Clock import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDate import kotlinx.datetime.toLocalDateTime @@ -122,23 +124,37 @@ internal object ModelMapper { private fun to(model: TcgPlayerModel): Card.TcgPlayer { return Card.TcgPlayer( url = model.url, - updatedAt = model.updated?.apiToLocalDate(), + updatedAt = model.updated?.apiToLocalDate() + // TODO abstract this cause testability blows + ?: Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date, prices = model.prices?.let { prices -> Card.TcgPlayer.Prices( - low = prices.low, - mid = prices.mid, - high = prices.high, - market = prices.market, - directLow = prices.directLow, + normal = prices.normal?.let(::to), + holofoil = prices.holofoil?.let(::to), + reverseHolofoil = prices.reverseHolofoil?.let(::to), + firstEditionHolofoil = prices.firstEditionHolofoil?.let(::to), + firstEditionNormal = prices.firstEditionNormal?.let(::to), ) }, ) } + private fun to(model: TcgPlayerModel.PriceModel): Card.TcgPlayer.Price { + return Card.TcgPlayer.Price( + low = model.low, + mid = model.mid, + high = model.high, + market = model.market, + directLow = model.directLow, + ) + } + private fun to(model: CardMarketModel): Card.CardMarket { return Card.CardMarket( url = model.url, - updatedAt = model.updatedAt?.apiToLocalDate(), + updatedAt = model.updatedAt?.apiToLocalDate() + // TODO abstract this cause testability blows + ?: Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date, prices = model.prices?.let { prices -> Card.CardMarket.Prices( averageSellPrice = prices.averageSellPrice, diff --git a/data/network/impl/src/commonMain/kotlin/app/deckbox/network/api/NetworkModels.kt b/data/network/impl/src/commonMain/kotlin/app/deckbox/network/api/NetworkModels.kt index 2878f477a..b4d542d13 100644 --- a/data/network/impl/src/commonMain/kotlin/app/deckbox/network/api/NetworkModels.kt +++ b/data/network/impl/src/commonMain/kotlin/app/deckbox/network/api/NetworkModels.kt @@ -119,6 +119,15 @@ internal class TcgPlayerModel( @Serializable internal class PricesModel( + val normal: PriceModel? = null, + val holofoil: PriceModel? = null, + val reverseHolofoil: PriceModel? = null, + @SerialName("1stEditionHolofoil") val firstEditionHolofoil: PriceModel? = null, + @SerialName("1stEditionNormal") val firstEditionNormal: PriceModel? = null, + ) + + @Serializable + internal class PriceModel( val low: Double? = null, val mid: Double? = null, val high: Double? = null, diff --git a/data/network/public/build.gradle.kts b/data/network/public/build.gradle.kts index 78044b114..234acc082 100644 --- a/data/network/public/build.gradle.kts +++ b/data/network/public/build.gradle.kts @@ -5,7 +5,7 @@ plugins { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { implementation(projects.core) implementation(libs.kotlinx.coroutines.core) diff --git a/di/kotlin-inject-merge-annotations/build.gradle.kts b/di/kotlin-inject-merge-annotations/build.gradle.kts index d3f3e2a68..7d86a4e4b 100644 --- a/di/kotlin-inject-merge-annotations/build.gradle.kts +++ b/di/kotlin-inject-merge-annotations/build.gradle.kts @@ -5,7 +5,7 @@ plugins { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { } } diff --git a/features/cards/impl/build.gradle.kts b/features/cards/impl/build.gradle.kts index 7e858d772..00e94437f 100644 --- a/features/cards/impl/build.gradle.kts +++ b/features/cards/impl/build.gradle.kts @@ -9,7 +9,7 @@ plugins { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { implementation(projects.core) implementation(projects.data.db) diff --git a/features/cards/impl/src/commonMain/kotlin/app/deckbox/features/cards/impl/db/SqlDelightCardDao.kt b/features/cards/impl/src/commonMain/kotlin/app/deckbox/features/cards/impl/db/SqlDelightCardDao.kt index dac9d7093..30099d90e 100644 --- a/features/cards/impl/src/commonMain/kotlin/app/deckbox/features/cards/impl/db/SqlDelightCardDao.kt +++ b/features/cards/impl/src/commonMain/kotlin/app/deckbox/features/cards/impl/db/SqlDelightCardDao.kt @@ -132,6 +132,14 @@ class SqlDelightCardDao( database.attackQueries.insert(attack) } + card.tcgPlayer?.toEntity(card.id)?.let { tcgPlayer -> + database.tcgPlayerPricesQueries.insert(tcgPlayer) + } + + card.cardMarket?.toEntity(card.id)?.let { cardMarket -> + database.cardMarketPricesQueries.insert(cardMarket) + } + afterRollback { bark(ERROR) { "Insert Card Failed Card(${card.id})" } } @@ -168,10 +176,20 @@ class SqlDelightCardDao( .getById(entity.id) .executeAsList() + val tcgPlayerPrices = database.tcgPlayerPricesQueries + .getById(entity.id) + .executeAsOneOrNull() + + val cardMarketPrices = database.cardMarketPricesQueries + .getById(entity.id) + .executeAsOneOrNull() + return entity.toModel( expansion = expansion, abilities = abilities, attacks = attacks, + tcgPlayerPrices = tcgPlayerPrices, + cardMarketPrices = cardMarketPrices, ) } @@ -193,11 +211,23 @@ class SqlDelightCardDao( .executeAsList() .groupBy { it.cardId } + val tcgPlayerPrices = database.tcgPlayerPricesQueries + .getByIds(ids) + .executeAsList() + .associateBy { it.cardId } + + val cardMarketPlayerPrices = database.cardMarketPricesQueries + .getByIds(ids) + .executeAsList() + .associateBy { it.cardId } + return entities.map { entity -> entity.toModel( expansion = expansions.find { it.id == entity.expansionId }!!, abilities = abilities[entity.id] ?: emptyList(), attacks = attacks[entity.id] ?: emptyList(), + tcgPlayerPrices = tcgPlayerPrices[entity.id], + cardMarketPrices = cardMarketPlayerPrices[entity.id], ) } } diff --git a/features/cards/public/build.gradle.kts b/features/cards/public/build.gradle.kts index 02dc4fd21..257fd8af9 100644 --- a/features/cards/public/build.gradle.kts +++ b/features/cards/public/build.gradle.kts @@ -5,7 +5,7 @@ plugins { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { implementation(projects.core) diff --git a/features/cards/ui/build.gradle.kts b/features/cards/ui/build.gradle.kts index 797fcb7bc..cc1480cbc 100644 --- a/features/cards/ui/build.gradle.kts +++ b/features/cards/ui/build.gradle.kts @@ -4,10 +4,22 @@ plugins { kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { implementation(projects.features.cards.public) } } + + jvmMain { + dependencies { + implementation(compose.preview) + } + } + + androidMain { + dependencies { + implementation(compose.preview) + } + } } } diff --git a/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/CardDetail.kt b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/CardDetail.kt index bdbb52771..2b6f9a3cd 100644 --- a/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/CardDetail.kt +++ b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/CardDetail.kt @@ -1,32 +1,30 @@ package app.deckbox.features.cards.ui +import DeckBoxAppBar import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.VectorConverter import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.gestures.rememberTransformableState import androidx.compose.foundation.gestures.transformable import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.exclude import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.BottomSheetScaffold -import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Close +import androidx.compose.material.icons.rounded.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.Text +import androidx.compose.material3.Scaffold +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -43,107 +41,88 @@ import androidx.compose.ui.zIndex import app.deckbox.common.compose.widgets.SpinningPokeballLoadingIndicator import app.deckbox.common.screens.CardDetailScreen import app.deckbox.core.di.MergeActivityScope +import app.deckbox.features.cards.ui.composables.CardMarketPriceCard +import app.deckbox.features.cards.ui.composables.InfoCard +import app.deckbox.features.cards.ui.composables.TcgPlayerPriceCard +import com.moriatsushi.insetsx.navigationBars import com.moriatsushi.insetsx.systemBars import com.r0adkll.kotlininject.merge.annotations.CircuitInject -import com.seiko.imageloader.rememberImagePainter +import com.seiko.imageloader.model.ImageEvent +import com.seiko.imageloader.rememberImageAction +import com.seiko.imageloader.rememberImageActionPainter import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterial3Api::class) @CircuitInject(MergeActivityScope::class, CardDetailScreen::class) @Composable internal fun CardDetail( state: CardDetailUiState, modifier: Modifier = Modifier, ) { - BottomSheetScaffold( - sheetContent = { - Text( - text = "Bottom Sheet", - modifier = Modifier.height(300.dp), + Scaffold( + modifier = modifier, + topBar = { + DeckBoxAppBar( + title = "", + navigationIcon = { + IconButton( + onClick = { state.eventSink(CardDetailUiEvent.NavigateBack) }, + ) { + Icon(Icons.Rounded.ArrowBack, contentDescription = null) + } + }, + colors = TopAppBarDefaults.smallTopAppBarColors( + containerColor = Color.Transparent, + ), ) }, - sheetShape = RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp), - backgroundColor = Color.Black.copy(0.75f), - ) { - Box( - modifier = modifier.fillMaxSize(), + contentWindowInsets = WindowInsets.systemBars.exclude(WindowInsets.navigationBars), + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .verticalScroll(rememberScrollState()), ) { - CompositionLocalProvider( - LocalContentColor provides Color.White, - ) { - when (state) { - CardDetailUiState.Loading -> Loading() - is CardDetailUiState.Error -> Error() - is CardDetailUiState.Loaded -> { - CardDetailContent( - state = state, - modifier = Modifier - .align(Alignment.Center), - ) - } - } - } - } - } -} - -@Composable -private fun BoxScope.Loading( - modifier: Modifier = Modifier, -) { - SpinningPokeballLoadingIndicator( - size = 56.dp, - modifier = modifier.align(Alignment.Center), - ) -} + CardImage( + url = state.cardImageUrl, + contentDescription = state.cardName, + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = 32.dp, + ) + .zIndex(2f), + ) -@Composable -private fun BoxScope.Error( - modifier: Modifier = Modifier, -) { - Text( - text = "Uh-oh! Unable to load pokemon card.", - modifier = modifier - .align(Alignment.Center) - .padding(horizontal = 32.dp), - ) -} + Spacer(Modifier.height(16.dp)) -@Composable -private fun CardDetailContent( - state: CardDetailUiState.Loaded, - modifier: Modifier = Modifier, -) { - Box( - modifier = Modifier - .background(Color.Black.copy(0.5f)) - .windowInsetsPadding( - WindowInsets.systemBars - .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top), + InfoCard( + name = state.cardName, + card = state.pokemonCard, + modifier = Modifier.padding(horizontal = 16.dp), ) - .height(56.dp) - .fillMaxWidth() - .zIndex(1f), - contentAlignment = Alignment.CenterStart, - ) { - IconButton( - onClick = { - (state as? CardDetailUiState.Loaded) - ?.eventSink - ?.invoke(CardDetailUiEvent.NavigateBack) - }, - ) { - Icon(Icons.Rounded.Close, contentDescription = null) + + state.pokemonCard?.tcgPlayer?.let { tcgPlayer -> + Spacer(Modifier.height(16.dp)) + TcgPlayerPriceCard( + tcgPlayer = tcgPlayer, + modifier = Modifier.padding(horizontal = 16.dp), + ) + } + + state.pokemonCard?.cardMarket?.let { cardMarket -> + Spacer(Modifier.height(16.dp)) + CardMarketPriceCard( + cardMarket = cardMarket, + modifier = Modifier.padding(horizontal = 16.dp), + ) + } + + Spacer(Modifier.height(16.dp)) } } - - CardImage( - url = state.card.image.large, - contentDescription = state.card.name, - modifier = modifier.fillMaxSize(), - ) } @Composable @@ -166,38 +145,49 @@ private fun CardImage( } } - val painter = key(url) { rememberImagePainter(url) } - Image( - painter = painter, - modifier = modifier - .graphicsLayer( - scaleX = scale.value, - scaleY = scale.value, - rotationZ = rotation.value, - translationX = offset.value.x, - translationY = offset.value.y, - ) - .transformable(state) - .pointerInput(url) { - coroutineScope { - awaitPointerEventScope { - while (true) { - val event = awaitPointerEvent() - if (event.type == PointerEventType.Release || event.type == PointerEventType.Exit) { - launch { - val s = async { scale.animateTo(1f) } - val r = async { rotation.animateTo(0f) } - val o = async { offset.animateTo(Offset.Zero) } - s.await() - r.await() - o.await() + val imageAction by key(url) { rememberImageAction(url) } + Box( + modifier = modifier, + ) { + Image( + painter = rememberImageActionPainter(imageAction), + modifier = Modifier + .fillMaxWidth() + .graphicsLayer( + scaleX = scale.value, + scaleY = scale.value, + rotationZ = rotation.value, + translationX = offset.value.x, + translationY = offset.value.y, + ) + .transformable(state, enabled = false) + .pointerInput(url) { + coroutineScope { + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent() + if (event.type == PointerEventType.Release || event.type == PointerEventType.Exit) { + launch { + val s = async { scale.animateTo(1f) } + val r = async { rotation.animateTo(0f) } + val o = async { offset.animateTo(Offset.Zero) } + s.await() + r.await() + o.await() + } } } } } - } - }, - contentDescription = contentDescription, - contentScale = ContentScale.FillWidth, - ) + }, + contentDescription = contentDescription, + contentScale = ContentScale.FillWidth, + ) + + if (imageAction is ImageEvent) { + SpinningPokeballLoadingIndicator( + modifier = Modifier.align(Alignment.Center), + ) + } + } } diff --git a/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/CardDetailPresenter.kt b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/CardDetailPresenter.kt index 0bf03cdd9..ac40ac911 100644 --- a/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/CardDetailPresenter.kt +++ b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/CardDetailPresenter.kt @@ -1,12 +1,14 @@ package app.deckbox.features.cards.ui import androidx.compose.runtime.Composable +import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import app.deckbox.common.screens.CardDetailScreen import app.deckbox.core.coroutines.LoadState import app.deckbox.core.di.MergeActivityScope +import app.deckbox.core.model.Card import app.deckbox.features.cards.public.CardRepository import com.r0adkll.kotlininject.merge.annotations.CircuitInject import com.slack.circuit.runtime.Navigator @@ -25,28 +27,31 @@ class CardDetailPresenter( @Composable override fun present(): CardDetailUiState { - val cardLoadState by remember { + val cardLoadState by loadCard(screen.cardId) + + // TODO Load additional card information such as evolution info, similar cards, etc + + return CardDetailUiState( + cardName = screen.cardName, + cardImageUrl = screen.cardImageLarge, + card = cardLoadState, + ) { event -> + when (event) { + CardDetailUiEvent.NavigateBack -> navigator.pop() + } + } + } + + @Composable + private fun loadCard(id: String): State> { + return remember { flow { - val card = repository.getCard(screen.cardId) + val card = repository.getCard(id) emit( card?.let { LoadState.Loaded(card) } ?: LoadState.Error("Unable to load card for ${screen.cardId}"), ) } }.collectAsState(LoadState.Loading) - - // TODO Load additional card information such as evolution info, similar cards, etc - - return when (val state = cardLoadState) { - LoadState.Loading -> CardDetailUiState.Loading - is LoadState.Error -> CardDetailUiState.Error(state.message) - is LoadState.Loaded -> CardDetailUiState.Loaded( - card = state.data, - ) { event -> - when (event) { - CardDetailUiEvent.NavigateBack -> navigator.pop() - } - } - } } } diff --git a/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/CardDetailUiState.kt b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/CardDetailUiState.kt index f27831f90..1e15eae1f 100644 --- a/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/CardDetailUiState.kt +++ b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/CardDetailUiState.kt @@ -1,26 +1,25 @@ package app.deckbox.features.cards.ui import androidx.compose.runtime.Stable +import app.deckbox.core.coroutines.LoadState import app.deckbox.core.model.Card import com.slack.circuit.runtime.CircuitUiEvent import com.slack.circuit.runtime.CircuitUiState -sealed interface CardDetailUiState : CircuitUiState { - object Loading : CardDetailUiState - - @Stable - data class Error(val message: String) : CardDetailUiState - - @Stable - data class Loaded( - val card: Card, - val evolvesFrom: List = emptyList(), - val evolvesTo: List = emptyList(), - val similar: List = emptyList(), - val eventSink: (CardDetailUiEvent) -> Unit, - ) : CardDetailUiState -} +@Stable +data class CardDetailUiState( + val cardName: String, + val cardImageUrl: String, + val card: LoadState, + val evolvesFrom: List = emptyList(), + val evolvesTo: List = emptyList(), + val similar: List = emptyList(), + val eventSink: (CardDetailUiEvent) -> Unit, +) : CircuitUiState sealed interface CardDetailUiEvent : CircuitUiEvent { object NavigateBack : CardDetailUiEvent } + +val CardDetailUiState.pokemonCard: Card? + get() = (card as? LoadState.Loaded)?.data diff --git a/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/CardMarketPriceCard.kt b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/CardMarketPriceCard.kt new file mode 100644 index 000000000..d9051aab0 --- /dev/null +++ b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/CardMarketPriceCard.kt @@ -0,0 +1,69 @@ +package app.deckbox.features.cards.ui.composables + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import app.deckbox.common.resources.strings.CurrencyFormatter +import app.deckbox.common.resources.strings.CurrencyType +import app.deckbox.core.extensions.readableFormat +import app.deckbox.core.model.Card +import cafe.adriel.lyricist.LocalStrings + +@Composable +fun CardMarketPriceCard( + cardMarket: Card.CardMarket, + modifier: Modifier = Modifier, +) { + OutlinedCard( + modifier = modifier, + ) { + PricingHeader( + title = "Cardmarket", + lastUpdated = "Last updated @ ${cardMarket.updatedAt.readableFormat}", + ) + + cardMarket.prices?.let { prices -> + Row( + modifier = Modifier + .padding( + start = 16.dp, + end = 16.dp, + bottom = 16.dp, + ), + ) { + prices.trendPrice?.let { trend -> + PriceUnit( + label = LocalStrings.current.priceTrend, + price = { Text(CurrencyFormatter.format(trend, CurrencyType.EUR)) }, + modifier = Modifier.weight(1f), + ) + } + prices.avg1?.let { avg1 -> + PriceUnit( + label = LocalStrings.current.oneDayAvg, + price = { Text(CurrencyFormatter.format(avg1, CurrencyType.EUR)) }, + modifier = Modifier.weight(1f), + ) + } + prices.avg7?.let { avg7 -> + PriceUnit( + label = LocalStrings.current.sevenDayAvg, + price = { Text(CurrencyFormatter.format(avg7, CurrencyType.EUR)) }, + modifier = Modifier.weight(1f), + ) + } + prices.avg30?.let { avg30 -> + PriceUnit( + label = LocalStrings.current.thirtyDayAvg, + price = { Text(CurrencyFormatter.format(avg30, CurrencyType.EUR)) }, + modifier = Modifier.weight(1f), + ) + } + } + } + } +} diff --git a/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/InfoCard.kt b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/InfoCard.kt new file mode 100644 index 000000000..215d86462 --- /dev/null +++ b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/InfoCard.kt @@ -0,0 +1,175 @@ +package app.deckbox.features.cards.ui.composables + +import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp +import app.deckbox.common.compose.icons.types.TypeIcon +import app.deckbox.common.compose.theme.PokemonTypeColor.toBackgroundColor +import app.deckbox.common.compose.theme.PokemonTypeColor.toColor +import app.deckbox.core.model.Card +import app.deckbox.core.model.Type + +@Composable +internal fun InfoCard( + name: String, + card: Card?, + modifier: Modifier = Modifier, +) { + val backgroundColor by animateColorAsState( + card?.types?.firstOrNull()?.toBackgroundColor() + ?: MaterialTheme.colorScheme.surfaceVariant, + ) + + val borderColor by animateColorAsState( + card?.types?.firstOrNull()?.toColor() + ?: MaterialTheme.colorScheme.onSurfaceVariant, + ) + + OutlinedCard( + colors = CardDefaults.outlinedCardColors( + containerColor = backgroundColor, + ), + border = BorderStroke( + width = 1.dp, + color = borderColor, + ), + modifier = modifier, + ) { + // Header + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding( + start = 16.dp, + end = 16.dp, + top = 16.dp, + ), + ) { + Text( + text = name, + style = MaterialTheme.typography.headlineMedium, + modifier = Modifier.weight(1f), + ) + + val type = card?.types?.firstOrNull() + if (type != null) { + TypeIcon( + type = type, + ) + } + } + + if (card != null) { + InfoChipGroup( + modifier = Modifier.padding( + start = 16.dp, + end = 16.dp, + top = 16.dp, + ), + ) { + InfoChip("Set") { + Text(card.expansion.name) + } + + InfoChip("Number") { + Text( + text = buildAnnotatedString { + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(card.number) + } + append(" of ") + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(card.expansion.printedTotal.toString()) + } + }, + ) + } + + card.artist?.let { artist -> + InfoChip("Artist") { + Text(artist) + } + } + + card.rarity?.let { rarity -> + InfoChip("Rarity") { + Text(rarity) + } + } + + if (card.subtypes.isNotEmpty()) { + InfoChip("Subtypes") { + Text(card.subtypes.joinToString()) + } + } + + if (!card.weaknesses.isNullOrEmpty()) { + InfoChip("Weaknesses") { + Row( + horizontalArrangement = Arrangement.spacedBy(2.dp), + ) { + card.weaknesses?.forEach { effect -> + TypeIcon( + type = effect.type, + modifier = Modifier.size(24.dp), + ) + Text(effect.value) + } + } + } + } + + if (!card.resistances.isNullOrEmpty()) { + InfoChip("Resistances") { + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + card.resistances?.forEach { effect -> + TypeIcon( + type = effect.type, + modifier = Modifier.size(24.dp), + ) + Text(effect.value) + } + } + } + } + + card.convertedRetreatCost?.let { retreatCost -> + InfoChip("Retreat Cost") { + Row( + horizontalArrangement = Arrangement.spacedBy(2.dp), + ) { + repeat(retreatCost) { + TypeIcon( + type = Type.COLORLESS, + modifier = Modifier.size(24.dp), + ) + } + } + } + } + } + + Spacer(Modifier.height(16.dp)) + } + } +} diff --git a/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/InfoChip.kt b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/InfoChip.kt new file mode 100644 index 000000000..d1887053b --- /dev/null +++ b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/InfoChip.kt @@ -0,0 +1,34 @@ +package app.deckbox.features.cards.ui.composables + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp + +@Composable +internal fun InfoChip( + label: String, + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + Column( + modifier = modifier, + ) { + Text( + text = label.uppercase(), + style = MaterialTheme.typography.labelSmall.copy( + fontWeight = FontWeight.SemiBold, + ), + ) + Spacer(Modifier.height(4.dp)) + ProvideTextStyle(MaterialTheme.typography.bodyLarge) { + content() + } + } +} diff --git a/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/InfoChipGroup.kt b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/InfoChipGroup.kt new file mode 100644 index 000000000..d0288920e --- /dev/null +++ b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/InfoChipGroup.kt @@ -0,0 +1,83 @@ +package app.deckbox.features.cards.ui.composables + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import kotlin.math.ceil +import kotlin.math.roundToInt + +private const val DefaultChipColumns = 3 +private val DefaultHorizontalSpacing = 16.dp +private val DefaultVerticalSpacing = 16.dp + +@Composable +internal fun InfoChipGroup( + modifier: Modifier = Modifier, + columns: Int = DefaultChipColumns, + horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(DefaultHorizontalSpacing), + verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(DefaultVerticalSpacing), + content: @Composable () -> Unit, +) { + Layout( + content = content, + modifier = modifier, + ) { measurables, constraints -> + val rowSpacing = (measurables.size - 1) * horizontalArrangement.spacing.roundToPx() + val chipWidth = (constraints.maxWidth - rowSpacing) / columns + + val chipConstraints = Constraints( + minWidth = chipWidth, + maxWidth = chipWidth, + ) + + val placeables = measurables + .map { it.measure(chipConstraints) } + + // Compute widths and horizontal spacing + val horizontalPositions = IntArray(columns) + with(horizontalArrangement) { + arrange( + totalSize = constraints.maxWidth, + sizes = (0 until columns).map { chipWidth }.toIntArray(), + layoutDirection = LayoutDirection.Ltr, + outPositions = horizontalPositions, + ) + } + + // Compute heights and vertical spacing + val rows = ceil(measurables.size.toFloat() / columns.toFloat()).roundToInt() + val columnHeights = (0 until rows).map { row -> + val from = row * columns + val to = (from + columns).coerceAtMost(measurables.size) + placeables.subList(from, to) + .maxBy { it.height } + .height + }.toIntArray() + + val verticalPositions = IntArray(columnHeights.size) + val totalHeight = columnHeights.sum() + + rows.minus(1).coerceAtLeast(0).times(verticalArrangement.spacing.roundToPx()) + + with(verticalArrangement) { + arrange( + totalSize = totalHeight, + sizes = columnHeights, + outPositions = verticalPositions, + ) + } + + layout(constraints.maxWidth, totalHeight) { + placeables.forEachIndexed { index, placeable -> + val column = index % columns + val x = horizontalPositions[column] + val row = index / columns + val y = verticalPositions[row] + placeable.place(x, y) + } + } + } +} diff --git a/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/PriceUnit.kt b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/PriceUnit.kt new file mode 100644 index 000000000..5c37b2b85 --- /dev/null +++ b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/PriceUnit.kt @@ -0,0 +1,30 @@ +package app.deckbox.features.cards.ui.composables + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight + +@Composable +internal fun PriceUnit( + label: String, + price: @Composable () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier, + ) { + Text( + text = label.uppercase(), + style = MaterialTheme.typography.labelSmall.copy( + fontWeight = FontWeight.SemiBold, + ), + ) + ProvideTextStyle(MaterialTheme.typography.bodyLarge) { + price() + } + } +} diff --git a/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/PricingHeader.kt b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/PricingHeader.kt new file mode 100644 index 000000000..1da132846 --- /dev/null +++ b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/PricingHeader.kt @@ -0,0 +1,47 @@ +package app.deckbox.features.cards.ui.composables + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import cafe.adriel.lyricist.LocalStrings + +@Composable +internal fun PricingHeader( + title: String, + lastUpdated: String, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + modifier = Modifier + .weight(1f) + .padding(16.dp), + ) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + ) + Text( + text = lastUpdated, + style = MaterialTheme.typography.labelSmall, + ) + } + + Button( + onClick = {}, + modifier = Modifier.padding(end = 16.dp), + ) { + Text(LocalStrings.current.actionBuy) + } + } +} diff --git a/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/TcgPlayerPriceCard.kt b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/TcgPlayerPriceCard.kt new file mode 100644 index 000000000..9afcea0fc --- /dev/null +++ b/features/cards/ui/src/commonMain/kotlin/app.deckbox.features.cards.ui/composables/TcgPlayerPriceCard.kt @@ -0,0 +1,121 @@ +package app.deckbox.features.cards.ui.composables + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import app.deckbox.common.resources.strings.CurrencyFormatter +import app.deckbox.core.extensions.readableFormat +import app.deckbox.core.model.Card +import cafe.adriel.lyricist.LocalStrings + +@Composable +fun TcgPlayerPriceCard( + tcgPlayer: Card.TcgPlayer, + modifier: Modifier = Modifier, +) { + OutlinedCard( + modifier = modifier, + ) { + PricingHeader( + title = "TCG Player", + lastUpdated = "Last updated @ ${tcgPlayer.updatedAt.readableFormat}", + ) + + tcgPlayer.prices?.let { prices -> + prices.normal?.let { normal -> + PriceRow( + label = LocalStrings.current.tcgPlayerNormal, + price = normal, + ) + } + + prices.holofoil?.let { holofoil -> + PriceRow( + label = LocalStrings.current.tcgPlayerHolofoil, + price = holofoil, + ) + } + + prices.reverseHolofoil?.let { reverseHolofoil -> + PriceRow( + label = LocalStrings.current.tcgPlayerReverseHolofoil, + price = reverseHolofoil, + ) + } + + prices.firstEditionNormal?.let { normal -> + PriceRow( + label = LocalStrings.current.tcgPlayerFirstEditionNormal, + price = normal, + ) + } + + prices.firstEditionHolofoil?.let { holofoil -> + PriceRow( + label = LocalStrings.current.tcgPlayerFirstEditionHolofoil, + price = holofoil, + ) + } + } + } +} + +@Composable +private fun PriceRow( + label: String, + price: Card.TcgPlayer.Price, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .padding( + horizontal = 16.dp, + ), + ) { + Text( + text = label, + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.secondary, + ) + Spacer(Modifier.height(4.dp)) + Row { + price.market?.let { market -> + PriceUnit( + label = LocalStrings.current.priceMarket, + price = { Text(CurrencyFormatter.format(market)) }, + modifier = Modifier.weight(1f), + ) + } + price.low?.let { low -> + PriceUnit( + label = LocalStrings.current.priceLow, + price = { Text(CurrencyFormatter.format(low)) }, + modifier = Modifier.weight(1f), + ) + } + price.mid?.let { mid -> + PriceUnit( + label = LocalStrings.current.priceMid, + price = { Text(CurrencyFormatter.format(mid)) }, + modifier = Modifier.weight(1f), + ) + } + price.high?.let { high -> + PriceUnit( + label = LocalStrings.current.priceHigh, + price = { Text(CurrencyFormatter.format(high)) }, + modifier = Modifier.weight(1f), + ) + } + } + Spacer(Modifier.height(16.dp)) + } +} diff --git a/features/cards/ui/src/jvmMain/kotlin/app/deckbox/features/cards/ui/CardDetailPreview.kt b/features/cards/ui/src/jvmMain/kotlin/app/deckbox/features/cards/ui/CardDetailPreview.kt new file mode 100644 index 000000000..e2b33c399 --- /dev/null +++ b/features/cards/ui/src/jvmMain/kotlin/app/deckbox/features/cards/ui/CardDetailPreview.kt @@ -0,0 +1,4 @@ +// Copyright 2023, Drew Heavner and the Deckbox project contributors +// SPDX-License-Identifier: Apache-2.0 + +package app.deckbox.features.cards.ui diff --git a/features/cards/ui/src/jvmMain/kotlin/app/deckbox/features/cards/ui/composables/InfoCardPreview.kt b/features/cards/ui/src/jvmMain/kotlin/app/deckbox/features/cards/ui/composables/InfoCardPreview.kt new file mode 100644 index 000000000..b64ccc40e --- /dev/null +++ b/features/cards/ui/src/jvmMain/kotlin/app/deckbox/features/cards/ui/composables/InfoCardPreview.kt @@ -0,0 +1,70 @@ +package app.deckbox.features.cards.ui.composables + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import app.deckbox.common.compose.theme.DeckBoxTheme +import app.deckbox.core.model.Card +import app.deckbox.core.model.Expansion +import app.deckbox.core.model.SuperType +import app.deckbox.core.model.Type +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime + +@Preview +@Composable +fun InfoCardPreview() { + DeckBoxTheme { + InfoCard( + name = "Charizard EX", + modifier = Modifier.padding(32.dp), + card = Card( + id = "someid", + name = "Charizard EX", + image = Card.Image("", ""), + supertype = SuperType.POKEMON, + subtypes = emptyList(), + level = null, + hp = 280, + types = listOf( + Type.FIRE, + ), + evolvesFrom = null, + evolvesTo = null, + rules = null, + ancientTrait = null, + abilities = null, + attacks = null, + weaknesses = null, + resistances = null, + retreatCost = listOf(Type.COLORLESS, Type.COLORLESS, Type.COLORLESS), + convertedRetreatCost = 3, + number = "101", + expansion = Expansion( + id = "set1", + name = "Obsidian Flames", + releaseDate = LocalDate(2000, 1, 1), + total = 500, + printedTotal = 500, + series = "Scarlet & Violet", + legalities = null, + ptcgoCode = null, + updatedAt = LocalDateTime(2000, 1, 1, 12, 0, 0, 0), + images = Expansion.Images( + symbol = "", + logo = "", + ), + ), + artist = "r0adkll", + rarity = "Double Rare", + flavorText = null, + nationalPokedexNumbers = null, + legalities = null, + tcgPlayer = null, + cardMarket = null, + ), + ) + } +} diff --git a/features/decks/impl/build.gradle.kts b/features/decks/impl/build.gradle.kts index 1de5f2ddf..8711d1d69 100644 --- a/features/decks/impl/build.gradle.kts +++ b/features/decks/impl/build.gradle.kts @@ -9,7 +9,7 @@ plugins { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { implementation(projects.core) implementation(projects.common.settings) diff --git a/features/decks/public-ui/build.gradle.kts b/features/decks/public-ui/build.gradle.kts index 316877235..90cad1718 100644 --- a/features/decks/public-ui/build.gradle.kts +++ b/features/decks/public-ui/build.gradle.kts @@ -4,7 +4,7 @@ plugins { kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { } } diff --git a/features/decks/public/build.gradle.kts b/features/decks/public/build.gradle.kts index 4d333b7a7..6d5e7da37 100644 --- a/features/decks/public/build.gradle.kts +++ b/features/decks/public/build.gradle.kts @@ -5,7 +5,7 @@ plugins { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { implementation(projects.core) } diff --git a/features/decks/ui/build.gradle.kts b/features/decks/ui/build.gradle.kts index c079a1b17..6baece828 100644 --- a/features/decks/ui/build.gradle.kts +++ b/features/decks/ui/build.gradle.kts @@ -4,7 +4,7 @@ plugins { kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { implementation(projects.features.decks.public) implementation(projects.features.decks.publicUi) diff --git a/features/decks/ui/src/commonMain/kotlin/app.deckbox.ui.decks/Decks.kt b/features/decks/ui/src/commonMain/kotlin/app.deckbox.ui.decks/Decks.kt index f786f231e..527f01e86 100644 --- a/features/decks/ui/src/commonMain/kotlin/app.deckbox.ui.decks/Decks.kt +++ b/features/decks/ui/src/commonMain/kotlin/app.deckbox.ui.decks/Decks.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.exclude import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState @@ -86,7 +87,7 @@ internal fun Decks( }, contentWindowInsets = WindowInsets.systemBars.exclude(WindowInsets.navigationBars), ) { paddingValues -> - CompactDeckContent( + DeckList( decks = state.decks, deckCardConfig = state.deckCardConfig, contentPadding = paddingValues, @@ -102,7 +103,7 @@ internal fun Decks( } @Composable -private fun CompactDeckContent( +private fun DeckList( decks: List, deckCardConfig: DeckCardConfig, contentPadding: PaddingValues, @@ -124,7 +125,8 @@ private fun CompactDeckContent( LazyColumn( verticalArrangement = Arrangement.spacedBy(16.dp), contentPadding = contentPadding, - modifier = modifier, + modifier = modifier + .padding(horizontal = 16.dp), state = state, ) { items( diff --git a/features/expansions/impl/build.gradle.kts b/features/expansions/impl/build.gradle.kts index 4ec23abed..38c536615 100644 --- a/features/expansions/impl/build.gradle.kts +++ b/features/expansions/impl/build.gradle.kts @@ -9,7 +9,7 @@ plugins { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { implementation(projects.core) implementation(projects.data.db) diff --git a/features/expansions/public/build.gradle.kts b/features/expansions/public/build.gradle.kts index 4d333b7a7..6d5e7da37 100644 --- a/features/expansions/public/build.gradle.kts +++ b/features/expansions/public/build.gradle.kts @@ -5,7 +5,7 @@ plugins { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { implementation(projects.core) } diff --git a/features/expansions/ui/build.gradle.kts b/features/expansions/ui/build.gradle.kts index 246939e9e..e6e65a900 100644 --- a/features/expansions/ui/build.gradle.kts +++ b/features/expansions/ui/build.gradle.kts @@ -5,7 +5,7 @@ plugins { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { implementation(projects.features.expansions.public) implementation(projects.features.cards.public) diff --git a/features/expansions/ui/src/commonMain/kotlin/app.deckbox.ui.expansions/detail/ExpansionDetail.kt b/features/expansions/ui/src/commonMain/kotlin/app.deckbox.ui.expansions/detail/ExpansionDetail.kt index 81577284c..d4e4d1abf 100644 --- a/features/expansions/ui/src/commonMain/kotlin/app.deckbox.ui.expansions/detail/ExpansionDetail.kt +++ b/features/expansions/ui/src/commonMain/kotlin/app.deckbox.ui.expansions/detail/ExpansionDetail.kt @@ -17,22 +17,17 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import app.deckbox.common.compose.navigation.isInDetailMode -import app.deckbox.common.compose.overlays.showInFullScreen import app.deckbox.common.compose.widgets.PokeballLoadingIndicator import app.deckbox.common.compose.widgets.PokemonCardGrid -import app.deckbox.common.screens.CardDetailScreen import app.deckbox.common.screens.ExpansionDetailScreen import app.deckbox.core.di.MergeActivityScope import com.r0adkll.kotlininject.merge.annotations.CircuitInject -import com.slack.circuit.overlay.LocalOverlayHost -import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @CircuitInject(MergeActivityScope::class, ExpansionDetailScreen::class) @@ -43,9 +38,6 @@ internal fun ExpansionDetail( ) { val isDetailMode = isInDetailMode() - val coroutineScope = rememberCoroutineScope() - val overlayHost = LocalOverlayHost.current - val scrollState = rememberLazyGridState() val isScrolled by remember { derivedStateOf { @@ -88,9 +80,7 @@ internal fun ExpansionDetail( cardPager = state.cardsPager, state = scrollState, onClick = { card -> - coroutineScope.launch { - overlayHost.showInFullScreen(CardDetailScreen(card.id)) - } + state.eventSink(ExpansionDetailUiEvent.CardSelected(card)) }, contentPadding = paddingValues, modifier = Modifier.padding(horizontal = 16.dp), diff --git a/features/expansions/ui/src/commonMain/kotlin/app.deckbox.ui.expansions/detail/ExpansionDetailPresenter.kt b/features/expansions/ui/src/commonMain/kotlin/app.deckbox.ui.expansions/detail/ExpansionDetailPresenter.kt index fe49743fa..828dbb86c 100644 --- a/features/expansions/ui/src/commonMain/kotlin/app.deckbox.ui.expansions/detail/ExpansionDetailPresenter.kt +++ b/features/expansions/ui/src/commonMain/kotlin/app.deckbox.ui.expansions/detail/ExpansionDetailPresenter.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import app.cash.paging.Pager import app.cash.paging.PagingConfig +import app.deckbox.common.screens.CardDetailScreen import app.deckbox.common.screens.ExpansionDetailScreen import app.deckbox.core.coroutines.LoadState import app.deckbox.core.di.MergeActivityScope @@ -53,6 +54,7 @@ class ExpansionDetailPresenter( ) { event -> when (event) { ExpansionDetailUiEvent.NavigateBack -> navigator.pop() + is ExpansionDetailUiEvent.CardSelected -> navigator.goTo(CardDetailScreen(event.card)) } } } diff --git a/features/expansions/ui/src/commonMain/kotlin/app.deckbox.ui.expansions/detail/ExpansionDetailUiState.kt b/features/expansions/ui/src/commonMain/kotlin/app.deckbox.ui.expansions/detail/ExpansionDetailUiState.kt index 5aafad140..acc9f9896 100644 --- a/features/expansions/ui/src/commonMain/kotlin/app.deckbox.ui.expansions/detail/ExpansionDetailUiState.kt +++ b/features/expansions/ui/src/commonMain/kotlin/app.deckbox.ui.expansions/detail/ExpansionDetailUiState.kt @@ -19,4 +19,5 @@ sealed interface ExpansionDetailUiState : CircuitUiState { sealed interface ExpansionDetailUiEvent : CircuitUiEvent { object NavigateBack : ExpansionDetailUiEvent + data class CardSelected(val card: Card) : ExpansionDetailUiEvent } diff --git a/gradle/build-logic/settings.gradle.kts b/gradle/build-logic/settings.gradle.kts index d27d58af4..61a2c6ff9 100644 --- a/gradle/build-logic/settings.gradle.kts +++ b/gradle/build-logic/settings.gradle.kts @@ -11,23 +11,11 @@ dependencyResolutionManagement { } } -//buildCache { -// val remoteBuildCacheUrl = extra["REMOTE_BUILD_CACHE_URL"] ?: return@buildCache -// val isCi = System.getenv().containsKey("CI") -// -// local { -// isEnabled = !isCi -// } -// -// remote(HttpBuildCache::class) { -// url = uri(remoteBuildCacheUrl) -// isPush = isCi -// -// credentials { -// username = extra["REMOTE_BUILD_CACHE_USERNAME"]?.toString() -// password = extra["REMOTE_BUILD_CACHE_PASSWORD"]?.toString() -// } -// } -//} +buildCache { + val isCi = System.getenv().containsKey("CI") + local { + isEnabled = !isCi + } +} include(":convention") diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 65a7548eb..5f9ab7b00 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -19,7 +19,7 @@ kotlin { } sourceSets { - val commonMain by getting { + commonMain { dependencies { api(projects.core) api(projects.common.screens) @@ -57,7 +57,7 @@ kotlin { } } - val androidMain by getting { + androidMain { dependencies { api(libs.androidx.activity.activity) api(libs.androidx.activity.compose) diff --git a/thirdparty/shimmer/build.gradle.kts b/thirdparty/shimmer/build.gradle.kts index 25facd133..e9d8c72e7 100644 --- a/thirdparty/shimmer/build.gradle.kts +++ b/thirdparty/shimmer/build.gradle.kts @@ -6,7 +6,7 @@ plugins { kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { api(compose.foundation) api(libs.kotlinx.coroutines.core) diff --git a/ui/browse/build.gradle.kts b/ui/browse/build.gradle.kts index 797fcb7bc..790827e91 100644 --- a/ui/browse/build.gradle.kts +++ b/ui/browse/build.gradle.kts @@ -4,7 +4,7 @@ plugins { kotlin { sourceSets { - val commonMain by getting { + commonMain { dependencies { implementation(projects.features.cards.public) } diff --git a/ui/browse/src/commonMain/kotlin/app/deckbox/ui/browse/Browse.kt b/ui/browse/src/commonMain/kotlin/app/deckbox/ui/browse/Browse.kt index a7abc3006..666a9edd3 100644 --- a/ui/browse/src/commonMain/kotlin/app/deckbox/ui/browse/Browse.kt +++ b/ui/browse/src/commonMain/kotlin/app/deckbox/ui/browse/Browse.kt @@ -14,12 +14,10 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex -import app.deckbox.common.compose.overlays.showInFullScreen import app.deckbox.common.compose.widgets.AdaptiveExpandedThreshold import app.deckbox.common.compose.widgets.DefaultEmptyView import app.deckbox.common.compose.widgets.PokemonCardGrid @@ -27,14 +25,11 @@ import app.deckbox.common.compose.widgets.SearchBar import app.deckbox.common.compose.widgets.SearchBarHeight import app.deckbox.common.compose.widgets.SearchEmptyView import app.deckbox.common.screens.BrowseScreen -import app.deckbox.common.screens.CardDetailScreen import app.deckbox.core.di.MergeActivityScope import app.deckbox.core.logging.bark import cafe.adriel.lyricist.LocalStrings import com.moriatsushi.insetsx.statusBars import com.r0adkll.kotlininject.merge.annotations.CircuitInject -import com.slack.circuit.overlay.LocalOverlayHost -import kotlinx.coroutines.launch @CircuitInject(MergeActivityScope::class, BrowseScreen::class) @Composable @@ -42,9 +37,6 @@ internal fun Browse( state: BrowseUiState, modifier: Modifier = Modifier, ) { - val coroutineScope = rememberCoroutineScope() - val overlayHost = LocalOverlayHost.current - Surface( modifier = modifier, ) { @@ -88,9 +80,7 @@ internal fun Browse( PokemonCardGrid( cardPager = state.cardsPager, onClick = { card -> - coroutineScope.launch { - overlayHost.showInFullScreen(CardDetailScreen(card.id)) - } + state.eventSink(BrowseUiEvent.CardClicked(card)) }, contentPadding = PaddingValues( start = 16.dp, diff --git a/ui/browse/src/commonMain/kotlin/app/deckbox/ui/browse/BrowsePresenter.kt b/ui/browse/src/commonMain/kotlin/app/deckbox/ui/browse/BrowsePresenter.kt index 7ca339980..b0b5b0274 100644 --- a/ui/browse/src/commonMain/kotlin/app/deckbox/ui/browse/BrowsePresenter.kt +++ b/ui/browse/src/commonMain/kotlin/app/deckbox/ui/browse/BrowsePresenter.kt @@ -90,7 +90,7 @@ class BrowsePresenter( coroutineScope.launch { queryPipeline.emit(event.query) } } BrowseUiEvent.SearchCleared -> searchQuery = null - is BrowseUiEvent.CardClicked -> navigator.goTo(CardDetailScreen(event.card.id)) + is BrowseUiEvent.CardClicked -> navigator.goTo(CardDetailScreen(event.card)) } } }