From 6e1c6aca7e29bbdaddba9adaec360e3082e320ac Mon Sep 17 00:00:00 2001 From: Artem Semenov Date: Mon, 31 Jul 2023 14:24:39 +0300 Subject: [PATCH 1/7] Implement the container component for the chart. --- .../client/market/ShareProfileTab.kt | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/client/src/main/kotlin/io/spine/examples/shareaware/client/market/ShareProfileTab.kt b/client/src/main/kotlin/io/spine/examples/shareaware/client/market/ShareProfileTab.kt index 62721522..b67436a9 100644 --- a/client/src/main/kotlin/io/spine/examples/shareaware/client/market/ShareProfileTab.kt +++ b/client/src/main/kotlin/io/spine/examples/shareaware/client/market/ShareProfileTab.kt @@ -26,11 +26,13 @@ package io.spine.examples.shareaware.client.market +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width @@ -43,10 +45,10 @@ 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.unit.sp import io.spine.examples.shareaware.client.component.ContainerWithPopup import io.spine.examples.shareaware.client.component.PopupConfig import io.spine.examples.shareaware.client.component.PrimaryButton -import io.spine.examples.shareaware.client.share.ShareLogo import io.spine.examples.shareaware.client.share.SharePrice import io.spine.examples.shareaware.share.Share import io.spine.money.Money @@ -121,17 +123,47 @@ private fun ShareProfile( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - ShareLogo(share) + ShareInfo(share, previousPrice) + ButtonSection( + share = share, + purchaseModel = purchaseModel + ) + } +} + +/** + * Displays information about particular share. + */ +@Composable +private fun ShareInfo(share: Share, previousPrice: Money?) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Bottom + ) { Text( text = share.companyName, + fontSize = 30.sp, style = MaterialTheme.typography.labelMedium, modifier = Modifier.padding(top = 10.dp) ) SharePrice(share.price, previousPrice) - ButtonSection( - share = share, - purchaseModel = purchaseModel - ) + } +} + +/** + * Displays container purposed for rendering chart inside it. + */ +@Composable +private fun ChartContainer(chart: @Composable () -> Unit) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(250.dp) + .background(MaterialTheme.colorScheme.secondary) + .padding(10.dp) + ) { + chart() } } From 1894fc09618ad53906b4ecb4489e08d578ad116a Mon Sep 17 00:00:00 2001 From: Artem Semenov Date: Mon, 31 Jul 2023 14:30:36 +0300 Subject: [PATCH 2/7] Implement the extensions for the `DrawScope` type. --- .../shareaware/client/DrawScopeExts.kt | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 client/src/main/kotlin/io/spine/examples/shareaware/client/DrawScopeExts.kt diff --git a/client/src/main/kotlin/io/spine/examples/shareaware/client/DrawScopeExts.kt b/client/src/main/kotlin/io/spine/examples/shareaware/client/DrawScopeExts.kt new file mode 100644 index 00000000..5453f405 --- /dev/null +++ b/client/src/main/kotlin/io/spine/examples/shareaware/client/DrawScopeExts.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.examples.shareaware.client + +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.DrawScope +import org.jetbrains.skia.Point + +/** + * Draws an Y axis inside the drawing area. + * + * @param color the color of the axis line + * @param axisWidth the width of the axis line + */ +public fun DrawScope.drawYAxis( + color: Color, + axisWidth: Float = 2f +) { + drawLine( + color = color, + start = Offset(0f, 0f), + end = Offset(0f, size.height), + strokeWidth = axisWidth + ) +} + +/** + * Draws an X axis inside the drawing area. + * + * @param color the color of the axis line + * @param axisWidth the width of the axis line + */ +public fun DrawScope.drawXAxis( + color: Color, + axisWidth: Float = 2f +) { + drawLine( + color = color, + start = Offset(0f, size.height - 0f), + end = Offset(size.width, size.height - 0f), + strokeWidth = axisWidth + ) +} + +/** + * Maps a list of points from their original range to pixel coordinates within the drawing area. + * + * @param points the list of points to map + */ +public fun DrawScope.mapToPixelPoints(points: List): List { + val minXValue = points.minOf { it.x } + val maxXValue = points.maxOf { it.x } + val minYValue = points.minOf { it.y } + val maxYValue = points.maxOf { it.y } + return points.map { + val x = it.x.mapToDifferentRange( + inMin = minXValue, + inMax = maxXValue, + outMin = 0f, + outMax = size.width + ) + val y = it.y.mapToDifferentRange( + inMin = minYValue, + inMax = maxYValue, + outMin = size.height, + outMax = 0f + ) + Point(x, y) + } +} + +/** + * Remaps a value from one range to another. + * + * @param inMin The minimum value of the original range. + * @param inMax The maximum value of the original range. + * @param outMin The minimum value of the target range. + * @param outMax The maximum value of the target range. + */ +private fun Float.mapToDifferentRange( + inMin: Float, + inMax: Float, + outMin: Float, + outMax: Float +): Float = (this - inMin) * (outMax - outMin) / (inMax - inMin) + outMin From bb22404795e5ff34ecd8e5ee0c5700315a381423 Mon Sep 17 00:00:00 2001 From: Artem Semenov Date: Mon, 31 Jul 2023 14:45:41 +0300 Subject: [PATCH 3/7] Implement the `Chart` component. --- .../shareaware/client/market/Chart.kt | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 client/src/main/kotlin/io/spine/examples/shareaware/client/market/Chart.kt diff --git a/client/src/main/kotlin/io/spine/examples/shareaware/client/market/Chart.kt b/client/src/main/kotlin/io/spine/examples/shareaware/client/market/Chart.kt new file mode 100644 index 00000000..27798a71 --- /dev/null +++ b/client/src/main/kotlin/io/spine/examples/shareaware/client/market/Chart.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.examples.shareaware.client.market + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.drawscope.Stroke +import io.spine.examples.shareaware.client.drawXAxis +import io.spine.examples.shareaware.client.drawYAxis +import io.spine.examples.shareaware.client.mapToPixelPoints +import org.jetbrains.skia.Point + +/** + * Draws a chart. + * + * @param points chart points + */ +@Composable +public fun Chart(points: List) { + val axisColor = MaterialTheme.colors.onSecondary + val chartLineColor = MaterialTheme.colors.primary + Canvas( + modifier = Modifier.fillMaxSize() + ) { + drawYAxis(axisColor) + drawXAxis(axisColor) + val pixelPoints = mapToPixelPoints(points) + val path = Path() + pixelPoints.forEachIndexed { index, point -> + if (index == 0) { + path.moveTo(point.x, point.y) + } else { + path.lineTo(point.x, point.y) + } + } + drawPath( + path = path, + color = chartLineColor, + style = Stroke(width = 2f) + ) + } +} From e633d22be0d93bef65991bce1bc604adf8b53755 Mon Sep 17 00:00:00 2001 From: Artem Semenov Date: Mon, 31 Jul 2023 14:59:50 +0300 Subject: [PATCH 4/7] Add documentation for the parameters of the `ShareInfo` and `ChartContainer` components. --- .../examples/shareaware/client/market/ShareProfileTab.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/src/main/kotlin/io/spine/examples/shareaware/client/market/ShareProfileTab.kt b/client/src/main/kotlin/io/spine/examples/shareaware/client/market/ShareProfileTab.kt index b67436a9..b3860ef1 100644 --- a/client/src/main/kotlin/io/spine/examples/shareaware/client/market/ShareProfileTab.kt +++ b/client/src/main/kotlin/io/spine/examples/shareaware/client/market/ShareProfileTab.kt @@ -132,7 +132,10 @@ private fun ShareProfile( } /** - * Displays information about particular share. + * Displays information about a particular share. + * + * @param share the share to display info about + * @param previousPrice the previous price of the share */ @Composable private fun ShareInfo(share: Share, previousPrice: Money?) { @@ -152,7 +155,9 @@ private fun ShareInfo(share: Share, previousPrice: Money?) { } /** - * Displays container purposed for rendering chart inside it. + * Displays container purposed for drawing chart inside it. + * + * @param chart the chart to draw inside the container */ @Composable private fun ChartContainer(chart: @Composable () -> Unit) { From 942b07f19310d3aa883ac376066d007fdb41aabb Mon Sep 17 00:00:00 2001 From: Artem Semenov Date: Mon, 31 Jul 2023 15:11:08 +0300 Subject: [PATCH 5/7] Move `Chart` component to the `component` package. --- .../examples/shareaware/client/{market => component}/Chart.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename client/src/main/kotlin/io/spine/examples/shareaware/client/{market => component}/Chart.kt (97%) diff --git a/client/src/main/kotlin/io/spine/examples/shareaware/client/market/Chart.kt b/client/src/main/kotlin/io/spine/examples/shareaware/client/component/Chart.kt similarity index 97% rename from client/src/main/kotlin/io/spine/examples/shareaware/client/market/Chart.kt rename to client/src/main/kotlin/io/spine/examples/shareaware/client/component/Chart.kt index 27798a71..6f638159 100644 --- a/client/src/main/kotlin/io/spine/examples/shareaware/client/market/Chart.kt +++ b/client/src/main/kotlin/io/spine/examples/shareaware/client/component/Chart.kt @@ -24,7 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package io.spine.examples.shareaware.client.market +package io.spine.examples.shareaware.client.component import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.fillMaxSize From 302cf6e5bef0102af384234715d0363e7a21701e Mon Sep 17 00:00:00 2001 From: Artem Semenov Date: Mon, 31 Jul 2023 15:21:45 +0300 Subject: [PATCH 6/7] Configure the `headlineLarge` text style for the `MaterialTheme`. --- .../kotlin/io/spine/examples/shareaware/client/Theme.kt | 6 ++++++ .../examples/shareaware/client/market/ShareProfileTab.kt | 4 +--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/client/src/main/kotlin/io/spine/examples/shareaware/client/Theme.kt b/client/src/main/kotlin/io/spine/examples/shareaware/client/Theme.kt index f0e971ea..12f2fd2d 100644 --- a/client/src/main/kotlin/io/spine/examples/shareaware/client/Theme.kt +++ b/client/src/main/kotlin/io/spine/examples/shareaware/client/Theme.kt @@ -118,6 +118,12 @@ private val typography: Typography = Typography( fontWeight = FontWeight.Normal, fontFamily = sanFrancisco ), + headlineLarge = TextStyle( + fontSize = 30.sp, + fontWeight = FontWeight.SemiBold, + letterSpacing = 0.5.sp, + fontFamily = sanFrancisco + ), bodyMedium = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.SemiBold, diff --git a/client/src/main/kotlin/io/spine/examples/shareaware/client/market/ShareProfileTab.kt b/client/src/main/kotlin/io/spine/examples/shareaware/client/market/ShareProfileTab.kt index b3860ef1..21c347c5 100644 --- a/client/src/main/kotlin/io/spine/examples/shareaware/client/market/ShareProfileTab.kt +++ b/client/src/main/kotlin/io/spine/examples/shareaware/client/market/ShareProfileTab.kt @@ -45,7 +45,6 @@ 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.unit.sp import io.spine.examples.shareaware.client.component.ContainerWithPopup import io.spine.examples.shareaware.client.component.PopupConfig import io.spine.examples.shareaware.client.component.PrimaryButton @@ -146,8 +145,7 @@ private fun ShareInfo(share: Share, previousPrice: Money?) { ) { Text( text = share.companyName, - fontSize = 30.sp, - style = MaterialTheme.typography.labelMedium, + style = MaterialTheme.typography.headlineLarge, modifier = Modifier.padding(top = 10.dp) ) SharePrice(share.price, previousPrice) From 4a6955a09c94478619027bd2b945c212777ab3f6 Mon Sep 17 00:00:00 2001 From: Artem Semenov Date: Mon, 31 Jul 2023 15:24:26 +0300 Subject: [PATCH 7/7] Remove dots from the parameter section of documentation for the `mapToDifferentRange` extension. --- .../io/spine/examples/shareaware/client/DrawScopeExts.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/main/kotlin/io/spine/examples/shareaware/client/DrawScopeExts.kt b/client/src/main/kotlin/io/spine/examples/shareaware/client/DrawScopeExts.kt index 5453f405..fda2c1d2 100644 --- a/client/src/main/kotlin/io/spine/examples/shareaware/client/DrawScopeExts.kt +++ b/client/src/main/kotlin/io/spine/examples/shareaware/client/DrawScopeExts.kt @@ -97,10 +97,10 @@ public fun DrawScope.mapToPixelPoints(points: List): List { /** * Remaps a value from one range to another. * - * @param inMin The minimum value of the original range. - * @param inMax The maximum value of the original range. - * @param outMin The minimum value of the target range. - * @param outMax The maximum value of the target range. + * @param inMin The minimum value of the original range + * @param inMax The maximum value of the original range + * @param outMin The minimum value of the target range + * @param outMax The maximum value of the target range */ private fun Float.mapToDifferentRange( inMin: Float,