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))
}
}
}