diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ec4f5eb7..25ce1dcb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -141,6 +141,7 @@ dependencies { implementation("androidx.navigation:navigation-fragment-ktx:${Versions.ANDROIDX_NAVIGATION_VERSION}") implementation("androidx.navigation:navigation-runtime-ktx:${Versions.ANDROIDX_NAVIGATION_VERSION}") implementation("androidx.navigation:navigation-ui-ktx:${Versions.ANDROIDX_NAVIGATION_VERSION}") + implementation("androidx.navigation:navigation-compose:${Versions.ANDROIDX_NAVIGATION_COMPOSE_VERSION}") implementation("com.google.dagger:hilt-android:${Versions.HILT_VERSION}") diff --git a/app/src/main/java/co/nimblehq/compose/crypto/extension/ViewModelExt.kt b/app/src/main/java/co/nimblehq/compose/crypto/extension/ViewModelExt.kt new file mode 100644 index 00000000..b89d534a --- /dev/null +++ b/app/src/main/java/co/nimblehq/compose/crypto/extension/ViewModelExt.kt @@ -0,0 +1,20 @@ +package co.nimblehq.compose.crypto.extension + +import androidx.activity.ComponentActivity +import androidx.activity.viewModels +import androidx.annotation.MainThread +import androidx.lifecycle.* +import androidx.lifecycle.viewmodel.CreationExtras + +@MainThread +inline fun ComponentActivity.provideViewModels( + noinline factoryProducer: (() -> CreationExtras)? = null +): Lazy = OverridableLazy(viewModels(factoryProducer)) + +class OverridableLazy(var implementation: Lazy) : Lazy { + + override val value + get() = implementation.value + + override fun isInitialized() = implementation.isInitialized() +} diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/PriceChange.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/common/price/PriceChange.kt similarity index 76% rename from app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/PriceChange.kt rename to app/src/main/java/co/nimblehq/compose/crypto/ui/common/price/PriceChange.kt index 1ef55d74..04d956ca 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/PriceChange.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/common/price/PriceChange.kt @@ -1,4 +1,4 @@ -package co.nimblehq.compose.crypto.ui.screens.home +package co.nimblehq.compose.crypto.ui.common.price import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding @@ -11,39 +11,34 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.Dp import co.nimblehq.compose.crypto.R import co.nimblehq.compose.crypto.extension.toFormattedString import co.nimblehq.compose.crypto.ui.preview.CoinItemPreviewParameterProvider import co.nimblehq.compose.crypto.ui.theme.* import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp13 +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp9 import co.nimblehq.compose.crypto.ui.uimodel.CoinItemUiModel import kotlin.math.abs -@Suppress("FunctionNaming", "LongMethod") @Composable fun PriceChange( priceChangePercentage24hInCurrency: Double, modifier: Modifier, - iconPaddingEnd: Dp + displayForDetailPage: Boolean = false ) { val isPositiveNumber = priceChangePercentage24hInCurrency >= 0 Row(modifier = modifier) { Icon( modifier = Modifier - .padding(end = iconPaddingEnd) + .padding(end = if (displayForDetailPage) Dp9 else Dp13) .align(alignment = Alignment.CenterVertically), painter = if (isPositiveNumber) { painterResource(id = R.drawable.ic_guppie_green_arrow_up) } else { painterResource(id = R.drawable.ic_fire_opal_arrow_down) }, - tint = if (isPositiveNumber) { - Color.GuppieGreen - } else { - Color.FireOpal - }, + tint = if (isPositiveNumber) Color.GuppieGreen else Color.FireOpal, contentDescription = null ) @@ -52,16 +47,12 @@ fun PriceChange( R.string.coin_profit_percent, abs(priceChangePercentage24hInCurrency).toFormattedString() ), - style = if (isPositiveNumber) { - Style.guppieGreenMedium16() - } else { - Style.fireOpalGreenMedium16() - } + style = if (displayForDetailPage) Style.medium14() else Style.medium16(), + color = if (isPositiveNumber) Color.GuppieGreen else Color.FireOpal ) } } -@Suppress("FunctionNaming") @Composable @Preview fun PriceChangePreview( @@ -70,8 +61,7 @@ fun PriceChangePreview( ComposeTheme { PriceChange( priceChangePercentage24hInCurrency = coinItem.priceChangePercentage24hInCurrency, - modifier = Modifier, - iconPaddingEnd = Dp13 + modifier = Modifier ) } } diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/common/price/PriceChangeButton.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/common/price/PriceChangeButton.kt new file mode 100644 index 00000000..da213376 --- /dev/null +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/common/price/PriceChangeButton.kt @@ -0,0 +1,69 @@ +package co.nimblehq.compose.crypto.ui.common.price + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import co.nimblehq.compose.crypto.R +import co.nimblehq.compose.crypto.ui.theme.* +import co.nimblehq.compose.crypto.ui.theme.Color.FireOpal +import co.nimblehq.compose.crypto.ui.theme.Color.GuppieGreen +import co.nimblehq.compose.crypto.ui.theme.Color.Water +import co.nimblehq.compose.crypto.ui.theme.Color.WhiteIce +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp0 +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp13 +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp9 + +@Composable +fun PriceChangeButton( + modifier: Modifier, + priceChangePercent: String, + displayForDetailPage: Boolean = false, + isPositiveNumber: Boolean = false +) { + Button( + modifier = modifier, + colors = ButtonDefaults.buttonColors( + backgroundColor = if (displayForDetailPage) WhiteIce else Water, + contentColor = if (isPositiveNumber) GuppieGreen else FireOpal + ), + shape = RoundedCornerShape(Dimension.Dp20), + contentPadding = PaddingValues(start = Dp13, end = Dimension.Dp8), + elevation = ButtonDefaults.elevation(defaultElevation = Dp0, pressedElevation = Dp0), + onClick = { /* TODO */ }, + ) { + Icon( + modifier = Modifier + .padding(end = if (displayForDetailPage) Dp9 else Dp13) + .align(alignment = Alignment.CenterVertically), + painter = if (isPositiveNumber) { + painterResource(id = R.drawable.ic_guppie_green_arrow_up) + } else { + painterResource(id = R.drawable.ic_fire_opal_arrow_down) + }, + contentDescription = null + ) + + Text( + text = stringResource(R.string.coin_profit_percent, priceChangePercent), + style = Style.medium16() + ) + } +} + +@Composable +@Preview +fun PriceChangeButtonPreview() { + ComposeTheme { + PriceChangeButton( + modifier = Modifier, + priceChangePercent = "6.21" + ) + } +} diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/MainActivity.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/MainActivity.kt index 1733a7fb..3a9955e4 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/MainActivity.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/MainActivity.kt @@ -4,7 +4,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.core.view.WindowCompat -import co.nimblehq.compose.crypto.ui.screens.home.HomeScreen +import co.nimblehq.compose.crypto.ui.screens.navigation.CryptoNavGraph import co.nimblehq.compose.crypto.ui.theme.ComposeTheme import dagger.hilt.android.AndroidEntryPoint @@ -17,7 +17,7 @@ class MainActivity : ComponentActivity() { WindowCompat.setDecorFitsSystemWindows(window, false) setContent { ComposeTheme { - HomeScreen() + CryptoNavGraph(this) } } } diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/Appbar.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/Appbar.kt new file mode 100644 index 00000000..1fd4ab08 --- /dev/null +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/Appbar.kt @@ -0,0 +1,72 @@ +package co.nimblehq.compose.crypto.ui.screens.detail + +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import co.nimblehq.compose.crypto.R +import co.nimblehq.compose.crypto.ui.theme.ComposeTheme +import co.nimblehq.compose.crypto.ui.theme.Style +import co.nimblehq.compose.crypto.ui.theme.Style.coinInfoAppBarIconColor +import co.nimblehq.compose.crypto.ui.theme.Style.textColor + +@Composable +fun Appbar( + modifier: Modifier +) { + BoxWithConstraints(modifier = modifier.fillMaxWidth()) { + IconButton( + modifier = Modifier.align(Alignment.CenterStart), + onClick = { /*TODO*/ } + ) { + Icon( + painter = painterResource(id = R.drawable.ic_back), + tint = MaterialTheme.colors.coinInfoAppBarIconColor, + contentDescription = null + ) + } + + Text( + modifier = Modifier.align(Alignment.Center), + // TODO: Remove dummy value when work on Integrate. + text = "Ethereum", + color = MaterialTheme.colors.textColor, + style = Style.medium16() + ) + + IconButton( + modifier = Modifier.align(Alignment.CenterEnd), + onClick = { /*TODO*/ } + ) { + Icon( + painter = painterResource(id = R.drawable.ic_heart), + tint = MaterialTheme.colors.coinInfoAppBarIconColor, + contentDescription = null + ) + } + } +} + +@Preview +@Composable +fun AppbarPreview() { + ComposeTheme { + Surface { + Appbar(modifier = Modifier) + } + } +} + +@Preview +@Composable +fun AppbarPreviewDark() { + ComposeTheme(darkTheme = true) { + Surface { + Appbar(modifier = Modifier) + } + } +} diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/DetailItem.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/DetailItem.kt new file mode 100644 index 00000000..6f5cccd1 --- /dev/null +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/DetailItem.kt @@ -0,0 +1,102 @@ +package co.nimblehq.compose.crypto.ui.screens.detail + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension +import co.nimblehq.compose.crypto.R +import co.nimblehq.compose.crypto.ui.common.price.PriceChange +import co.nimblehq.compose.crypto.ui.theme.ComposeTheme +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp4 +import co.nimblehq.compose.crypto.ui.theme.Style +import co.nimblehq.compose.crypto.ui.theme.Style.coinNameColor +import co.nimblehq.compose.crypto.ui.theme.Style.textColor + +@Composable +fun DetailItem( + modifier: Modifier, + // TODO Update values to Object on Integrate ticket + title: String, + price: String, + pricePercent: Double +) { + ConstraintLayout( + modifier = modifier.fillMaxWidth() + ) { + val ( + itemTitle, + itemPrice, + itemPriceChange + ) = createRefs() + + Text( + modifier = Modifier + .constrainAs(itemTitle) { + top.linkTo(parent.top) + start.linkTo(parent.start) + }, + text = title, + color = MaterialTheme.colors.coinNameColor, + style = Style.medium12() + ) + + Text( + modifier = Modifier + .padding(top = Dp4) + .constrainAs(itemPrice) { + start.linkTo(itemTitle.start) + top.linkTo(itemTitle.bottom) + width = Dimension.preferredWrapContent + }, + text = stringResource(R.string.coin_currency, price), + color = MaterialTheme.colors.textColor, + style = Style.medium16() + ) + + PriceChange( + priceChangePercentage24hInCurrency = pricePercent, + modifier = Modifier + .constrainAs(itemPriceChange) { + end.linkTo(parent.end) + top.linkTo(itemTitle.top) + bottom.linkTo(itemPrice.bottom) + }, + displayForDetailPage = true + ) + } +} + +@Composable +@Preview +fun DetailItemPreview() { + ComposeTheme { + Surface { + DetailItem( + modifier = Modifier, + title = "Market Cap", + price = "387,992,058,833.42", + pricePercent = 7.23 + ) + } + } +} + +@Composable +@Preview +fun DetailItemPreviewDark() { + ComposeTheme(darkTheme = true) { + Surface { + DetailItem( + modifier = Modifier, + title = "Market Cap", + price = "387,992,058,833.42", + pricePercent = -7.23 + ) + } + } +} diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/DetailScreen.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/DetailScreen.kt new file mode 100644 index 00000000..10fbe08f --- /dev/null +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/DetailScreen.kt @@ -0,0 +1,172 @@ +package co.nimblehq.compose.crypto.ui.screens.detail + +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstraintLayout +import co.nimblehq.compose.crypto.R +import co.nimblehq.compose.crypto.ui.common.price.PriceChangeButton +import co.nimblehq.compose.crypto.ui.theme.* +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp0 +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp16 +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp60 +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp8 +import co.nimblehq.compose.crypto.ui.theme.Style +import co.nimblehq.compose.crypto.ui.theme.Style.textColor +import coil.compose.rememberAsyncImagePainter + +@Composable +fun DetailScreen() { + val localDensity = LocalDensity.current + val sellBuyLayoutHeight = remember { mutableStateOf(Dp0) } + + Surface { + ConstraintLayout( + modifier = Modifier + .fillMaxSize() + .systemBarsPadding() + .verticalScroll(state = rememberScrollState()) + ) { + + val ( + appBar, + logo, + currentPrice, + priceChangePercent, + graph, + coinInfoItem + ) = createRefs() + + Appbar(modifier = Modifier + .constrainAs(appBar) { + top.linkTo(parent.top) + start.linkTo(parent.start) + } + ) + + Image( + modifier = Modifier + .size(Dp60) + .constrainAs(logo) { + top.linkTo(appBar.bottom) + linkTo(start = parent.start, end = parent.end) + } + .padding(top = Dp8), + // TODO: Remove dummy image when work on Integrate. + painter = rememberAsyncImagePainter( + "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579" + ), + contentDescription = null + ) + + Text( + modifier = Modifier + .constrainAs(currentPrice) { + top.linkTo(logo.bottom) + linkTo(start = parent.start, end = parent.end) + } + .padding(vertical = Dp8), + // TODO: Remove dummy value when work on Integrate. + text = stringResource(R.string.coin_currency, "3,260.62"), + color = MaterialTheme.colors.textColor, + style = Style.semiBold24() + ) + + PriceChangeButton( + modifier = Modifier + .constrainAs(priceChangePercent) { + top.linkTo(currentPrice.bottom) + linkTo(start = parent.start, end = parent.end) + }, + // TODO: Remove dummy image when work on Integrate. + priceChangePercent = "6.21", + displayForDetailPage = true, + isPositiveNumber = true + ) + + // TODO: Update this section when work create UI for a graph. + Spacer( + modifier = Modifier + .height(350.dp) + .constrainAs(graph) { + top.linkTo(priceChangePercent.bottom) + } + ) + + CoinInfo( + modifier = Modifier.constrainAs(coinInfoItem) { + top.linkTo(graph.bottom) + }, + sellBuyLayoutHeight = sellBuyLayoutHeight.value + ) + } + + Box( + modifier = Modifier + .fillMaxSize() + .navigationBarsPadding(), + contentAlignment = Alignment.BottomEnd + ) { + SellBuyGroup( + modifier = Modifier.onGloballyPositioned { + sellBuyLayoutHeight.value = with(localDensity) { it.size.height.toDp() } + } + ) + } + } +} + +@Composable +private fun CoinInfo( + modifier: Modifier, + sellBuyLayoutHeight: Dp +) { + // TODO: Remove dummy value when work on Integrate. + Column(modifier = modifier.padding(start = Dp16, end = Dp16, bottom = sellBuyLayoutHeight)) { + DetailItem( + modifier = Modifier, + title = "Market Cap", + price = "387,992,058,833.42", + pricePercent = 7.32 + ) + + DetailItem( + modifier = Modifier.padding(top = Dp16), + title = "All Time High", + price = "4,891.70", + pricePercent = 33.42 + ) + + DetailItem( + modifier = Modifier.padding(vertical = Dp16), + title = "All Time Low", + price = "0.4209", + pricePercent = 773717.23 + ) + } +} + +@Composable +@Preview +fun DetailScreenPreview() { + ComposeTheme { + DetailScreen() + } +} + +@Composable +@Preview +fun DetailScreenPreviewDark() { + ComposeTheme(darkTheme = true) { + DetailScreen() + } +} diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/SellBuyButton.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/SellBuyButton.kt new file mode 100644 index 00000000..81306e30 --- /dev/null +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/SellBuyButton.kt @@ -0,0 +1,56 @@ +package co.nimblehq.compose.crypto.ui.screens.detail + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import co.nimblehq.compose.crypto.ui.theme.Color.FireOpal +import co.nimblehq.compose.crypto.ui.theme.Color.White +import co.nimblehq.compose.crypto.ui.theme.ComposeTheme +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp0 +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp12 +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp16 +import co.nimblehq.compose.crypto.ui.theme.Style + +@Composable +fun SellBuyButton( + modifier: Modifier, + backgroundColor: Color, + text: String +) { + Button( + modifier = modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + backgroundColor = backgroundColor, + contentColor = White + ), + shape = RoundedCornerShape(Dp12), + elevation = ButtonDefaults.elevation( + defaultElevation = Dp0, + pressedElevation = Dp0 + ), + contentPadding = PaddingValues(vertical = Dp16), + onClick = { /* TODO */ }, + ) { + Text( + text = text, + style = Style.medium14() + ) + } +} + +@Composable +@Preview +fun SellBuyButtonPreview() { + ComposeTheme { + SellBuyButton( + modifier = Modifier, + backgroundColor = FireOpal, + text = "Sell" + ) + } +} diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/SellBuyGroup.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/SellBuyGroup.kt new file mode 100644 index 00000000..a24b9d37 --- /dev/null +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/SellBuyGroup.kt @@ -0,0 +1,62 @@ +package co.nimblehq.compose.crypto.ui.screens.detail + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import co.nimblehq.compose.crypto.R +import co.nimblehq.compose.crypto.ui.theme.Color.FireOpal +import co.nimblehq.compose.crypto.ui.theme.Color.TiffanyBlue +import co.nimblehq.compose.crypto.ui.theme.ComposeTheme +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp12 +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp16 +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp20 +import co.nimblehq.compose.crypto.ui.theme.Style.coinInfoSellBuyBackground + +@Composable +fun SellBuyGroup( + modifier: Modifier +) { + Row( + modifier = modifier + .fillMaxWidth() + .background(MaterialTheme.colors.coinInfoSellBuyBackground) + .padding(horizontal = Dp20, vertical = Dp16), + ) { + + SellBuyButton( + modifier = Modifier + .weight(1f) + .padding(end = Dp12), + backgroundColor = FireOpal, + text = stringResource(id = R.string.coin_info_sell_button) + ) + + SellBuyButton( + modifier = Modifier + .weight(1f) + .padding(start = Dp12), + backgroundColor = TiffanyBlue, + text = stringResource(id = R.string.coin_info_buy_button) + ) + } +} + +@Composable +@Preview +fun SellBuyGroupPreview() { + ComposeTheme { + SellBuyGroup(modifier = Modifier) + } +} + +@Composable +@Preview +fun SellBuyGroupPreviewDark() { + ComposeTheme(darkTheme = true) { + SellBuyGroup(modifier = Modifier) + } +} diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/CoinItem.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/CoinItem.kt index ca6f3eea..06125213 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/CoinItem.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/CoinItem.kt @@ -4,7 +4,10 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -18,15 +21,15 @@ import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import co.nimblehq.compose.crypto.R import co.nimblehq.compose.crypto.extension.toFormattedString +import co.nimblehq.compose.crypto.ui.common.price.PriceChange import co.nimblehq.compose.crypto.ui.preview.CoinItemPreviewParameterProvider import co.nimblehq.compose.crypto.ui.theme.ComposeTheme import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp12 -import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp13 +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp14 import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp16 -import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp22 import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp25 import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp4 -import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp60 +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp40 import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp8 import co.nimblehq.compose.crypto.ui.theme.Style import co.nimblehq.compose.crypto.ui.theme.Style.coinItemColor @@ -35,17 +38,18 @@ import co.nimblehq.compose.crypto.ui.theme.Style.textColor import co.nimblehq.compose.crypto.ui.uimodel.CoinItemUiModel import coil.compose.rememberAsyncImagePainter -@Suppress("FunctionNaming", "LongMethod") @Composable fun CoinItem( - coinItem: CoinItemUiModel + coinItem: CoinItemUiModel, + onMyCoinsItemClick: () -> Unit ) { ConstraintLayout( modifier = Modifier .wrapContentWidth() .clip(RoundedCornerShape(Dp12)) + .clickable { onMyCoinsItemClick.invoke() } .background(color = MaterialTheme.colors.coinItemColor) - .padding(horizontal = Dp8, vertical = Dp8) + .padding(Dp8) ) { val ( logo, @@ -57,11 +61,10 @@ fun CoinItem( Image( modifier = Modifier - .size(Dp60) - .padding(end = Dp16) + .size(Dp40) .constrainAs(logo) { - top.linkTo(coinSymbol.top) - bottom.linkTo(coinName.bottom) + top.linkTo(anchor = coinSymbol.top, margin = Dp8) + bottom.linkTo(anchor = coinName.bottom, margin = Dp8) start.linkTo(parent.start) }, painter = rememberAsyncImagePainter(coinItem.image), @@ -72,7 +75,7 @@ fun CoinItem( modifier = Modifier .constrainAs(coinSymbol) { top.linkTo(parent.top) - start.linkTo(logo.end) + start.linkTo(anchor = logo.end, margin = Dp16) }, text = coinItem.symbol.uppercase(), color = MaterialTheme.colors.textColor, @@ -94,10 +97,9 @@ fun CoinItem( Text( modifier = Modifier - .padding(top = Dp22) .constrainAs(price) { start.linkTo(logo.start) - top.linkTo(coinName.bottom) + top.linkTo(anchor = coinName.bottom, margin = Dp14) width = Dimension.preferredWrapContent }, text = stringResource( @@ -116,30 +118,33 @@ fun CoinItem( start.linkTo(price.end) bottom.linkTo(parent.bottom) width = Dimension.preferredWrapContent - }, - iconPaddingEnd = Dp13 + } ) } } -@Suppress("FunctionNaming") @Composable @Preview(uiMode = UI_MODE_NIGHT_NO) fun CoinItemPreview( @PreviewParameter(CoinItemPreviewParameterProvider::class) coinItem: CoinItemUiModel ) { ComposeTheme { - CoinItem(coinItem) + CoinItem( + coinItem = coinItem, + onMyCoinsItemClick = {} + ) } } -@Suppress("FunctionNaming") @Composable @Preview(uiMode = UI_MODE_NIGHT_YES) fun CoinItemPreviewDark( @PreviewParameter(CoinItemPreviewParameterProvider::class) coinItem: CoinItemUiModel ) { ComposeTheme { - CoinItem(coinItem) + CoinItem( + coinItem = coinItem, + onMyCoinsItemClick = {} + ) } } diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreen.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreen.kt index 7d03b31a..93644bae 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreen.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreen.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.constraintlayout.compose.ConstraintLayout -import androidx.lifecycle.viewmodel.compose.viewModel import co.nimblehq.compose.crypto.R import co.nimblehq.compose.crypto.lib.IsLoading import co.nimblehq.compose.crypto.ui.preview.HomeScreenParams @@ -31,10 +30,11 @@ import co.nimblehq.compose.crypto.ui.theme.Style.textColor import co.nimblehq.compose.crypto.ui.uimodel.CoinItemUiModel import co.nimblehq.compose.crypto.ui.userReadableMessage -@Suppress("FunctionNaming", "LongMethod") @Composable fun HomeScreen( - viewModel: HomeViewModel = viewModel() + viewModel: HomeViewModel, + // TODO: Remove it and handle in VM instead + onMyCoinsItemClick: () -> Unit ) { val context = LocalContext.current LaunchedEffect(Unit) { @@ -53,17 +53,19 @@ fun HomeScreen( showMyCoinsLoading = showMyCoinsLoading, showTrendingCoinsLoading = showTrendingCoinsLoading, myCoins = myCoins, - trendingCoins = trendingCoins + trendingCoins = trendingCoins, + onMyCoinsItemClick = onMyCoinsItemClick ) } -@Suppress("FunctionNaming") +@Suppress("LongParameterList") @Composable private fun HomeScreenContent( showMyCoinsLoading: IsLoading, showTrendingCoinsLoading: IsLoading, myCoins: List, trendingCoins: List, + onMyCoinsItemClick: () -> Unit ) { Surface { Column( @@ -93,7 +95,8 @@ private fun HomeScreenContent( item { MyCoins( showMyCoinsLoading = showMyCoinsLoading, - coins = myCoins + coins = myCoins, + onMyCoinsItemClick = onMyCoinsItemClick ) } @@ -137,11 +140,11 @@ private fun HomeScreenContent( } } -@Suppress("FunctionNaming", "LongMethod", "MagicNumber") @Composable private fun MyCoins( showMyCoinsLoading: IsLoading, - coins: List + coins: List, + onMyCoinsItemClick: () -> Unit ) { ConstraintLayout( modifier = Modifier @@ -196,14 +199,16 @@ private fun MyCoins( horizontalArrangement = Arrangement.spacedBy(Dp16) ) { items(coins) { coin -> - CoinItem(coin) + CoinItem( + coinItem = coin, + onMyCoinsItemClick = onMyCoinsItemClick + ) } } } } } -@Suppress("FunctionNaming") @Composable @Preview(showSystemUi = true, uiMode = UI_MODE_NIGHT_NO) fun HomeScreenPreview( @@ -216,12 +221,12 @@ fun HomeScreenPreview( showTrendingCoinsLoading = isLoading, myCoins = myCoins, trendingCoins = trendingCoins, + onMyCoinsItemClick = {} ) } } } -@Suppress("FunctionNaming") @Composable @Preview(showSystemUi = true, uiMode = UI_MODE_NIGHT_YES) fun HomeScreenPreviewDark( @@ -234,6 +239,7 @@ fun HomeScreenPreviewDark( showTrendingCoinsLoading = isLoading, myCoins = myCoins, trendingCoins = trendingCoins, + onMyCoinsItemClick = {} ) } } diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModel.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModel.kt index 66ce07cc..70f09692 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModel.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModel.kt @@ -55,47 +55,43 @@ class HomeViewModel @Inject constructor( getTrendingCoins() } - private fun getMyCoins() { - execute { - _showMyCoinsLoading.value = true - getMyCoinsUseCase.execute( - GetMyCoinsUseCase.Input( - currency = MY_COINS_CURRENCY, - order = MY_COINS_ORDER, - priceChangeInHour = MY_COINS_PRICE_CHANGE_IN_HOUR, - itemPerPage = MY_COINS_ITEM_PER_PAGE, - page = MY_COINS_INITIAL_PAGE - ) + private fun getMyCoins() = execute { + _showMyCoinsLoading.value = true + getMyCoinsUseCase.execute( + GetMyCoinsUseCase.Input( + currency = MY_COINS_CURRENCY, + order = MY_COINS_ORDER, + priceChangeInHour = MY_COINS_PRICE_CHANGE_IN_HOUR, + itemPerPage = MY_COINS_ITEM_PER_PAGE, + page = MY_COINS_INITIAL_PAGE ) - .catch { e -> - _error.emit(e) - } - .collect { coins -> - _myCoins.emit(coins.map { it.toUiModel() }) - } - _showMyCoinsLoading.value = false - } + ) + .catch { e -> + _error.emit(e) + } + .collect { coins -> + _myCoins.emit(coins.map { it.toUiModel() }) + } + _showMyCoinsLoading.value = false } - private fun getTrendingCoins() { - execute { - _showTrendingCoinsLoading.value = true - getTrendingCoinsUseCase.execute( - GetTrendingCoinsUseCase.Input( - currency = MY_COINS_CURRENCY, - order = MY_COINS_ORDER, - priceChangeInHour = MY_COINS_PRICE_CHANGE_IN_HOUR, - itemPerPage = MY_COINS_ITEM_PER_PAGE, - page = MY_COINS_INITIAL_PAGE - ) + private fun getTrendingCoins() = execute { + _showTrendingCoinsLoading.value = true + getTrendingCoinsUseCase.execute( + GetTrendingCoinsUseCase.Input( + currency = MY_COINS_CURRENCY, + order = MY_COINS_ORDER, + priceChangeInHour = MY_COINS_PRICE_CHANGE_IN_HOUR, + itemPerPage = MY_COINS_ITEM_PER_PAGE, + page = MY_COINS_INITIAL_PAGE ) - .catch { e -> - _error.emit(e) - } - .collect { coins -> - _trendingCoins.emit(coins.map { it.toUiModel() }) - } - _showTrendingCoinsLoading.value = false - } + ) + .catch { e -> + _error.emit(e) + } + .collect { coins -> + _trendingCoins.emit(coins.map { it.toUiModel() }) + } + _showTrendingCoinsLoading.value = false } } diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/PortfolioCard.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/PortfolioCard.kt index a3378842..bc0c593e 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/PortfolioCard.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/PortfolioCard.kt @@ -1,33 +1,25 @@ package co.nimblehq.compose.crypto.ui.screens.home import androidx.compose.foundation.background -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.* +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.constraintlayout.compose.ConstraintLayout import co.nimblehq.compose.crypto.R -import co.nimblehq.compose.crypto.ui.theme.Color -import co.nimblehq.compose.crypto.ui.theme.ComposeTheme -import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp0 +import co.nimblehq.compose.crypto.ui.common.price.PriceChangeButton +import co.nimblehq.compose.crypto.ui.theme.* import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp12 -import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp13 import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp16 -import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp20 import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp40 import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp8 -import co.nimblehq.compose.crypto.ui.theme.Style -@Suppress("FunctionNaming", "LongMethod") @Composable fun PortfolioCard( modifier: Modifier @@ -89,36 +81,19 @@ fun PortfolioCard( style = Style.whiteSemiBold24() ) - Button( + PriceChangeButton( modifier = Modifier - .shadow(elevation = Dp0) .constrainAs(profitPercent) { linkTo(top = todayProfitLabel.top, bottom = todayProfit.bottom) end.linkTo(parent.end) }, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.Water, - contentColor = Color.GuppieGreen - ), - shape = RoundedCornerShape(Dp20), - contentPadding = PaddingValues(start = Dp13, end = Dp8), - onClick = { /* TODO */ } - ) { - Icon( - painter = painterResource(id = R.drawable.ic_guppie_green_arrow_up), - modifier = Modifier.padding(end = Dp13), - contentDescription = null - ) - Text( - // TODO: Remove dummy value when work on Integrate. - text = stringResource(R.string.coin_profit_percent, "2.41"), - style = Style.medium16() - ) - } + // TODO: Remove dummy value when work on Integrate. + priceChangePercent = "6.21", + isPositiveNumber = true + ) } } -@Suppress("FunctionNaming") @Composable @Preview fun PortfolioCardPreview() { diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/SeeAll.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/SeeAll.kt index 16abdc18..b8921fd9 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/SeeAll.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/SeeAll.kt @@ -1,8 +1,6 @@ package co.nimblehq.compose.crypto.ui.screens.home -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.material.icons.Icons @@ -13,13 +11,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import co.nimblehq.compose.crypto.R -import co.nimblehq.compose.crypto.ui.theme.Color -import co.nimblehq.compose.crypto.ui.theme.ComposeTheme +import co.nimblehq.compose.crypto.ui.theme.* import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp14 import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp8 -import co.nimblehq.compose.crypto.ui.theme.Style -@Suppress("FunctionNaming") @Composable fun SeeAll( modifier: Modifier @@ -41,7 +36,6 @@ fun SeeAll( } } -@Suppress("FunctionNaming") @Composable @Preview fun SeeAllPreview() { diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/TrendingItem.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/TrendingItem.kt index 92f1a713..75a7473c 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/TrendingItem.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/TrendingItem.kt @@ -4,7 +4,9 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -15,12 +17,13 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension +import co.nimblehq.compose.crypto.ui.common.price.PriceChange import co.nimblehq.compose.crypto.ui.preview.CoinItemPreviewParameterProvider import co.nimblehq.compose.crypto.ui.theme.ComposeTheme import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp12 -import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp13 import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp16 -import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp60 +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp40 +import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp6 import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp8 import co.nimblehq.compose.crypto.ui.theme.Style import co.nimblehq.compose.crypto.ui.theme.Style.coinItemColor @@ -29,7 +32,6 @@ import co.nimblehq.compose.crypto.ui.theme.Style.textColor import co.nimblehq.compose.crypto.ui.uimodel.CoinItemUiModel import coil.compose.rememberAsyncImagePainter -@Suppress("FunctionNaming", "LongMethod") @Composable fun TrendingItem( coinItem: CoinItemUiModel @@ -40,7 +42,7 @@ fun TrendingItem( .fillMaxWidth() .clip(RoundedCornerShape(Dp12)) .background(color = MaterialTheme.colors.coinItemColor) - .padding(horizontal = Dp8, vertical = Dp8) + .padding(Dp8) ) { val ( logo, @@ -51,10 +53,14 @@ fun TrendingItem( Image( modifier = Modifier - .size(Dp60) - .padding(end = Dp16) + .size(Dp40) .constrainAs(logo) { - linkTo(top = parent.top, bottom = parent.bottom) + linkTo( + top = parent.top, + bottom = parent.bottom, + topMargin = Dp6, + bottomMargin = Dp6 + ) start.linkTo(parent.start) }, painter = rememberAsyncImagePainter(coinItem.image), @@ -66,7 +72,7 @@ fun TrendingItem( .constrainAs(coinSymbol) { top.linkTo(parent.top) bottom.linkTo(coinName.top) - start.linkTo(logo.end) + start.linkTo(anchor = logo.end, margin = Dp16) }, text = coinItem.symbol.uppercase(), color = MaterialTheme.colors.textColor, @@ -94,13 +100,11 @@ fun TrendingItem( top.linkTo(coinSymbol.top) bottom.linkTo(coinName.bottom) width = Dimension.preferredWrapContent - }, - iconPaddingEnd = Dp13 + } ) } } -@Suppress("FunctionNaming") @Composable @Preview(uiMode = UI_MODE_NIGHT_NO) fun TrendingItemPreview( @@ -111,7 +115,6 @@ fun TrendingItemPreview( } } -@Suppress("FunctionNaming") @Composable @Preview(uiMode = UI_MODE_NIGHT_YES) fun TrendingItemPreviewDark( diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/navigation/CryptoNavGraph.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/navigation/CryptoNavGraph.kt new file mode 100644 index 00000000..a75efa34 --- /dev/null +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/navigation/CryptoNavGraph.kt @@ -0,0 +1,35 @@ +package co.nimblehq.compose.crypto.ui.screens.navigation + +import androidx.activity.ComponentActivity +import androidx.compose.runtime.Composable +import androidx.navigation.NavHostController +import androidx.navigation.compose.* +import co.nimblehq.compose.crypto.extension.provideViewModels +import co.nimblehq.compose.crypto.ui.screens.detail.DetailScreen +import co.nimblehq.compose.crypto.ui.screens.home.HomeScreen +import co.nimblehq.compose.crypto.ui.screens.home.HomeViewModel + +@Composable +fun CryptoNavGraph( + componentActivity: ComponentActivity, + navController: NavHostController = rememberNavController(), + startDestination: String = CryptoScreen.HOME.name +) { + NavHost( + navController = navController, + startDestination = startDestination + ) { + composable(CryptoScreen.HOME.name) { + val homeViewModel: HomeViewModel by componentActivity.provideViewModels() + HomeScreen( + viewModel = homeViewModel, + onMyCoinsItemClick = { + navController.navigate(route = CryptoScreen.COIN_INFORMATION.name) + } + ) + } + composable(CryptoScreen.COIN_INFORMATION.name) { + DetailScreen() + } + } +} diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/navigation/CryptoScreen.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/navigation/CryptoScreen.kt new file mode 100644 index 00000000..526dd0cf --- /dev/null +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/navigation/CryptoScreen.kt @@ -0,0 +1,6 @@ +package co.nimblehq.compose.crypto.ui.screens.navigation + +enum class CryptoScreen { + HOME, + COIN_INFORMATION +} diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Color.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Color.kt index b11d0078..90206085 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Color.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Color.kt @@ -2,12 +2,13 @@ package co.nimblehq.compose.crypto.ui.theme import androidx.compose.ui.graphics.Color -@Suppress("MagicNumber") object Color { + val White = Color(0xFFFFFFFF) val Guyabano = Color(0xFFF8F8F8) val MetallicSeaweed = Color(0xFF028090) val TiffanyBlue = Color(0xFF00BFB2) val Water = Color(0xFFD6F5F3) + val WhiteIce = Color(0xFFDBFBEC) val GuppieGreen = Color(0xFF10DC78) val DarkJungleGreen = Color(0xFF141B29) val LightSilver = Color(0xFFD6D7D8) diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Dimension.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Dimension.kt index 7fa3d84e..64ce1ed8 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Dimension.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Dimension.kt @@ -2,11 +2,12 @@ package co.nimblehq.compose.crypto.ui.theme import androidx.compose.ui.unit.dp -@Suppress("MagicNumber") object Dimension { val Dp0 = 0.dp val Dp4 = 4.dp + val Dp6 = 6.dp val Dp8 = 8.dp + val Dp9 = 9.dp val Dp12 = 12.dp val Dp13 = 13.dp val Dp14 = 14.dp diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Style.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Style.kt index acb3799d..b44242b7 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Style.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Style.kt @@ -3,19 +3,19 @@ package co.nimblehq.compose.crypto.ui.theme import androidx.compose.material.Colors import androidx.compose.ui.graphics.Color import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color.Companion.White import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import co.nimblehq.compose.crypto.R import co.nimblehq.compose.crypto.ui.theme.Color.DarkJungleGreen -import co.nimblehq.compose.crypto.ui.theme.Color.FireOpal -import co.nimblehq.compose.crypto.ui.theme.Color.GuppieGreen import co.nimblehq.compose.crypto.ui.theme.Color.LightSilver +import co.nimblehq.compose.crypto.ui.theme.Color.Quartz import co.nimblehq.compose.crypto.ui.theme.Color.QuartzAlpha20 import co.nimblehq.compose.crypto.ui.theme.Color.SonicSilver import co.nimblehq.compose.crypto.ui.theme.Color.TiffanyBlue +import co.nimblehq.compose.crypto.ui.theme.Color.White +import co.nimblehq.compose.crypto.ui.theme.TextDimension.Sp12 import co.nimblehq.compose.crypto.ui.theme.TextDimension.Sp14 import co.nimblehq.compose.crypto.ui.theme.TextDimension.Sp16 import co.nimblehq.compose.crypto.ui.theme.TextDimension.Sp24 @@ -41,6 +41,17 @@ object Style { @Composable get() = if (isLight) SonicSilver else LightSilver + val Colors.coinInfoSellBuyBackground: Color + @Composable + get() = if (isLight) White else DarkJungleGreen + + val Colors.coinInfoAppBarIconColor: Color + @Composable + get() = if (isLight) Quartz else White + + @Composable + fun medium12() = textStyle.copy(fontWeight = FontWeight.Medium, fontSize = Sp12) + @Composable fun medium14() = textStyle.copy(fontWeight = FontWeight.Medium, fontSize = Sp14) @@ -53,12 +64,6 @@ object Style { @Composable fun lightSilverMedium16() = medium16().copy(color = LightSilver) - @Composable - fun guppieGreenMedium16() = medium16().copy(color = GuppieGreen) - - @Composable - fun fireOpalGreenMedium16() = medium16().copy(color = FireOpal) - @Composable fun semiBold16() = textStyle.copy(fontWeight = FontWeight.SemiBold, fontSize = Sp16) diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/TextDimension.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/TextDimension.kt index abec2d59..8383b1fe 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/TextDimension.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/TextDimension.kt @@ -2,8 +2,8 @@ package co.nimblehq.compose.crypto.ui.theme import androidx.compose.ui.unit.sp -@Suppress("MagicNumber") object TextDimension { + val Sp12 = 12.sp val Sp14 = 14.sp val Sp16 = 16.sp val Sp24 = 24.sp diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Theme.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Theme.kt index 14dc4c41..39f03a7f 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Theme.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/theme/Theme.kt @@ -1,3 +1,4 @@ +@file:Suppress("MatchingDeclarationName") package co.nimblehq.compose.crypto.ui.theme import androidx.compose.foundation.isSystemInDarkTheme @@ -6,7 +7,6 @@ import androidx.compose.runtime.Composable import co.nimblehq.compose.crypto.ui.theme.Color.DarkJungleGreen import co.nimblehq.compose.crypto.ui.theme.Color.Guyabano -@Suppress("MatchingDeclarationName") object Palette { val ComposeLightPalette = lightColors( surface = Guyabano, @@ -17,7 +17,6 @@ object Palette { ) } -@Suppress("FunctionNaming") @Composable fun ComposeTheme( darkTheme: Boolean = isSystemInDarkTheme(), diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml new file mode 100644 index 00000000..70d286e3 --- /dev/null +++ b/app/src/main/res/drawable/ic_back.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_heart.xml b/app/src/main/res/drawable/ic_heart.xml new file mode 100644 index 00000000..c4eb5ea2 --- /dev/null +++ b/app/src/main/res/drawable/ic_heart.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d61fae41..ed6f6d02 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,4 +13,7 @@ $%s %1$s%% + + Sell + Buy diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 0829a70e..f1578a60 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -5,8 +5,8 @@ object Versions { const val ANDROID_MIN_SDK_VERSION = 23 const val ANDROID_TARGET_SDK_VERSION = 32 - const val ANDROID_VERSION_CODE = 2 - const val ANDROID_VERSION_NAME = "0.2.0" + const val ANDROID_VERSION_CODE = 3 + const val ANDROID_VERSION_NAME = "0.3.0" // Dependencies (Alphabet sorted) const val ANDROID_COMMON_KTX_VERSION = "0.1.1" @@ -15,6 +15,7 @@ object Versions { const val ANDROIDX_CORE_KTX_VERSION = "1.8.0" const val ANDROIDX_FRAGMENT_VERSION = "1.3.3" const val ANDROIDX_LIFECYCLE_VERSION = "2.5.1" + const val ANDROIDX_NAVIGATION_COMPOSE_VERSION = "2.5.1" const val ANDROIDX_NAVIGATION_VERSION = "2.3.4" const val ANDROIDX_SUPPORT_VERSION = "1.3.0" diff --git a/data/src/main/java/co/nimblehq/compose/crypto/data/extension/NumberExt.kt b/data/src/main/java/co/nimblehq/compose/crypto/data/extension/NumberExt.kt new file mode 100644 index 00000000..b5278d3e --- /dev/null +++ b/data/src/main/java/co/nimblehq/compose/crypto/data/extension/NumberExt.kt @@ -0,0 +1,7 @@ +package co.nimblehq.compose.crypto.data.extension + +import java.math.BigDecimal + +fun BigDecimal?.orZero(): BigDecimal = this ?: BigDecimal.ZERO +fun Double?.orZero(): Double = this ?: 0.0 +fun Int?.orZero(): Int = this ?: 0 diff --git a/data/src/main/java/co/nimblehq/compose/crypto/data/model/response/CoinDetailResponse.kt b/data/src/main/java/co/nimblehq/compose/crypto/data/model/response/CoinDetailResponse.kt new file mode 100644 index 00000000..da03ffda --- /dev/null +++ b/data/src/main/java/co/nimblehq/compose/crypto/data/model/response/CoinDetailResponse.kt @@ -0,0 +1,169 @@ +package co.nimblehq.compose.crypto.data.model.response + +import co.nimblehq.compose.crypto.data.extension.orZero +import co.nimblehq.compose.crypto.domain.model.CoinDetail +import com.squareup.moshi.Json +import java.math.BigDecimal + +data class CoinDetailResponse( + @Json(name = "id") + val id: String?, + @Json(name = "symbol") + val symbol: String?, + @Json(name = "name") + val coinName: String?, + @Json(name = "image") + val image: ImageResponse?, + @Json(name = "market_data") + val marketData: MarketDataResponse? +) { + + data class ImageResponse( + @Json(name = "thumb") + val thumb: String?, + @Json(name = "small") + val small: String?, + @Json(name = "large") + val large: String?, + ) + + data class MarketDataResponse( + @Json(name = "current_price") + val currentPrice: Map?, + @Json(name = "ath") + val ath: Map?, + @Json(name = "ath_change_percentage") + val athChangePercentage: Map?, + @Json(name = "ath_date") + val athDate: Map?, + @Json(name = "atl") + val atl: Map?, + @Json(name = "atl_change_percentage") + val atlChangePercentage: Map?, + @Json(name = "atl_date") + val atlDate: Map?, + @Json(name = "market_cap") + val marketCap: Map?, + @Json(name = "market_cap_rank") + val marketCapRank: Int?, + @Json(name = "fully_diluted_valuation") + val fullyDilutedValuation: Map?, + @Json(name = "total_volume") + val totalVolume: Map?, + @Json(name = "high_24h") + val high24h: Map?, + @Json(name = "low_24h") + val low24h: Map?, + + @Json(name = "price_change_24h") + val priceChange24h: BigDecimal?, + @Json(name = "price_change_percentage_24h") + val priceChangePercentage24h: Double?, + @Json(name = "price_change_percentage_7d") + val priceChangePercentage7d: Double?, + @Json(name = "price_change_percentage_14d") + val priceChangePercentage14d: Double?, + @Json(name = "price_change_percentage_30d") + val priceChangePercentage30d: Double?, + @Json(name = "price_change_percentage_60d") + val priceChangePercentage60d: Double?, + @Json(name = "price_change_percentage_200d") + val priceChangePercentage200d: Double?, + @Json(name = "price_change_percentage_1y") + val priceChangePercentage1y: Double?, + @Json(name = "market_cap_change_24h") + val marketCapChange24h: BigDecimal?, + @Json(name = "market_cap_change_percentage_24h") + val marketCapChangePercentage24h: Double?, + + @Json(name = "price_change_24h_in_currency") + val priceChange24hInCurrency: Map?, + @Json(name = "price_change_percentage_24h_in_currency") + val priceChangePercentage24hInCurrency: Map?, + @Json(name = "price_change_percentage_7d_in_currency") + val priceChangePercentage7dInCurrency: Map?, + @Json(name = "price_change_percentage_14d_in_currency") + val priceChangePercentage14dInCurrency: Map?, + @Json(name = "price_change_percentage_30d_in_currency") + val priceChangePercentage30dInCurrency: Map?, + @Json(name = "price_change_percentage_60d_in_currency") + val priceChangePercentage60dInCurrency: Map?, + @Json(name = "price_change_percentage_200d_in_currency") + val priceChangePercentage200dInCurrency: Map?, + @Json(name = "price_change_percentage_1y_in_currency") + val priceChangePercentage1yInCurrency: Map?, + @Json(name = "market_cap_change_24h_in_currency") + val marketCapChange24hInCurrency: Map?, + @Json(name = "market_cap_change_percentage_24h_in_currency") + val marketCapChangePercentage24hInCurrency: Map?, + + @Json(name = "total_supply") + val totalSupply: BigDecimal?, + @Json(name = "max_supply") + val maxSupply: BigDecimal?, + @Json(name = "circulating_supply") + val circulatingSupply: BigDecimal?, + + @Json(name = "last_updated") + val lastUpdated: String?, + ) +} + +internal fun CoinDetailResponse.toModel() = CoinDetail( + id = id.orEmpty(), + symbol = symbol.orEmpty(), + coinName = coinName.orEmpty(), + + image = image?.toModel(), + marketData = marketData?.toModel(), +) + +private fun CoinDetailResponse.ImageResponse.toModel() = CoinDetail.Image( + thumb = thumb.orEmpty(), + small = small.orEmpty(), + large = large.orEmpty(), +) + +private fun CoinDetailResponse.MarketDataResponse.toModel() = CoinDetail.MarketData( + currentPrice = currentPrice.orEmpty(), + ath = ath.orEmpty(), + athChangePercentage = athChangePercentage.orEmpty(), + athDate = athDate.orEmpty(), + atl = atl.orEmpty(), + atlChangePercentage = atlChangePercentage.orEmpty(), + atlDate = atlDate.orEmpty(), + marketCap = marketCap.orEmpty(), + marketCapRank = marketCapRank.orZero(), + fullyDilutedValuation = fullyDilutedValuation.orEmpty(), + totalVolume = totalVolume.orEmpty(), + high24h = high24h.orEmpty(), + low24h = low24h.orEmpty(), + + priceChange24h = priceChange24h.orZero(), + priceChangePercentage24h = priceChangePercentage24h.orZero(), + priceChangePercentage7d = priceChangePercentage7d.orZero(), + priceChangePercentage14d = priceChangePercentage14d.orZero(), + priceChangePercentage30d = priceChangePercentage30d.orZero(), + priceChangePercentage60d = priceChangePercentage60d.orZero(), + priceChangePercentage200d = priceChangePercentage200d.orZero(), + priceChangePercentage1y = priceChangePercentage1y.orZero(), + marketCapChange24h = marketCapChange24h.orZero(), + marketCapChangePercentage24h = marketCapChangePercentage24h.orZero(), + + priceChange24hInCurrency = priceChange24hInCurrency.orEmpty(), + priceChangePercentage24hInCurrency = priceChangePercentage24hInCurrency.orEmpty(), + priceChangePercentage7dInCurrency = priceChangePercentage7dInCurrency.orEmpty(), + priceChangePercentage14dInCurrency = priceChangePercentage14dInCurrency.orEmpty(), + priceChangePercentage30dInCurrency = priceChangePercentage30dInCurrency.orEmpty(), + priceChangePercentage60dInCurrency = priceChangePercentage60dInCurrency.orEmpty(), + priceChangePercentage200dInCurrency = priceChangePercentage200dInCurrency.orEmpty(), + priceChangePercentage1yInCurrency = priceChangePercentage1yInCurrency.orEmpty(), + marketCapChange24hInCurrency = marketCapChange24hInCurrency.orEmpty(), + marketCapChangePercentage24hInCurrency = marketCapChangePercentage24hInCurrency.orEmpty(), + + totalSupply = totalSupply.orZero(), + maxSupply = maxSupply.orZero(), + circulatingSupply = circulatingSupply.orZero(), + + lastUpdated = lastUpdated.orEmpty(), +) diff --git a/data/src/main/java/co/nimblehq/compose/crypto/data/model/response/CoinItemResponse.kt b/data/src/main/java/co/nimblehq/compose/crypto/data/model/response/CoinItemResponse.kt index ffd88df2..ecffc930 100644 --- a/data/src/main/java/co/nimblehq/compose/crypto/data/model/response/CoinItemResponse.kt +++ b/data/src/main/java/co/nimblehq/compose/crypto/data/model/response/CoinItemResponse.kt @@ -1,5 +1,6 @@ package co.nimblehq.compose.crypto.data.model.response +import co.nimblehq.compose.crypto.data.extension.orZero import co.nimblehq.compose.crypto.domain.model.CoinItem import com.squareup.moshi.Json import java.math.BigDecimal @@ -71,41 +72,38 @@ data class CoinItemResponse( ) } -@Suppress("ComplexMethod") -private fun CoinItemResponse.toModel() = CoinItem( +internal fun CoinItemResponse.toModel() = CoinItem( id = id.orEmpty(), symbol = symbol.orEmpty(), coinName = coinName.orEmpty(), image = image.orEmpty(), - currentPrice = currentPrice ?: BigDecimal.ZERO, - marketCap = marketCap ?: BigDecimal.ZERO, - marketCapRank = marketCapRank ?: 0, - fullyDilutedValuation = fullyDilutedValuation ?: BigDecimal.ZERO, - totalVolume = totalVolume ?: BigDecimal.ZERO, - high24h = high24h ?: BigDecimal.ZERO, - low24h = low24h ?: BigDecimal.ZERO, - priceChange24h = priceChange24h ?: BigDecimal.ZERO, - priceChangePercentage24h = priceChangePercentage24h ?: 0.0, - marketCapChange24h = marketCapChange24h ?: BigDecimal.ZERO, - marketCapChangePercentage24h = marketCapChangePercentage24h ?: 0.0, - circulatingSupply = circulatingSupply ?: BigDecimal.ZERO, - totalSupply = totalSupply ?: BigDecimal.ZERO, - maxSupply = maxSupply ?: BigDecimal.ZERO, - ath = ath ?: BigDecimal.ZERO, - athChangePercentage = athChangePercentage ?: 0.0, + currentPrice = currentPrice.orZero(), + marketCap = marketCap.orZero(), + marketCapRank = marketCapRank.orZero(), + fullyDilutedValuation = fullyDilutedValuation.orZero(), + totalVolume = totalVolume.orZero(), + high24h = high24h.orZero(), + low24h = low24h.orZero(), + priceChange24h = priceChange24h.orZero(), + priceChangePercentage24h = priceChangePercentage24h.orZero(), + marketCapChange24h = marketCapChange24h.orZero(), + marketCapChangePercentage24h = marketCapChangePercentage24h.orZero(), + circulatingSupply = circulatingSupply.orZero(), + totalSupply = totalSupply.orZero(), + maxSupply = maxSupply.orZero(), + ath = ath.orZero(), + athChangePercentage = athChangePercentage.orZero(), athDate = athDate.orEmpty(), - atl = atl ?: BigDecimal.ZERO, - atlChangePercentage = atlChangePercentage ?: 0.0, + atl = atl.orZero(), + atlChangePercentage = atlChangePercentage.orZero(), atlDate = atlDate.orEmpty(), roi = roi?.toModel(), lastUpdated = lastUpdated.orEmpty(), - priceChangePercentage24hInCurrency = priceChangePercentage24hInCurrency ?: 0.0 + priceChangePercentage24hInCurrency = priceChangePercentage24hInCurrency.orZero() ) -fun List.toModels() = this.map { it.toModel() } - private fun CoinItemResponse.RoiItemResponse.toModel() = CoinItem.RoiItem( - times = times ?: BigDecimal.ZERO, + times = times.orZero(), currency = currency.orEmpty(), - percentage = percentage ?: 0.0 + percentage = percentage.orZero() ) diff --git a/data/src/main/java/co/nimblehq/compose/crypto/data/model/response/CoinPriceResponse.kt b/data/src/main/java/co/nimblehq/compose/crypto/data/model/response/CoinPriceResponse.kt index fbe868a2..1ff336d6 100644 --- a/data/src/main/java/co/nimblehq/compose/crypto/data/model/response/CoinPriceResponse.kt +++ b/data/src/main/java/co/nimblehq/compose/crypto/data/model/response/CoinPriceResponse.kt @@ -9,7 +9,7 @@ data class CoinPriceResponse( val prices: List> ) -fun CoinPriceResponse.toModel() = this.prices.map { response -> +internal fun CoinPriceResponse.toModel() = this.prices.map { response -> CoinPrice( timeStamp = (response[0].toLong()), price = response[1] diff --git a/data/src/main/java/co/nimblehq/compose/crypto/data/repository/CoinRepositoryImpl.kt b/data/src/main/java/co/nimblehq/compose/crypto/data/repository/CoinRepositoryImpl.kt index e5f7188e..15b9d3d6 100644 --- a/data/src/main/java/co/nimblehq/compose/crypto/data/repository/CoinRepositoryImpl.kt +++ b/data/src/main/java/co/nimblehq/compose/crypto/data/repository/CoinRepositoryImpl.kt @@ -1,13 +1,9 @@ package co.nimblehq.compose.crypto.data.repository import co.nimblehq.compose.crypto.data.flowTransform -import co.nimblehq.compose.crypto.data.model.response.CoinItemResponse -import co.nimblehq.compose.crypto.data.model.response.CoinPriceResponse -import co.nimblehq.compose.crypto.data.model.response.toModel -import co.nimblehq.compose.crypto.data.model.response.toModels +import co.nimblehq.compose.crypto.data.model.response.* import co.nimblehq.compose.crypto.data.service.ApiService -import co.nimblehq.compose.crypto.domain.model.CoinItem -import co.nimblehq.compose.crypto.domain.model.CoinPrice +import co.nimblehq.compose.crypto.domain.model.* import co.nimblehq.compose.crypto.domain.repository.CoinRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -32,7 +28,27 @@ class CoinRepositoryImpl( itemPerPage = itemPerPage, page = page ) - }.map { it.toModels() } + }.map { coinResponses -> coinResponses.map { it.toModel() } } + + override fun getCoinDetail( + coinId: String, + localization: Boolean, + tickers: Boolean, + marketData: Boolean, + communityData: Boolean, + developerData: Boolean, + sparkline: Boolean + ): Flow = flowTransform { + api.getCoin( + coinId = coinId, + localization = localization, + tickers = tickers, + marketData = marketData, + communityData = communityData, + developerData = developerData, + sparkline = sparkline, + ) + }.map { it.toModel() } override fun getCoinPrices( coinId: String, diff --git a/data/src/main/java/co/nimblehq/compose/crypto/data/service/ApiService.kt b/data/src/main/java/co/nimblehq/compose/crypto/data/service/ApiService.kt index f5494554..0d5d0378 100644 --- a/data/src/main/java/co/nimblehq/compose/crypto/data/service/ApiService.kt +++ b/data/src/main/java/co/nimblehq/compose/crypto/data/service/ApiService.kt @@ -1,11 +1,8 @@ package co.nimblehq.compose.crypto.data.service -import co.nimblehq.compose.crypto.data.model.response.CoinItemResponse -import co.nimblehq.compose.crypto.data.model.response.CoinPriceResponse +import co.nimblehq.compose.crypto.data.model.response.* import retrofit2.Response -import retrofit2.http.GET -import retrofit2.http.Path -import retrofit2.http.Query +import retrofit2.http.* @Suppress("LongParameterList") interface ApiService { @@ -19,6 +16,17 @@ interface ApiService { @Query("page") page: Int ): Response> + @GET("coins/{id}") + suspend fun getCoin( + @Path("id") coinId: String, + @Query("localization") localization: Boolean, + @Query("tickers") tickers: Boolean, + @Query("market_data") marketData: Boolean, + @Query("community_data") communityData: Boolean, + @Query("developer_data") developerData: Boolean, + @Query("sparkline") sparkline: Boolean + ): Response + @GET("coins/{id}/market_chart/range") suspend fun getCoinPrices( @Path("id") coinId: String, diff --git a/detekt-config.yml b/detekt-config.yml index 1b0b030d..53424d61 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -67,11 +67,11 @@ complexity: threshold: 150 LongMethod: active: true - threshold: 20 + threshold: 60 LongParameterList: active: true threshold: 5 - ignoreDefaultParameters: false + ignoreDefaultParameters: true MethodOverloading: active: false threshold: 5 @@ -188,7 +188,8 @@ naming: minimumFunctionNameLength: 3 FunctionNaming: active: true - functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$' + functionPattern: '[a-zA-Z][a-zA-Z0-9]*' + ignoreAnnotated: [ 'Composable' ] MatchingDeclarationName: active: true MemberNameEqualsClassName: @@ -202,7 +203,7 @@ naming: packagePattern: '^[a-z]+(\.[a-z][a-z0-9]*)*$' TopLevelPropertyNaming: active: true - constantPattern: '[A-Z][_A-Z0-9]*' + constantPattern: '[A-Z][_A-Za-z0-9]*' propertyPattern: '[a-z][A-Za-z\d]*' privatePropertyPattern: '(_)?[a-z][A-Za-z0-9]*' VariableMaxLength: @@ -286,7 +287,7 @@ style: active: true ignoreNumbers: '-1,0,1,2' ignoreHashCodeFunction: false - ignorePropertyDeclaration: false + ignorePropertyDeclaration: true ignoreConstantDeclaration: true ignoreCompanionObjectPropertyDeclaration: true ignoreAnnotation: false @@ -336,6 +337,8 @@ style: active: false UnusedImports: active: false + UnusedPrivateMember: + ignoreAnnotated: [ 'Preview' ] UseDataClass: active: false excludeAnnotatedClasses: "" diff --git a/domain/src/main/java/co/nimblehq/compose/crypto/domain/model/CoinDetail.kt b/domain/src/main/java/co/nimblehq/compose/crypto/domain/model/CoinDetail.kt new file mode 100644 index 00000000..e372d74d --- /dev/null +++ b/domain/src/main/java/co/nimblehq/compose/crypto/domain/model/CoinDetail.kt @@ -0,0 +1,62 @@ +package co.nimblehq.compose.crypto.domain.model + +import java.math.BigDecimal + +data class CoinDetail( + val id: String, + val symbol: String, + val coinName: String, + val image: Image?, + val marketData: MarketData? +) { + + data class Image( + val thumb: String, + val small: String, + val large: String, + ) + + data class MarketData( + val currentPrice: Map, + val ath: Map, + val athChangePercentage: Map, + val athDate: Map, + val atl: Map, + val atlChangePercentage: Map, + val atlDate: Map, + val marketCap: Map, + val marketCapRank: Int, + val fullyDilutedValuation: Map, + val totalVolume: Map, + val high24h: Map, + val low24h: Map, + + val priceChange24h: BigDecimal, + val priceChangePercentage24h: Double, + val priceChangePercentage7d: Double, + val priceChangePercentage14d: Double, + val priceChangePercentage30d: Double, + val priceChangePercentage60d: Double, + val priceChangePercentage200d: Double, + val priceChangePercentage1y: Double, + val marketCapChange24h: BigDecimal, + val marketCapChangePercentage24h: Double, + + val priceChange24hInCurrency: Map, + val priceChangePercentage24hInCurrency: Map, + val priceChangePercentage7dInCurrency: Map, + val priceChangePercentage14dInCurrency: Map, + val priceChangePercentage30dInCurrency: Map, + val priceChangePercentage60dInCurrency: Map, + val priceChangePercentage200dInCurrency: Map, + val priceChangePercentage1yInCurrency: Map, + val marketCapChange24hInCurrency: Map, + val marketCapChangePercentage24hInCurrency: Map, + + val totalSupply: BigDecimal, + val maxSupply: BigDecimal, + val circulatingSupply: BigDecimal, + + val lastUpdated: String, + ) +} diff --git a/domain/src/main/java/co/nimblehq/compose/crypto/domain/repository/CoinRepository.kt b/domain/src/main/java/co/nimblehq/compose/crypto/domain/repository/CoinRepository.kt index d747ec7d..4d556cb8 100644 --- a/domain/src/main/java/co/nimblehq/compose/crypto/domain/repository/CoinRepository.kt +++ b/domain/src/main/java/co/nimblehq/compose/crypto/domain/repository/CoinRepository.kt @@ -1,7 +1,6 @@ package co.nimblehq.compose.crypto.domain.repository -import co.nimblehq.compose.crypto.domain.model.CoinItem -import co.nimblehq.compose.crypto.domain.model.CoinPrice +import co.nimblehq.compose.crypto.domain.model.* import kotlinx.coroutines.flow.Flow @Suppress("LongParameterList") @@ -15,6 +14,16 @@ interface CoinRepository { page: Int ): Flow> + fun getCoinDetail( + coinId: String, + localization: Boolean = false, + tickers: Boolean = false, + marketData: Boolean = true, + communityData: Boolean = false, + developerData: Boolean = false, + sparkline: Boolean = false + ): Flow + fun getCoinPrices( coinId: String, currency: String, diff --git a/domain/src/main/java/co/nimblehq/compose/crypto/domain/usecase/GetCoinDetailUseCase.kt b/domain/src/main/java/co/nimblehq/compose/crypto/domain/usecase/GetCoinDetailUseCase.kt new file mode 100644 index 00000000..f92e87cd --- /dev/null +++ b/domain/src/main/java/co/nimblehq/compose/crypto/domain/usecase/GetCoinDetailUseCase.kt @@ -0,0 +1,13 @@ +package co.nimblehq.compose.crypto.domain.usecase + +import co.nimblehq.compose.crypto.domain.model.CoinDetail +import co.nimblehq.compose.crypto.domain.repository.CoinRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetCoinDetailUseCase @Inject constructor(private val repository: CoinRepository) { + + fun execute(coinId: String): Flow { + return repository.getCoinDetail(coinId = coinId) + } +}