Skip to content

Commit

Permalink
Merge pull request #58 from nimblehq/release/0.5.0
Browse files Browse the repository at this point in the history
Release - 0.5.0
  • Loading branch information
hoangnguyen92dn authored Oct 21, 2022
2 parents 245ea67 + 14524e6 commit a4308a0
Show file tree
Hide file tree
Showing 13 changed files with 471 additions and 15 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.KOTLIN_VERSION}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.KOTLINX_COROUTINES_VERSION}")

implementation("io.github.bytebeats:compose-charts:${Versions.COMPOSE_CHART_VERSION}")

kapt("com.google.dagger:hilt-compiler:${Versions.HILT_VERSION}")

debugImplementation("androidx.compose.ui:ui-tooling:${Versions.COMPOSE_VERSION}")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package co.nimblehq.compose.crypto.ui.components.chartintervals

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import co.nimblehq.compose.crypto.ui.theme.Color
import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp12
import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp14
import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp4
import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp45
import co.nimblehq.compose.crypto.ui.theme.Dimension.Dp8
import co.nimblehq.compose.crypto.ui.theme.Style

@Composable
fun ChartIntervalsButtonGroup(
modifier: Modifier,
onIntervalChanged: (TimeIntervals) -> Unit
) {

val selectedColor = remember { mutableStateOf(0) }
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(Dp14)
) {

TimeIntervals.values().forEachIndexed { index, interval ->
val backgroundColor = if (selectedColor.value == index) {
Color.CaribbeanGreen
} else {
Color.Transparent
}

ChartIntervalsButton(
modifier = Modifier
.requiredWidth(Dp45)
.background(
color = backgroundColor,
shape = RoundedCornerShape(Dp12)
),
interval = interval,
onClick = {
if (selectedColor.value != index) {
selectedColor.value = index
onIntervalChanged.invoke(interval)
}
}
)
}
}
}

@Composable
fun ChartIntervalsButton(
modifier: Modifier,
interval: TimeIntervals,
onClick: () -> Unit
) {
Text(
modifier = modifier
.clickable { onClick() }
.padding(vertical = Dp4, horizontal = Dp8),
textAlign = TextAlign.Center,
text = interval.text,
color = Color.White,
style = Style.medium14()
)
}

@Preview
@Composable
fun ChartIntervalsButtonGroupPreview() {
ChartIntervalsButtonGroup(
modifier = Modifier.fillMaxWidth(),
onIntervalChanged = {}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package co.nimblehq.compose.crypto.ui.components.chartintervals

enum class TimeIntervals(val text: String) {
ONE_DAY("1D"),
ONE_WEEK("7D"),
ONE_MONTH("1M"),
ONE_YEAR("1Y"),
FIVE_YEAR("5Y")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package co.nimblehq.compose.crypto.ui.components.linechart

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.unit.dp
import me.bytebeats.views.charts.line.*
import me.bytebeats.views.charts.line.render.line.EmptyLineShader
import me.bytebeats.views.charts.line.render.line.ILineDrawer
import me.bytebeats.views.charts.line.render.line.ILineShader
import me.bytebeats.views.charts.line.render.line.SolidLineDrawer
import me.bytebeats.views.charts.line.render.point.FilledCircularPointDrawer
import me.bytebeats.views.charts.line.render.point.IPointDrawer
import me.bytebeats.views.charts.simpleChartAnimation

private const val DEFAULT_AXIS_SIZE = 0f

@Suppress("MagicNumber", "LongMethod")
@Composable
fun CoinPriceChart(
lineChartData: LineChartData,
modifier: Modifier = Modifier,
animation: AnimationSpec<Float> = simpleChartAnimation(),
pointDrawer: IPointDrawer = FilledCircularPointDrawer(),
lineDrawer: ILineDrawer = SolidLineDrawer(),
lineShader: ILineShader = EmptyLineShader,
labelDrawer: ILabelDrawer = CoinPriceLabelDrawer(),
horizontalOffset: Float = 5F,
) {
check(horizontalOffset in 0F..25F) {
"Horizontal Offset is the percentage offset from side, and must be between 0 and 25, included."
}
val transitionAnimation = remember(lineChartData.points) { Animatable(initialValue = 0F) }

LaunchedEffect(lineChartData.points) {
transitionAnimation.snapTo(0F)
transitionAnimation.animateTo(1F, animationSpec = animation)
}

Canvas(modifier = modifier
.fillMaxWidth()
.height(184.dp)) {
drawIntoCanvas { canvas ->
val yAxisDrawableArea = computeYAxisDrawableArea(
xAxisLabelSize = DEFAULT_AXIS_SIZE,
size = size
)
val xAxisDrawableArea = computeXAxisDrawableArea(
yAxisWidth = yAxisDrawableArea.width,
labelHeight = DEFAULT_AXIS_SIZE,
size = size
)

val chartDrawableArea = computeDrawableArea(
xAxisDrawableArea = xAxisDrawableArea,
yAxisDrawableArea = yAxisDrawableArea,
size = size,
offset = horizontalOffset
).copy(left = 0F) // Chart should fill the screen width

lineDrawer.drawLine(
drawScope = this,
canvas = canvas,
linePath = computeLinePath(
drawableArea = chartDrawableArea,
lineChartData = lineChartData,
transitionProgress = transitionAnimation.value
)
)
lineShader.fillLine(
drawScope = this,
canvas = canvas,
fillPath = computeFillPath(
drawableArea = chartDrawableArea,
lineChartData = lineChartData,
transitionProgress = transitionAnimation.value
)
)

val maxPrice = lineChartData.points.maxOf { it.value }
val minPrice = lineChartData.points.minOf { it.value }
val maxPriceIndex = lineChartData.points.indexOfFirst { it.value == maxPrice }
val minPriceIndex = lineChartData.points.indexOfFirst { it.value == minPrice }

lineChartData.points.forEachIndexed { index, point ->
withProgress(
index = index,
lineChartData = lineChartData,
transitionProgress = transitionAnimation.value
) {
val pointLocation = computePointLocation(
drawableArea = chartDrawableArea,
lineChartData = lineChartData,
point = point,
index = index
)
pointDrawer.drawPoint(
drawScope = this,
canvas = canvas,
center = pointLocation
)
if (index in listOf(minPriceIndex, maxPriceIndex)) {
labelDrawer.drawLabel(
drawScope = this,
canvas = canvas,
label = point.label,
pointLocation = pointLocation,
xAxisArea = xAxisDrawableArea,
isHighestPrice = index == maxPriceIndex
)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package co.nimblehq.compose.crypto.ui.components.linechart

import android.graphics.Paint
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
import me.bytebeats.views.charts.AxisLabelFormatter
import me.bytebeats.views.charts.toLegacyInt

private const val Y_OFFSET = 20f

@Suppress("MagicNumber")
data class CoinPriceLabelDrawer(
val labelTextSize: TextUnit = 12.sp,
val labelTextColors: Pair<Color, Color> = Color.White to Color.Black,
val axisLabelFormatter: AxisLabelFormatter = { value -> "$value" }
) : ILabelDrawer {
private val mLabelTextArea: Float? = null

private val mPaintLowest by lazy {
Paint().apply {
textAlign = Paint.Align.CENTER
color = labelTextColors.first.toLegacyInt()
}
}

private val mPaintHighest by lazy {
Paint().apply {
textAlign = Paint.Align.CENTER
color = labelTextColors.second.toLegacyInt()
}
}

override fun requiredAboveBarHeight(drawScope: DrawScope): Float =
3F / 2F * labelTextHeight(drawScope)

override fun requiredXAxisHeight(drawScope: DrawScope): Float = 0F

override fun drawLabel(
drawScope: DrawScope,
canvas: Canvas,
label: Any?,
pointLocation: Offset,
xAxisArea: Rect,
isHighestPrice: Boolean
) {
val textPaint = if (isHighestPrice) {
mPaintHighest
} else {
mPaintLowest
}
val labelValue = axisLabelFormatter(label)
val bounds = android.graphics.Rect()
textPaint.getTextBounds(labelValue, 0, labelValue.length, bounds)
val xCenter = when {
pointLocation.x <= 0f -> { // First point on the chart
bounds.width().toFloat() / 2
}
pointLocation.x >= canvas.nativeCanvas.width -> { // Last point on the chart
canvas.nativeCanvas.width - bounds.width().toFloat() / 2
}
else -> {
pointLocation.x
}
}
val yCenter = if (isHighestPrice) {
pointLocation.y - labelTextHeight(drawScope) / 2
} else {
pointLocation.y + labelTextHeight(drawScope) / 2 + Y_OFFSET
}
canvas.nativeCanvas.drawText(labelValue, xCenter, yCenter, paint(drawScope, textPaint))
}

private fun labelTextHeight(drawScope: DrawScope): Float = with(drawScope) {
mLabelTextArea ?: (1.5F * labelTextSize.toPx())
}

private fun paint(drawScope: DrawScope, textPaint: Paint): Paint =
with(drawScope) {
textPaint.apply {
textSize = labelTextSize.toPx()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package co.nimblehq.compose.crypto.ui.components.linechart

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.drawscope.DrawScope

@Suppress("LongParameterList")
interface ILabelDrawer {
fun requiredXAxisHeight(drawScope: DrawScope): Float = 0F
fun requiredAboveBarHeight(drawScope: DrawScope): Float = 0F
fun drawLabel(
drawScope: DrawScope,
canvas: Canvas,
label: Any?,
pointLocation: Offset,
xAxisArea: Rect,
isHighestPrice: Boolean
)
}
Loading

0 comments on commit a4308a0

Please sign in to comment.