Skip to content

Commit be3fa47

Browse files
committed
feat: Activity list item polishing & localized title + subtitle
1 parent eaed822 commit be3fa47

File tree

3 files changed

+152
-76
lines changed

3 files changed

+152
-76
lines changed

app/src/main/java/to/bitkit/ext/DateTime.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ object DatePattern {
1818
const val DATE_TIME = "dd/MM/yyyy, HH:mm"
1919
const val INVOICE_EXPIRY = "MMM dd, h:mm a"
2020
const val ACTIVITY_DATE = "MMMM d"
21+
const val ACTIVITY_ROW_DATE = "MMMM d, HH:mm"
22+
const val ACTIVITY_ROW_DATE_YEAR = "MMMM d yyyy, HH:mm"
2123
const val ACTIVITY_TIME = "h:mm"
2224
const val LOG_FILE = "yyyy-MM-dd_HH-mm-ss"
2325
}

app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityRow.kt

Lines changed: 132 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
1616
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
1717
import androidx.compose.ui.unit.dp
1818
import to.bitkit.R
19-
import to.bitkit.ext.toActivityItemDate
19+
import to.bitkit.ext.DatePattern
20+
import to.bitkit.ext.formatted
2021
import to.bitkit.models.PrimaryDisplay
2122
import to.bitkit.ui.LocalCurrencies
2223
import to.bitkit.ui.components.ActivityIcon
@@ -29,6 +30,9 @@ import to.bitkit.ui.theme.Colors
2930
import uniffi.bitkitcore.Activity
3031
import uniffi.bitkitcore.PaymentState
3132
import uniffi.bitkitcore.PaymentType
33+
import java.time.Instant
34+
import java.time.LocalDate
35+
import java.time.ZoneId
3236

3337
@Composable
3438
fun ActivityRow(
@@ -39,6 +43,17 @@ fun ActivityRow(
3943
is Activity.Onchain -> item.v1.id
4044
is Activity.Lightning -> item.v1.id
4145
}
46+
ActivityRowContent(
47+
item = item,
48+
onClick = { onClick(id) },
49+
)
50+
}
51+
52+
@Composable
53+
private fun ActivityRowContent(
54+
item: Activity,
55+
onClick: () -> Unit,
56+
) {
4257
val status: PaymentState? = when (item) {
4358
is Activity.Lightning -> item.v1.status
4459
is Activity.Onchain -> null
@@ -53,8 +68,7 @@ fun ActivityRow(
5368
is Activity.Onchain -> item.v1.txType
5469
}
5570
val isSent = txType == PaymentType.SENT
56-
57-
val amountPrefix = if (txType == PaymentType.SENT) "-" else "+"
71+
val amountPrefix = if (isSent) "-" else "+"
5872
val confirmed: Boolean? = when (item) {
5973
is Activity.Lightning -> null
6074
is Activity.Onchain -> item.v1.confirmed
@@ -63,108 +77,151 @@ fun ActivityRow(
6377
verticalAlignment = Alignment.CenterVertically,
6478
modifier = Modifier
6579
.fillMaxWidth()
66-
.clickableAlpha { onClick(id) }
80+
.clickableAlpha { onClick() }
6781
.padding(vertical = 16.dp)
6882
) {
69-
ActivityIcon(item)
70-
Spacer(modifier = Modifier.width(12.dp))
71-
72-
// TODO: use localized status texts
73-
val lightningStatus = when {
74-
txType == PaymentType.SENT -> when (status) {
75-
PaymentState.FAILED -> "Sending Failed"
76-
PaymentState.PENDING -> "Sending..."
77-
PaymentState.SUCCEEDED -> "Sent"
78-
else -> ""
79-
}
80-
81-
else -> when (status) {
82-
PaymentState.FAILED -> "Receive Failed"
83-
PaymentState.PENDING -> "Receiving..."
84-
PaymentState.SUCCEEDED -> "Received"
85-
else -> ""
86-
}
87-
}
88-
val onchainStatus = when {
89-
txType == PaymentType.SENT -> if (confirmed == true) stringResource(R.string.wallet__activity_sent) else "Sending..."
90-
else -> if (confirmed == true) stringResource(R.string.wallet__activity_received) else "Receiving..."
91-
}
92-
83+
ActivityIcon(activity = item, size = 32.dp)
84+
Spacer(modifier = Modifier.width(16.dp))
9385
Column(
94-
verticalArrangement = Arrangement.spacedBy(2.dp)
86+
verticalArrangement = Arrangement.spacedBy(4.dp),
9587
) {
96-
BodyMSB(text = if (isLightning) lightningStatus else onchainStatus)
97-
// TODO timestamp: if today - only hour
88+
TransactionStatusText(
89+
txType = txType,
90+
isLightning = isLightning,
91+
status = status,
92+
confirmed = confirmed,
93+
)
9894
val subtitleText = when (item) {
99-
is Activity.Lightning -> {
100-
item.v1.message.ifEmpty { timestamp.toActivityItemDate() }
95+
is Activity.Lightning -> item.v1.message.ifEmpty { formattedTime(timestamp) }
96+
is Activity.Onchain -> {
97+
if (confirmed == true) {
98+
formattedTime(timestamp)
99+
} else {
100+
// TODO: calculate confirmsIn text
101+
stringResource(R.string.wallet__activity_confirms_in).replace("{feeRateDescription}", "???")
102+
}
101103
}
102-
103-
else -> timestamp.toActivityItemDate()
104104
}
105105
CaptionB(
106106
text = subtitleText,
107107
color = Colors.White64,
108108
)
109109
}
110110
Spacer(modifier = Modifier.weight(1f))
111-
val amount = when (item) {
112-
is Activity.Lightning -> item.v1.value
113-
is Activity.Onchain -> when {
114-
isSent -> item.v1.value + item.v1.fee
115-
else -> item.v1.value
111+
AmountView(
112+
item = item,
113+
prefix = amountPrefix,
114+
)
115+
}
116+
}
117+
118+
@Composable
119+
private fun TransactionStatusText(
120+
txType: PaymentType,
121+
isLightning: Boolean,
122+
status: PaymentState?,
123+
confirmed: Boolean?,
124+
) {
125+
when {
126+
isLightning -> {
127+
when (txType) {
128+
PaymentType.SENT -> when (status) {
129+
PaymentState.FAILED -> BodyMSB(text = stringResource(R.string.wallet__activity_failed))
130+
PaymentState.PENDING -> BodyMSB(text = stringResource(R.string.wallet__activity_pending))
131+
PaymentState.SUCCEEDED -> BodyMSB(text = stringResource(R.string.wallet__activity_sent))
132+
else -> {}
133+
}
134+
135+
else -> when (status) {
136+
PaymentState.FAILED -> BodyMSB(text = stringResource(R.string.wallet__activity_failed))
137+
PaymentState.PENDING -> BodyMSB(text = stringResource(R.string.wallet__activity_pending))
138+
PaymentState.SUCCEEDED -> BodyMSB(text = stringResource(R.string.wallet__activity_received))
139+
else -> {}
140+
}
141+
}
142+
}
143+
144+
else -> {
145+
when (txType) {
146+
PaymentType.SENT -> BodyMSB(text = stringResource(R.string.wallet__activity_sent))
147+
else -> BodyMSB(text = stringResource(R.string.wallet__activity_received))
116148
}
117149
}
118-
amount.let { sats ->
119-
val currency = currencyViewModel ?: return
120-
val (_, _, _, _, displayUnit, primaryDisplay) = LocalCurrencies.current
150+
}
151+
}
121152

122-
currency.convert(sats = sats.toLong())?.let { converted ->
123-
Column(
124-
horizontalAlignment = Alignment.End,
125-
verticalArrangement = Arrangement.spacedBy(2.dp),
153+
@Composable
154+
private fun AmountView(
155+
item: Activity,
156+
prefix: String,
157+
) {
158+
val currency = currencyViewModel ?: return
159+
val (_, _, _, _, displayUnit, primaryDisplay) = LocalCurrencies.current
160+
val amount = when (item) {
161+
is Activity.Lightning -> item.v1.value
162+
is Activity.Onchain -> when (item.v1.txType) {
163+
PaymentType.SENT -> item.v1.value + item.v1.fee
164+
else -> item.v1.value
165+
}
166+
}
167+
currency.convert(sats = amount.toLong())?.let { converted ->
168+
val btcComponents = converted.bitcoinDisplay(displayUnit)
169+
Column(
170+
horizontalAlignment = Alignment.End,
171+
verticalArrangement = Arrangement.spacedBy(2.dp),
172+
) {
173+
if (primaryDisplay == PrimaryDisplay.BITCOIN) {
174+
Row(
175+
verticalAlignment = Alignment.CenterVertically,
176+
horizontalArrangement = Arrangement.spacedBy(1.dp),
126177
) {
127-
val btcComponents = converted.bitcoinDisplay(displayUnit)
128-
if (primaryDisplay == PrimaryDisplay.BITCOIN) {
129-
Row(
130-
verticalAlignment = Alignment.CenterVertically,
131-
horizontalArrangement = Arrangement.spacedBy(1.dp)
132-
) {
133-
BodyMSB(text = amountPrefix, color = Colors.White64)
134-
BodyMSB(text = btcComponents.value)
135-
}
136-
CaptionB(
137-
text = "${converted.symbol} ${converted.formatted}",
138-
color = Colors.White64,
139-
)
140-
} else {
141-
Row(
142-
verticalAlignment = Alignment.CenterVertically,
143-
horizontalArrangement = Arrangement.spacedBy(1.dp)
144-
) {
145-
BodyMSB(text = amountPrefix, color = Colors.White64)
146-
BodyMSB(text = "${converted.symbol} ${converted.formatted}")
147-
}
148-
CaptionB(
149-
text = btcComponents.value,
150-
color = Colors.White64,
151-
)
152-
}
178+
BodyMSB(text = prefix, color = Colors.White64)
179+
BodyMSB(text = btcComponents.value)
153180
}
181+
CaptionB(
182+
text = "${converted.symbol} ${converted.formatted}",
183+
color = Colors.White64,
184+
)
185+
} else {
186+
Row(
187+
verticalAlignment = Alignment.CenterVertically,
188+
horizontalArrangement = Arrangement.spacedBy(1.dp),
189+
) {
190+
BodyMSB(text = prefix, color = Colors.White64)
191+
BodyMSB(text = "${converted.symbol} ${converted.formatted}")
192+
}
193+
CaptionB(
194+
text = btcComponents.value,
195+
color = Colors.White64,
196+
)
154197
}
155198
}
156199
}
157200
}
158201

202+
private fun formattedTime(timestamp: ULong): String {
203+
val instant = Instant.ofEpochSecond(timestamp.toLong())
204+
val dateTime = instant.atZone(ZoneId.systemDefault())
205+
val now = LocalDate.now()
206+
207+
val isToday = dateTime.toLocalDate() == now
208+
val isThisYear = dateTime.year == now.year
209+
return when {
210+
isToday -> instant.formatted(DatePattern.ACTIVITY_TIME)
211+
isThisYear -> instant.formatted(DatePattern.ACTIVITY_ROW_DATE)
212+
else -> instant.formatted(DatePattern.ACTIVITY_ROW_DATE_YEAR)
213+
}
214+
}
215+
159216
private class ActivityItemsPreviewProvider : PreviewParameterProvider<Activity> {
160217
override val values: Sequence<Activity> get() = testActivityItems.asSequence()
161218
}
162219

163220
@Preview(showBackground = true)
164221
@Composable
165-
private fun ActivityRowPreview(@PreviewParameter(ActivityItemsPreviewProvider::class) item: Activity) {
222+
private fun Preview(@PreviewParameter(ActivityItemsPreviewProvider::class) item: Activity) {
166223
AppThemeSurface {
167-
ActivityRow(
224+
ActivityRowContent(
168225
item = item,
169226
onClick = {},
170227
)

app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ private val today: Calendar = Calendar.getInstance()
311311
private val yesterday: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -1) }
312312
private val thisWeek: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -3) }
313313
private val thisMonth: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -10) }
314+
private val lastYear: Calendar = Calendar.getInstance().apply { add(Calendar.YEAR, -1) }
314315

315316
val testActivityItems: List<Activity> = listOf(
316317
// Today
@@ -340,7 +341,7 @@ val testActivityItems: List<Activity> = listOf(
340341
LightningActivity(
341342
id = "2",
342343
txType = PaymentType.SENT,
343-
status = PaymentState.SUCCEEDED,
344+
status = PaymentState.PENDING,
344345
value = 30_000_u,
345346
fee = 15_u,
346347
invoice = "lnbcrt2",
@@ -389,5 +390,21 @@ val testActivityItems: List<Activity> = listOf(
389390
updatedAt = thisMonth.timeInMillis.toULong() / 1000u,
390391
)
391392
),
393+
// Last Year
394+
Activity.Lightning(
395+
LightningActivity(
396+
id = "5",
397+
txType = PaymentType.SENT,
398+
status = PaymentState.SUCCEEDED,
399+
value = 200_000_u,
400+
fee = 1_u,
401+
invoice = "lnbc…",
402+
message = "",
403+
timestamp = (lastYear.timeInMillis.toULong() / 1000u),
404+
preimage = null,
405+
createdAt = (lastYear.timeInMillis.toULong() / 1000u),
406+
updatedAt = (lastYear.timeInMillis.toULong() / 1000u),
407+
)
408+
),
392409
)
393410
// endregion

0 commit comments

Comments
 (0)