Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion app/src/main/java/to/bitkit/ext/DateTime.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ fun Instant.formatted(pattern: String = DatePattern.DATE_TIME): String {
object DatePattern {
const val DATE_TIME = "dd/MM/yyyy, HH:mm"
const val INVOICE_EXPIRY = "MMM dd, h:mm a"
const val ACTIVITY_ITEM = "MMMM d yyyy, HH:mm"
const val ACTIVITY_DATE = "MMMM d"
const val ACTIVITY_ROW_DATE = "MMMM d, HH:mm"
const val ACTIVITY_ROW_DATE_YEAR = "MMMM d yyyy, HH:mm"
const val ACTIVITY_TIME = "h:mm"
const val LOG_FILE = "yyyy-MM-dd_HH-mm-ss"
}
6 changes: 5 additions & 1 deletion app/src/main/java/to/bitkit/ext/Numbers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import java.time.Instant
val ULong.millis: ULong get() = this * 1000u

fun ULong.toActivityItemDate(): String {
return Instant.ofEpochSecond(this.toLong()).formatted(DatePattern.ACTIVITY_ITEM)
return Instant.ofEpochSecond(this.toLong()).formatted(DatePattern.ACTIVITY_DATE)
}

fun ULong.toActivityItemTime(): String {
return Instant.ofEpochSecond(this.toLong()).formatted(DatePattern.ACTIVITY_TIME)
}

fun Number.formatWithDotSeparator(): String {
Expand Down
231 changes: 231 additions & 0 deletions app/src/main/java/to/bitkit/ui/components/ActivityIcon.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package to.bitkit.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import to.bitkit.R
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
import uniffi.bitkitcore.Activity
import uniffi.bitkitcore.LightningActivity
import uniffi.bitkitcore.OnchainActivity
import uniffi.bitkitcore.PaymentState
import uniffi.bitkitcore.PaymentType

@Composable
fun ActivityIcon(
activity: Activity,
size: Dp = 32.dp,
modifier: Modifier = Modifier,
) {
val isLightning = activity is Activity.Lightning
val status: PaymentState? = when (activity) {
is Activity.Lightning -> activity.v1.status
is Activity.Onchain -> null
}
val txType: PaymentType = when (activity) {
is Activity.Lightning -> activity.v1.txType
is Activity.Onchain -> activity.v1.txType
}
val arrowIcon = painterResource(if (txType == PaymentType.SENT) R.drawable.ic_sent else R.drawable.ic_received)

if (isLightning) {
when (status) {
PaymentState.FAILED -> {
CircularIcon(
icon = painterResource(R.drawable.ic_x),
iconColor = Colors.Purple,
backgroundColor = Colors.Purple16,
size = size,
modifier = modifier,
)
}

PaymentState.PENDING -> {
CircularIcon(
icon = painterResource(R.drawable.ic_hourglass_simple),
iconColor = Colors.Purple,
backgroundColor = Colors.Purple16,
size = size,
modifier = modifier,
)
}

else -> {
CircularIcon(
icon = arrowIcon,
iconColor = Colors.Purple,
backgroundColor = Colors.Purple16,
size = size,
modifier = modifier,
)
}
}
} else {
val isTransfer = (activity as? Activity.Onchain)?.v1?.isTransfer == true
val onChainIcon = if (isTransfer) painterResource(R.drawable.ic_transfer) else arrowIcon

CircularIcon(
icon = onChainIcon,
iconColor = Colors.Brand,
backgroundColor = Colors.Brand16,
size = size,
modifier = modifier,
)
}
}

@Composable
fun CircularIcon(
icon: Painter,
iconColor: Color,
backgroundColor: Color,
size: Dp = 32.dp,
modifier: Modifier = Modifier,
) {
Box(
contentAlignment = Alignment.Companion.Center,
modifier = modifier
.size(size)
.background(backgroundColor, CircleShape)
) {
Icon(
painter = icon,
contentDescription = null,
tint = iconColor,
modifier = Modifier.size(size * 0.5f),
)
}
}

@Preview(showBackground = true)
@Composable
private fun Preview() {
AppThemeSurface {
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(16.dp),
) {
// Lightning Sent Succeeded
ActivityIcon(
activity = Activity.Lightning(
v1 = LightningActivity(
id = "test-lightning-1",
txType = PaymentType.SENT,
status = PaymentState.SUCCEEDED,
value = 50000uL,
fee = 1uL,
invoice = "lnbc...",
message = "",
timestamp = (System.currentTimeMillis() / 1000).toULong(),
preimage = null,
createdAt = null,
updatedAt = null,
)
)
)

// Lightning Received Failed
ActivityIcon(
activity = Activity.Lightning(
v1 = LightningActivity(
id = "test-lightning-2",
txType = PaymentType.RECEIVED,
status = PaymentState.FAILED,
value = 50000uL,
fee = 1uL,
invoice = "lnbc...",
message = "",
timestamp = (System.currentTimeMillis() / 1000).toULong(),
preimage = null,
createdAt = null,
updatedAt = null,
)
)
)

// Lightning Pending
ActivityIcon(
activity = Activity.Lightning(
v1 = LightningActivity(
id = "test-lightning-3",
txType = PaymentType.SENT,
status = PaymentState.PENDING,
value = 50000uL,
fee = 1uL,
invoice = "lnbc...",
message = "",
timestamp = (System.currentTimeMillis() / 1000).toULong(),
preimage = null,
createdAt = null,
updatedAt = null,
)
)
)

// Onchain Received
ActivityIcon(
activity = Activity.Onchain(
v1 = OnchainActivity(
id = "test-onchain-1",
txType = PaymentType.RECEIVED,
txId = "abc123",
value = 100000uL,
fee = 500uL,
feeRate = 8uL,
address = "bc1...",
confirmed = true,
timestamp = (System.currentTimeMillis() / 1000).toULong(),
isBoosted = false,
isTransfer = false,
doesExist = true,
confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(),
channelId = null,
transferTxId = null,
createdAt = null,
updatedAt = null,
)
)
)

// Onchain Transfer
ActivityIcon(
activity = Activity.Onchain(
v1 = OnchainActivity(
id = "test-onchain-2",
txType = PaymentType.SENT,
txId = "abc123",
value = 100000uL,
fee = 500uL,
feeRate = 8uL,
address = "bc1...",
confirmed = true,
timestamp = (System.currentTimeMillis() / 1000).toULong(),
isBoosted = false,
isTransfer = true,
doesExist = true,
confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(),
channelId = null,
transferTxId = "transferTxId",
createdAt = null,
updatedAt = null,
)
)
)
}
}
}
37 changes: 22 additions & 15 deletions app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import to.bitkit.models.BITCOIN_SYMBOL
import to.bitkit.models.ConvertedAmount
import to.bitkit.models.PrimaryDisplay
import to.bitkit.ui.LocalCurrencies
Expand All @@ -27,6 +29,21 @@ fun BalanceHeaderView(
prefix: String? = null,
showBitcoinSymbol: Boolean = true,
) {
val isPreview = LocalInspectionMode.current
if (isPreview) {
BalanceHeader(
modifier = modifier,
smallRowSymbol = "$",
smallRowText = "12.34",
largeRowPrefix = prefix,
largeRowText = "$sats",
largeRowSymbol = BITCOIN_SYMBOL,
showSymbol = showBitcoinSymbol,
onClick = {},
)
return
}

val currency = currencyViewModel ?: return
val (rates, _, _, _, displayUnit, primaryDisplay) = LocalCurrencies.current
val converted: ConvertedAmount? = if (rates.isNotEmpty()) currency.convert(sats = sats) else null
Expand All @@ -37,7 +54,6 @@ fun BalanceHeaderView(
if (primaryDisplay == PrimaryDisplay.BITCOIN) {
BalanceHeader(
modifier = modifier,
smallRowPrefix = prefix,
smallRowSymbol = converted.symbol,
smallRowText = converted.formatted,
largeRowPrefix = prefix,
Expand All @@ -49,7 +65,6 @@ fun BalanceHeaderView(
} else {
BalanceHeader(
modifier = modifier,
smallRowPrefix = prefix,
smallRowSymbol = btcComponents.symbol,
smallRowText = btcComponents.value,
largeRowPrefix = prefix,
Expand All @@ -65,7 +80,6 @@ fun BalanceHeaderView(
@Composable
fun BalanceHeader(
modifier: Modifier = Modifier,
smallRowPrefix: String? = null,
smallRowSymbol: String? = null,
smallRowText: String,
largeRowPrefix: String? = null,
Expand All @@ -80,7 +94,6 @@ fun BalanceHeader(
modifier = modifier.clickableAlpha { onClick() }
) {
SmallRow(
prefix = smallRowPrefix,
symbol = smallRowSymbol,
text = smallRowText
)
Expand Down Expand Up @@ -120,17 +133,11 @@ fun LargeRow(prefix: String?, text: String, symbol: String, showSymbol: Boolean)
}

@Composable
private fun SmallRow(prefix: String?, symbol: String?, text: String) {
private fun SmallRow(symbol: String?, text: String) {
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
if (prefix != null) {
Caption13Up(
text = prefix,
color = Colors.White64,
)
}
if (symbol != null) {
Caption13Up(
text = symbol,
Expand All @@ -149,12 +156,12 @@ private fun SmallRow(prefix: String?, symbol: String?, text: String) {
private fun Preview() {
AppThemeSurface {
BalanceHeader(
smallRowPrefix = "$",
smallRowSymbol = "$",
smallRowText = "27.36",
largeRowPrefix = "",
largeRowPrefix = "+",
largeRowText = "136 825",
largeRowSymbol = "sats",
showSymbol = false,
largeRowSymbol = "",
showSymbol = true,
modifier = Modifier.fillMaxWidth(),
onClick = {}
)
Expand Down
13 changes: 12 additions & 1 deletion app/src/main/java/to/bitkit/ui/components/Button.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package to.bitkit.ui.components

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
Expand All @@ -20,6 +21,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
Expand Down Expand Up @@ -74,7 +76,9 @@ fun PrimaryButton(
)
} else {
if (icon != null) {
icon()
Box(modifier = if (enabled) Modifier else Modifier.alpha(0.5f)) {
icon()
}
Spacer(modifier = Modifier.width(8.dp))
}
Text(
Expand Down Expand Up @@ -200,6 +204,13 @@ private fun PrimaryButtonPreview() {
PrimaryButton(
text = "Primary Disabled",
onClick = {},
icon = {
Icon(
imageVector = Icons.Filled.Favorite,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
},
enabled = false,
)
PrimaryButton(
Expand Down
Loading