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
20 changes: 12 additions & 8 deletions app/src/main/java/to/bitkit/repositories/LightningRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.lightningdevkit.ldknode.PaymentId
import org.lightningdevkit.ldknode.SpendableUtxo
import org.lightningdevkit.ldknode.Txid
import org.lightningdevkit.ldknode.UserChannelId
import to.bitkit.data.CacheStore
import to.bitkit.data.SettingsStore
import to.bitkit.data.keychain.Keychain
import to.bitkit.di.BgDispatcher
Expand Down Expand Up @@ -60,7 +61,8 @@ class LightningRepo @Inject constructor(
private val blocktankNotificationsService: BlocktankNotificationsService,
private val firebaseMessaging: FirebaseMessaging,
private val keychain: Keychain,
private val lnUrlWithdrawService: LnUrlWithdrawService
private val lnUrlWithdrawService: LnUrlWithdrawService,
private val cacheStore: CacheStore,
) {
private val _lightningState = MutableStateFlow(LightningState())
val lightningState = _lightningState.asStateFlow()
Expand Down Expand Up @@ -403,7 +405,7 @@ class LightningRepo @Inject constructor(

suspend fun createLnurlInvoice(
address: String,
amountSatoshis: ULong
amountSatoshis: ULong,
): Result<String> = executeWhenNodeRunning("getLnUrlInvoice") {
val invoice = getLnurlInvoice(address, amountSatoshis)
Result.success(invoice)
Expand Down Expand Up @@ -475,7 +477,7 @@ class LightningRepo @Inject constructor(
utxosToSpend: List<SpendableUtxo>? = null,
): Result<Txid> =
executeWhenNodeRunning("Send on-chain") {
val transactionSpeed = speed ?: settingsStore.data.map { it.defaultTransactionSpeed }.first()
val transactionSpeed = speed ?: settingsStore.data.first().defaultTransactionSpeed
val fees = coreService.blocktank.getFees().getOrThrow()
val satsPerVByte = fees.getSatsPerVByteFor(transactionSpeed)

Expand Down Expand Up @@ -541,25 +543,27 @@ class LightningRepo @Inject constructor(
}

suspend fun calculateTotalFee(
address: Address,
amountSats: ULong,
address: Address? = null,
speed: TransactionSpeed? = null,
utxosToSpend: List<SpendableUtxo>? = null,
): Result<ULong> = withContext(bgDispatcher) {
return@withContext try {
val transactionSpeed = speed ?: settingsStore.data.map { it.defaultTransactionSpeed }.first()
val transactionSpeed = speed ?: settingsStore.data.first().defaultTransactionSpeed
val satsPerVByte = getFeeRateForSpeed(transactionSpeed).getOrThrow().toUInt()

val addressOrDefault = address ?: cacheStore.data.first().onchainAddress

val fee = lightningService.calculateTotalFee(
address = address,
address = addressOrDefault,
amountSats = amountSats,
satsPerVByte = satsPerVByte,
utxosToSpend = utxosToSpend,
)
Result.success(fee)
} catch (e: Throwable) {
} catch (_: Throwable) {
val fallbackFee = 1000uL
Logger.error("Estimate fee error, using conservative fallback of $fallbackFee", e, context = TAG)
Logger.warn("Error calculating fee, using fallback of $fallbackFee", context = TAG)
Result.success(fallbackFee)
}
}
Expand Down
50 changes: 21 additions & 29 deletions app/src/main/java/to/bitkit/repositories/WalletRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,27 @@ class WalletRepo @Inject constructor(
}
}

suspend fun getMaxSendAmount(): ULong = withContext(bgDispatcher) {
val totalOnchainSats = balanceState.value.totalOnchainSats
if (totalOnchainSats == 0uL) {
return@withContext 0uL
}

try {
val minFeeBuffer = 1000uL
val amountSats = (totalOnchainSats - minFeeBuffer).coerceAtLeast(0uL)
val fee = lightningRepo.calculateTotalFee(amountSats).getOrThrow()
val maxSendable = (totalOnchainSats - fee).coerceAtLeast(0uL)

return@withContext maxSendable
} catch (_: Throwable) {
Logger.debug("Could not calculate max send amount, using as fallback 90% of total", context = TAG)
val fallbackMax = (totalOnchainSats.toDouble() * 0.9).toULong()
return@withContext fallbackMax
}
}


// Settings
suspend fun setShowEmptyState(show: Boolean) {
settingsStore.update { it.copy(showEmptyState = show) }
Expand Down Expand Up @@ -491,35 +512,6 @@ class WalletRepo @Inject constructor(
}
}

suspend fun deleteActivityById(id: String) = withContext(bgDispatcher) {
runCatching {
coreService.activity.delete(id)
}.onFailure { e ->
Logger.error(msg = "Error deleteActivityById. id:$id", e = e, context = TAG)
}
}


suspend fun getOnChainActivityByTxId(txId: String, txType: PaymentType): Result<Activity> {
return runCatching {
findActivityWithRetry(
paymentHashOrTxId = txId,
txType = txType,
type = ActivityFilter.ONCHAIN
) ?: return Result.failure(Exception("Activity not found"))
}.onFailure { e ->
Logger.error(msg = "Error getOnChainActivityByTxId. txId:$txId, txType:$txType", e = e, context = TAG)
}
}

suspend fun updateActivity(id: String, updatedActivity: Activity): Result<Unit> {
return runCatching {
coreService.activity.update(id, updatedActivity)
}.onFailure { e ->
Logger.error(msg = "Error updateActivity. id:$id, updatedActivity:$updatedActivity", e = e, context = TAG)
}
}

suspend fun attachTagsToActivity(
paymentHashOrTxId: String?,
type: ActivityFilter,
Expand Down
10 changes: 7 additions & 3 deletions app/src/main/java/to/bitkit/ui/ContentView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ import to.bitkit.viewmodels.AppViewModel
import to.bitkit.viewmodels.BackupsViewModel
import to.bitkit.viewmodels.BlocktankViewModel
import to.bitkit.viewmodels.CurrencyViewModel
import to.bitkit.viewmodels.ExternalNodeViewModel
import to.bitkit.ui.screens.transfer.external.ExternalNodeViewModel
import to.bitkit.viewmodels.MainScreenEffect
import to.bitkit.viewmodels.RestoreState
import to.bitkit.viewmodels.SendEvent
Expand Down Expand Up @@ -615,9 +615,13 @@ private fun RootNavHost(
)
}
composableWithDefaultTransitions<Routes.ExternalFeeCustom> {
val parentEntry = remember(it) { navController.getBackStackEntry(Routes.ExternalNav) }
val viewModel = hiltViewModel<ExternalNodeViewModel>(parentEntry)

ExternalFeeCustomScreen(
onBackClick = { navController.popBackStack() },
onCloseClick = { navController.popBackStack<Routes.TransferRoot>(inclusive = true) },
viewModel = viewModel,
onBack = { navController.popBackStack() },
onClose = { navController.navigateToHome() },
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,12 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.ui.LocalBalances
import to.bitkit.ui.LocalCurrencies
Expand All @@ -37,7 +33,7 @@ import to.bitkit.ui.scaffold.ScreenColumn
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
import to.bitkit.ui.utils.withAccent
import to.bitkit.viewmodels.ExternalNodeViewModel
import kotlin.math.min
import kotlin.math.roundToLong

@Composable
Expand All @@ -47,9 +43,14 @@ fun ExternalAmountScreen(
onBackClick: () -> Unit,
onCloseClick: () -> Unit,
) {
ExternalAmountContent(
onContinueClick = { satsAmount ->
viewModel.onAmountContinue(satsAmount)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()

Content(
amountState = uiState.amount,
onAmountChange = { sats -> viewModel.onAmountChange(sats) },
onAmountOverride = { sats -> viewModel.onAmountOverride(sats) },
onContinueClick = {
viewModel.onAmountContinue()
onContinue()
},
onBackClick = onBackClick,
Expand All @@ -58,8 +59,11 @@ fun ExternalAmountScreen(
}

@Composable
private fun ExternalAmountContent(
onContinueClick: (Long) -> Unit = {},
private fun Content(
amountState: ExternalNodeContract.UiState.Amount = ExternalNodeContract.UiState.Amount(),
onAmountChange: (Long) -> Unit = {},
onAmountOverride: (Long) -> Unit = {},
onContinueClick: () -> Unit = {},
onBackClick: () -> Unit = {},
onCloseClick: () -> Unit = {},
) {
Expand All @@ -75,21 +79,17 @@ private fun ExternalAmountContent(
.fillMaxSize()
.imePadding()
) {
var satsAmount by rememberSaveable { mutableLongStateOf(0) }
var overrideSats: Long? by remember { mutableStateOf(null) }

val availableAmount = LocalBalances.current.totalOnchainSats
val totalOnchainSats = LocalBalances.current.totalOnchainSats

Spacer(modifier = Modifier.height(16.dp))
Display(stringResource(R.string.lightning__external_amount__title).withAccent(accentColor = Colors.Purple))
Spacer(modifier = Modifier.height(32.dp))

AmountInput(
primaryDisplay = LocalCurrencies.current.primaryDisplay,
overrideSats = overrideSats,
overrideSats = amountState.overrideSats,
) { sats ->
satsAmount = sats
overrideSats = null
onAmountChange(sats)
}

Spacer(modifier = Modifier.weight(1f))
Expand All @@ -106,7 +106,7 @@ private fun ExternalAmountContent(
color = Colors.White64,
)
Spacer(modifier = Modifier.height(8.dp))
MoneySSB(sats = availableAmount.toLong())
MoneySSB(sats = amountState.max)
}
Spacer(modifier = Modifier.weight(1f))
UnitButton(color = Colors.Purple)
Expand All @@ -115,17 +115,17 @@ private fun ExternalAmountContent(
text = stringResource(R.string.lightning__spending_amount__quarter),
color = Colors.Purple,
onClick = {
val quarter = (availableAmount.toDouble() / 4.0).roundToLong()
overrideSats = quarter
val quarterOfTotal = (totalOnchainSats.toDouble() / 4.0).roundToLong()
val cappedQuarter = min(quarterOfTotal, amountState.max)
onAmountOverride(cappedQuarter)
},
)
// Max Button
NumberPadActionButton(
text = stringResource(R.string.common__max),
color = Colors.Purple,
onClick = {
val max = (availableAmount.toDouble() * 0.9).toLong() // TODO calc max amount
overrideSats = max
onAmountOverride(amountState.max)
},
)
}
Expand All @@ -134,20 +134,19 @@ private fun ExternalAmountContent(

PrimaryButton(
text = stringResource(R.string.common__continue),
onClick = { onContinueClick(satsAmount) },
enabled = satsAmount != 0L,
onClick = { onContinueClick() },
enabled = amountState.sats != 0L,
)

Spacer(modifier = Modifier.height(16.dp))
}
}
}

@Preview(showSystemUi = true, showBackground = true)
@Preview(showSystemUi = true)
@Composable
private fun Preview() {
AppThemeSurface {
ExternalAmountContent(
)
Content()
}
}
Loading