Skip to content

Commit a150c57

Browse files
authored
Merge pull request #914 from synonymdev/feat/price-widget-v61
feat: redesign price widget v61
2 parents 245021b + cfc0e3f commit a150c57

31 files changed

Lines changed: 607 additions & 713 deletions

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ suspend fun getData(): Result<Data> = withContext(Dispatchers.IO) {
194194
- ALWAYS use `remember` for expensive Compose computations
195195
- ALWAYS declare `modifier: Modifier = Modifier,` as the FIRST optional parameter in composable declarations
196196
- ALWAYS pass `modifier = ...` as the LAST argument in composable calls
197-
- ALWAYS add trailing commas in multi-line declarations; NEVER add a trailing comma to `modifier = ...` at call sites
197+
- ALWAYS add trailing commas in multi-line declarations, EXCEPT after a `modifier = ...` last argument — never add a trailing comma there, whether the modifier is a single call (`modifier = Modifier.weight(1f)`) or a chain (`modifier = Modifier.fillMaxWidth().testTag("foo")`)
198198
- ALWAYS use `navController.navigateTo(route)` for simple navigation; NEVER use raw `navController.navigate(route)``navigateTo` prevents duplicate destinations
199199
- ALWAYS prefer `VerticalSpacer`, `HorizontalSpacer`, `FillHeight` and `FillWidth` over `Spacer` when applicable
200200
- PREFER declaring small dependant classes, constants, interfaces or top-level functions in the same file with the core class where these are used

app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigScreen.kt

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package to.bitkit.appwidget.config
22

3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.clickable
35
import androidx.compose.foundation.layout.Arrangement
46
import androidx.compose.foundation.layout.Column
57
import androidx.compose.foundation.layout.Row
@@ -10,7 +12,6 @@ import androidx.compose.foundation.rememberScrollState
1012
import androidx.compose.foundation.verticalScroll
1113
import androidx.compose.material3.HorizontalDivider
1214
import androidx.compose.material3.Icon
13-
import androidx.compose.material3.IconButton
1415
import androidx.compose.runtime.Composable
1516
import androidx.compose.runtime.getValue
1617
import androidx.compose.ui.Alignment
@@ -23,9 +24,10 @@ import to.bitkit.R
2324
import to.bitkit.appwidget.model.AppWidgetType
2425
import to.bitkit.data.dto.price.GraphPeriod
2526
import to.bitkit.data.dto.price.TradingPair
27+
import to.bitkit.ext.label
2628
import to.bitkit.models.widget.PricePreferences
27-
import to.bitkit.ui.components.BodyM
2829
import to.bitkit.ui.components.BodySSB
30+
import to.bitkit.ui.components.Caption13Up
2931
import to.bitkit.ui.components.PrimaryButton
3032
import to.bitkit.ui.components.SecondaryButton
3133
import to.bitkit.ui.components.VerticalSpacer
@@ -44,7 +46,7 @@ fun AppWidgetConfigScreen(
4446
when (state.type) {
4547
AppWidgetType.PRICE -> Content(
4648
state = state,
47-
onTogglePair = { viewModel.togglePricePair(it) },
49+
onSelectPair = { viewModel.selectPricePair(it) },
4850
onSelectPeriod = { viewModel.selectPricePeriod(it) },
4951
onReset = { viewModel.resetPreferences() },
5052
onSave = { viewModel.saveAndFinish(onConfirm) },
@@ -56,16 +58,21 @@ fun AppWidgetConfigScreen(
5658
@Composable
5759
private fun Content(
5860
state: AppWidgetConfigUiState,
59-
onTogglePair: (TradingPair) -> Unit,
61+
onSelectPair: (TradingPair) -> Unit,
6062
onSelectPeriod: (GraphPeriod) -> Unit,
6163
onReset: () -> Unit,
6264
onSave: () -> Unit,
6365
onCancel: () -> Unit,
6466
) {
6567
val prefs = state.pricePreferences
66-
ScreenColumn {
68+
val selectedPair = prefs.enabledPairs.firstOrNull() ?: TradingPair.BTC_USD
69+
70+
ScreenColumn(
71+
noBackground = true,
72+
modifier = Modifier.background(Colors.Gray7)
73+
) {
6774
AppTopBar(
68-
titleText = stringResource(R.string.widgets__widget__edit),
75+
titleText = stringResource(R.string.widgets__price__name),
6976
onBackClick = onCancel,
7077
)
7178

@@ -75,43 +82,33 @@ private fun Content(
7582
.weight(1f)
7683
.verticalScroll(rememberScrollState())
7784
) {
78-
VerticalSpacer(26.dp)
79-
80-
BodyM(
81-
text = stringResource(R.string.widgets__widget__edit_description).replace(
82-
"{name}",
83-
stringResource(R.string.widgets__price__name),
84-
),
85-
color = Colors.White64,
86-
)
87-
88-
VerticalSpacer(32.dp)
85+
VerticalSpacer(16.dp)
8986

90-
BodySSB(
91-
text = stringResource(R.string.appwidget__price__trading_pairs),
87+
Caption13Up(
88+
text = stringResource(R.string.appwidget__price__currency),
9289
color = Colors.White64,
90+
modifier = Modifier.padding(bottom = 16.dp)
9391
)
94-
VerticalSpacer(8.dp)
9592

9693
for (pair in TradingPair.entries) {
97-
ConfigToggleRow(
94+
SelectableRow(
9895
label = pair.displayName,
99-
isEnabled = pair in prefs.enabledPairs,
100-
onClick = { onTogglePair(pair) },
96+
isSelected = pair == selectedPair,
97+
onClick = { onSelectPair(pair) },
10198
)
10299
}
103100

104101
VerticalSpacer(16.dp)
105-
BodySSB(
106-
text = stringResource(R.string.appwidget__price__period),
102+
Caption13Up(
103+
text = stringResource(R.string.appwidget__price__timeframe),
107104
color = Colors.White64,
105+
modifier = Modifier.padding(vertical = 16.dp)
108106
)
109-
VerticalSpacer(8.dp)
110107

111108
for (period in GraphPeriod.entries) {
112-
ConfigToggleRow(
113-
label = period.value,
114-
isEnabled = period == prefs.period,
109+
SelectableRow(
110+
label = period.label(),
111+
isSelected = period == prefs.period,
115112
onClick = { onSelectPeriod(period) },
116113
)
117114
}
@@ -120,7 +117,7 @@ private fun Content(
120117
Row(
121118
horizontalArrangement = Arrangement.spacedBy(16.dp),
122119
modifier = Modifier
123-
.padding(vertical = 21.dp, horizontal = 16.dp)
120+
.padding(16.dp)
124121
.fillMaxWidth()
125122
) {
126123
SecondaryButton(
@@ -143,29 +140,30 @@ private fun Content(
143140
}
144141

145142
@Composable
146-
private fun ConfigToggleRow(
143+
private fun SelectableRow(
147144
label: String,
148-
isEnabled: Boolean,
145+
isSelected: Boolean,
149146
onClick: () -> Unit,
150147
) {
151148
Column {
152149
Row(
153150
horizontalArrangement = Arrangement.spacedBy(16.dp),
154151
verticalAlignment = Alignment.CenterVertically,
155152
modifier = Modifier
156-
.padding(vertical = 12.dp)
157153
.fillMaxWidth()
154+
.clickable(onClick = onClick)
155+
.padding(vertical = 14.dp)
158156
) {
159157
BodySSB(
160158
text = label,
161-
color = Colors.White64,
159+
color = if (isSelected) Colors.White else Colors.White64,
162160
modifier = Modifier.weight(1f)
163161
)
164-
IconButton(onClick = onClick) {
162+
if (isSelected) {
165163
Icon(
166164
painter = painterResource(R.drawable.ic_checkmark),
167165
contentDescription = null,
168-
tint = if (isEnabled) Colors.Brand else Colors.White50,
166+
tint = Colors.Brand,
169167
modifier = Modifier.size(32.dp)
170168
)
171169
}

app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigViewModel.kt

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable
44
import androidx.lifecycle.ViewModel
55
import androidx.lifecycle.viewModelScope
66
import dagger.hilt.android.lifecycle.HiltViewModel
7+
import kotlinx.collections.immutable.persistentListOf
78
import kotlinx.coroutines.flow.MutableStateFlow
89
import kotlinx.coroutines.flow.StateFlow
910
import kotlinx.coroutines.flow.asStateFlow
@@ -46,15 +47,9 @@ class AppWidgetConfigViewModel @Inject constructor(
4647
}
4748
}
4849

49-
fun togglePricePair(pair: TradingPair) {
50+
fun selectPricePair(pair: TradingPair) {
5051
_uiState.update {
51-
val current = it.pricePreferences.enabledPairs.toMutableList()
52-
if (pair in current) {
53-
if (current.size > 1) current.remove(pair)
54-
} else {
55-
current.add(pair)
56-
}
57-
it.copy(pricePreferences = it.pricePreferences.copy(enabledPairs = current.sortedBy { p -> p.position }))
52+
it.copy(pricePreferences = it.pricePreferences.copy(enabledPairs = persistentListOf(pair)))
5853
}
5954
}
6055

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package to.bitkit.appwidget.ui.components
2+
3+
import androidx.compose.ui.unit.DpSize
4+
import androidx.compose.ui.unit.dp
5+
6+
object GlanceLayoutDimens {
7+
val WIDE_LAYOUT_MIN_WIDTH = 280.dp
8+
9+
val COMPACT_WIDGET_SIZE = DpSize(163.dp, 192.dp)
10+
val WIDE_WIDGET_SIZE = DpSize(343.dp, 152.dp)
11+
}

app/src/main/java/to/bitkit/appwidget/ui/components/GlanceWidgetScaffold.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import android.content.Intent
44
import androidx.compose.runtime.Composable
55
import androidx.compose.ui.unit.dp
66
import androidx.glance.GlanceModifier
7+
import androidx.glance.ImageProvider
78
import androidx.glance.action.clickable
89
import androidx.glance.appwidget.action.actionStartActivity
9-
import androidx.glance.appwidget.cornerRadius
1010
import androidx.glance.background
1111
import androidx.glance.layout.Column
1212
import androidx.glance.layout.fillMaxSize
1313
import androidx.glance.layout.padding
14-
import to.bitkit.appwidget.ui.theme.GlanceColors
14+
import to.bitkit.R
1515

1616
@Composable
1717
fun GlanceWidgetScaffold(
@@ -20,8 +20,7 @@ fun GlanceWidgetScaffold(
2020
) {
2121
val modifier = GlanceModifier
2222
.fillMaxSize()
23-
.cornerRadius(16.dp)
24-
.background(GlanceColors.cardBackgroundProvider)
23+
.background(ImageProvider(R.drawable.appwidget_background))
2524
.padding(16.dp)
2625
.let { mod ->
2726
if (onClick != null) mod.clickable(actionStartActivity(onClick)) else mod

app/src/main/java/to/bitkit/appwidget/ui/price/LineChartBitmap.kt

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ package to.bitkit.appwidget.ui.price
22

33
import android.graphics.Bitmap
44
import android.graphics.Canvas
5-
import android.graphics.LinearGradient
65
import android.graphics.Paint
76
import android.graphics.Path
8-
import android.graphics.Shader
97
import androidx.annotation.ColorInt
108
import androidx.core.graphics.createBitmap
119

@@ -36,7 +34,7 @@ fun renderLineChartBitmap(
3634
x to y
3735
}
3836

39-
val linePath = buildSmoothPath(points)
37+
val linePath = buildSmoothPath(points, yMin = padding, yMax = padding + drawHeight)
4038

4139
val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
4240
color = lineColor
@@ -47,28 +45,14 @@ fun renderLineChartBitmap(
4745
}
4846
canvas.drawPath(linePath, linePaint)
4947

50-
val fillPath = Path(linePath).apply {
51-
lineTo(points.last().first, height.toFloat())
52-
lineTo(points.first().first, height.toFloat())
53-
close()
54-
}
55-
56-
val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
57-
shader = LinearGradient(
58-
0f, padding,
59-
0f, height.toFloat(),
60-
(lineColor and 0x00FFFFFF) or 0xCC000000.toInt(),
61-
(lineColor and 0x00FFFFFF) or 0x4D000000,
62-
Shader.TileMode.CLAMP,
63-
)
64-
style = Paint.Style.FILL
65-
}
66-
canvas.drawPath(fillPath, fillPaint)
67-
6848
return bitmap
6949
}
7050

71-
private fun buildSmoothPath(points: List<Pair<Float, Float>>): Path = Path().apply {
51+
private fun buildSmoothPath(
52+
points: List<Pair<Float, Float>>,
53+
yMin: Float,
54+
yMax: Float,
55+
): Path = Path().apply {
7256
moveTo(points[0].first, points[0].second)
7357
for (i in 0 until points.size - 1) {
7458
val p0 = points[(i - 1).coerceAtLeast(0)]
@@ -77,9 +61,9 @@ private fun buildSmoothPath(points: List<Pair<Float, Float>>): Path = Path().app
7761
val p3 = points[(i + 2).coerceAtMost(points.lastIndex)]
7862

7963
val cp1x = p1.first + (p2.first - p0.first) * SMOOTHING
80-
val cp1y = p1.second + (p2.second - p0.second) * SMOOTHING
64+
val cp1y = (p1.second + (p2.second - p0.second) * SMOOTHING).coerceIn(yMin, yMax)
8165
val cp2x = p2.first - (p3.first - p1.first) * SMOOTHING
82-
val cp2y = p2.second - (p3.second - p1.second) * SMOOTHING
66+
val cp2y = (p2.second - (p3.second - p1.second) * SMOOTHING).coerceIn(yMin, yMax)
8367

8468
cubicTo(cp1x, cp1y, cp2x, cp2y, p2.first, p2.second)
8569
}

0 commit comments

Comments
 (0)