From 6066677dc2944781877d844d9d128872ba4016db Mon Sep 17 00:00:00 2001 From: Dominique Padiou <5765435+dpad85@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:37:39 +0100 Subject: [PATCH] (android) Add swap-in input signing tool This tool is for debugging purposes. It lets users sign a swap-in input locked in the potentiam scheme, and unlock it in cooperation with the peer. --- .../fr/acinq/phoenix/android/AppView.kt | 5 + .../fr/acinq/phoenix/android/Navigation.kt | 1 + .../phoenix/android/components/AmountInput.kt | 5 +- .../components/feedback/ErrorMessage.kt | 5 +- .../settings/walletinfo/SwapInSignerView.kt | 168 ++++++++++++++++++ .../walletinfo/SwapInSignerViewModel.kt | 106 +++++++++++ .../settings/walletinfo/SwapInWalletInfo.kt | 34 +++- .../settings/walletinfo/WalletInfoView.kt | 25 ++- .../phoenix/android/utils/AnnotatedText.kt | 2 +- .../src/main/res/values-b+es+419/strings.xml | 1 - .../src/main/res/values-cs/strings.xml | 1 - .../src/main/res/values-de/strings.xml | 1 - .../src/main/res/values/strings.xml | 15 +- 13 files changed, 353 insertions(+), 16 deletions(-) create mode 100644 phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/SwapInSignerView.kt create mode 100644 phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/SwapInSignerViewModel.kt diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/AppView.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/AppView.kt index e533ea2da..6b6ec023f 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/AppView.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/AppView.kt @@ -80,6 +80,7 @@ import fr.acinq.phoenix.android.settings.fees.AdvancedIncomingFeePolicy import fr.acinq.phoenix.android.settings.fees.LiquidityPolicyView import fr.acinq.phoenix.android.payments.liquidity.RequestLiquidityView import fr.acinq.phoenix.android.settings.walletinfo.FinalWalletInfo +import fr.acinq.phoenix.android.settings.walletinfo.SwapInSignerView import fr.acinq.phoenix.android.settings.walletinfo.SwapInWalletInfo import fr.acinq.phoenix.android.settings.walletinfo.WalletInfoView import fr.acinq.phoenix.android.startup.LegacySwitcherView @@ -400,8 +401,12 @@ fun AppView( SwapInWalletInfo( onBackClick = { navController.popBackStack() }, onViewChannelPolicyClick = { navController.navigate(Screen.LiquidityPolicy.route) }, + onAdvancedClick = { navController.navigate(Screen.WalletInfo.SwapInSigner.route) }, ) } + composable(Screen.WalletInfo.SwapInSigner.route) { + SwapInSignerView(onBackClick = { navController.popBackStack() }) + } composable(Screen.WalletInfo.FinalWallet.route) { FinalWalletInfo(onBackClick = { navController.popBackStack() }) } diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/Navigation.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/Navigation.kt index 6241de65e..2094776b5 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/Navigation.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/Navigation.kt @@ -57,6 +57,7 @@ sealed class Screen(val route: String) { object Logs : Screen("settings/logs") object WalletInfo : Screen("settings/walletinfo") { object SwapInWallet: Screen("settings/walletinfo/swapin") + object SwapInSigner: Screen("settings/walletinfo/swapinsigner") object FinalWallet: Screen("settings/walletinfo/final") } object LiquidityPolicy: Screen("settings/liquiditypolicy") diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/AmountInput.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/AmountInput.kt index 0e3f6a97b..28de3d788 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/AmountInput.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/AmountInput.kt @@ -183,13 +183,14 @@ fun AmountInput( staticLabel: String?, placeholder: @Composable (() -> Unit)? = null, enabled: Boolean = true, + forceUnit: CurrencyUnit? = null, ) { val context = LocalContext.current val prefBitcoinUnit = LocalBitcoinUnit.current val prefFiat = LocalFiatCurrency.current val rate = fiatRate - val units = listOf(BitcoinUnit.Sat, BitcoinUnit.Bit, BitcoinUnit.MBtc, BitcoinUnit.Btc, prefFiat) + val units = forceUnit?.let { listOf(it) } ?: listOf(BitcoinUnit.Sat, BitcoinUnit.Bit, BitcoinUnit.MBtc, BitcoinUnit.Btc, prefFiat) val focusManager = LocalFocusManager.current val customTextSelectionColors = TextSelectionColors( handleColor = MaterialTheme.colors.primary.copy(alpha = 0.7f), @@ -257,7 +258,7 @@ fun AmountInput( colors = outlinedTextFieldColors(), interactionSource = interactionSource, shape = RoundedCornerShape(8.dp), - modifier = Modifier.padding(bottom = 8.dp, top = if (staticLabel != null) 14.dp else 0.dp) + modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp, top = if (staticLabel != null) 14.dp else 0.dp) ) } diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/feedback/ErrorMessage.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/feedback/ErrorMessage.kt index 3dd8a43b4..b50f571d5 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/feedback/ErrorMessage.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/feedback/ErrorMessage.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.unit.dp import fr.acinq.phoenix.android.R import fr.acinq.phoenix.android.utils.annotatedStringResource import fr.acinq.phoenix.android.utils.negativeColor +import fr.acinq.phoenix.android.utils.spannableStringToAnnotatedString @Composable fun ErrorMessage( @@ -39,8 +40,8 @@ fun ErrorMessage( header = header, details = when (details) { null -> null - is AnnotatedString -> annotatedStringResource(id = R.string.component_error_message_details, details) - else -> stringResource(id = R.string.component_error_message_details, details.toString()) + is AnnotatedString -> spannableStringToAnnotatedString(details) + else -> details.toString() }, icon = R.drawable.ic_alert_triangle, iconColor = negativeColor, diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/SwapInSignerView.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/SwapInSignerView.kt new file mode 100644 index 000000000..e3864887a --- /dev/null +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/SwapInSignerView.kt @@ -0,0 +1,168 @@ +/* + * Copyright 2024 ACINQ SAS + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fr.acinq.phoenix.android.settings.walletinfo + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import fr.acinq.bitcoin.Satoshi +import fr.acinq.lightning.MilliSatoshi +import fr.acinq.phoenix.android.LocalBitcoinUnit +import fr.acinq.phoenix.android.business +import fr.acinq.phoenix.android.components.DefaultScreenHeader +import fr.acinq.phoenix.android.components.DefaultScreenLayout +import fr.acinq.phoenix.android.R +import fr.acinq.phoenix.android.components.AmountInput +import fr.acinq.phoenix.android.components.BorderButton +import fr.acinq.phoenix.android.components.Button +import fr.acinq.phoenix.android.components.Card +import fr.acinq.phoenix.android.components.InlineSatoshiInput +import fr.acinq.phoenix.android.components.ProgressView +import fr.acinq.phoenix.android.components.TextInput +import fr.acinq.phoenix.android.components.feedback.ErrorMessage +import fr.acinq.phoenix.android.settings.channels.ImportChannelsDataViewModel +import fr.acinq.phoenix.android.utils.copyToClipboard +import fr.acinq.phoenix.data.BitcoinUnit + + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun SwapInSignerView( + onBackClick: () -> Unit, +) { + val context = LocalContext.current + val vm = viewModel(factory = SwapInSignerViewModel.Factory(business.walletManager)) + + var amountInput by remember { mutableStateOf(null) } + var txInput by remember { mutableStateOf("") } + + DefaultScreenLayout(isScrollable = true) { + DefaultScreenHeader( + onBackClick = onBackClick, + title = stringResource(id = R.string.swapin_signer_title), + ) + + Card(internalPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp)) { + val state = vm.state.value + Text(text = stringResource(id = R.string.swapin_signer_instructions)) + Spacer(modifier = Modifier.height(16.dp)) + AmountInput( + amount = amountInput, + onAmountChange = { + amountInput = it?.amount + if (state != SwapInSignerState.Init) vm.state.value = SwapInSignerState.Init + }, + staticLabel = stringResource(id = R.string.swapin_signer_amount), + enabled = state !is SwapInSignerState.Signing, + modifier = Modifier.fillMaxWidth(), + forceUnit = BitcoinUnit.Sat, + ) + Spacer(modifier = Modifier.height(8.dp)) + TextInput( + text = txInput, + onTextChange = { + txInput = it + if (state != SwapInSignerState.Init) vm.state.value = SwapInSignerState.Init + }, + staticLabel = stringResource(id = R.string.swapin_signer_tx), + maxLines = 4, + enabled = state !is SwapInSignerState.Signing, + errorMessage = if (txInput.isBlank()) stringResource(id = R.string.validation_empty) else null + ) + } + + val keyboardManager = LocalSoftwareKeyboardController.current + Card(horizontalAlignment = Alignment.CenterHorizontally) { + when (val state = vm.state.value) { + is SwapInSignerState.Init -> { + Button( + text = stringResource(id = R.string.swapin_signer_sign), + icon = R.drawable.ic_check, + onClick = { + if (amountInput != null && txInput.isNotBlank()) { + vm.sign(unsignedTx = txInput, amount = amountInput!!.truncateToSatoshi()) + keyboardManager?.hide() + } + }, + modifier = Modifier.fillMaxWidth() + ) + } + is SwapInSignerState.Signing -> { + ProgressView(text = stringResource(id = R.string.swapin_signer_signing)) + } + is SwapInSignerState.Signed -> { + Column(modifier = Modifier.padding(PaddingValues(horizontal = 16.dp, vertical = 12.dp))) { + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + Text(text = stringResource(id = R.string.swapin_signer_signed_sig), style = MaterialTheme.typography.body2, modifier = Modifier.width(100.dp)) + Text(text = state.userSig) + } + Spacer(modifier = Modifier.height(16.dp)) + BorderButton( + text = stringResource(id = R.string.btn_copy), + icon = R.drawable.ic_copy, + onClick = { + copyToClipboard( + context = context, + data = """ + user_sig=${state.userSig} + """.trimIndent(), + dataLabel = "swap input signature" + ) + } + ) + } + } + is SwapInSignerState.Failed.Error -> { + ErrorMessage( + header = stringResource(id = R.string.swapin_signer_error_header), + details = state.cause.message ?: state.cause::class.java.simpleName, + modifier = Modifier.fillMaxWidth(), + ) + } + is SwapInSignerState.Failed.InvalidTxInput -> { + ErrorMessage( + header = stringResource(id = R.string.swapin_signer_invalid_tx_header), + details = stringResource(id = R.string.swapin_signer_invalid_tx_details), + modifier = Modifier.fillMaxWidth(), + ) + } + } + } + } +} \ No newline at end of file diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/SwapInSignerViewModel.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/SwapInSignerViewModel.kt new file mode 100644 index 000000000..209d0b717 --- /dev/null +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/SwapInSignerViewModel.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2024 ACINQ SAS + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fr.acinq.phoenix.android.settings.walletinfo + +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import fr.acinq.bitcoin.ByteVector +import fr.acinq.bitcoin.Satoshi +import fr.acinq.bitcoin.Transaction +import fr.acinq.bitcoin.TxOut +import fr.acinq.lightning.MilliSatoshi +import fr.acinq.lightning.crypto.KeyManager +import fr.acinq.lightning.crypto.LocalKeyManager +import fr.acinq.lightning.transactions.Transactions +import fr.acinq.phoenix.managers.NodeParamsManager +import fr.acinq.phoenix.managers.PeerManager +import fr.acinq.phoenix.managers.WalletManager +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import org.slf4j.LoggerFactory + +sealed class SwapInSignerState { + object Init : SwapInSignerState() + object Signing : SwapInSignerState() + data class Signed( + val amount: Satoshi, + val txId: String, + val userSig: String, + ) : SwapInSignerState() + sealed class Failed : SwapInSignerState() { + data class InvalidTxInput(val cause: Throwable) : Failed() + data class Error(val cause: Throwable) : Failed() + } +} + +class SwapInSignerViewModel( + val walletManager: WalletManager, +) : ViewModel() { + + val log = LoggerFactory.getLogger(this::class.java) + val state = mutableStateOf(SwapInSignerState.Init) + + fun sign( + unsignedTx: String, + amount: Satoshi, + ) { + if (state.value == SwapInSignerState.Signing) return + state.value = SwapInSignerState.Signing + + viewModelScope.launch(Dispatchers.Default + CoroutineExceptionHandler { _, e -> + log.error("failed to sign tx=$unsignedTx for amount=$amount : ", e) + state.value = SwapInSignerState.Failed.Error(e) + }) { + log.debug("signing tx=$unsignedTx amount=$amount") + val tx = try { + Transaction.read(unsignedTx) + } catch (e: Exception) { + log.error("invalid transaction input: ", e) + state.value = SwapInSignerState.Failed.InvalidTxInput(e) + return@launch + } + val keyManager = walletManager.keyManager.filterNotNull().first() + val userSig = Transactions.signSwapInputUser( + fundingTx = tx, + index = 0, + parentTxOut = TxOut(amount, ByteVector.empty), + userKey = keyManager.swapInOnChainWallet.userPrivateKey, + serverKey = keyManager.swapInOnChainWallet.remoteServerPublicKey, + refundDelay = 144 * 30 * 6 + ) + state.value = SwapInSignerState.Signed( + amount = amount, + txId = tx.txid.toString(), + userSig = userSig.toString() + ) + } + } + + class Factory( + private val walletManager: WalletManager, + ) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + @Suppress("UNCHECKED_CAST") + return SwapInSignerViewModel(walletManager) as T + } + } +} \ No newline at end of file diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/SwapInWalletInfo.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/SwapInWalletInfo.kt index e4c135958..48b9b07b7 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/SwapInWalletInfo.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/SwapInWalletInfo.kt @@ -17,12 +17,15 @@ package fr.acinq.phoenix.android.settings.walletinfo 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 import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -30,6 +33,8 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -51,6 +56,7 @@ import fr.acinq.phoenix.android.components.CardHeader import fr.acinq.phoenix.android.components.DefaultScreenHeader import fr.acinq.phoenix.android.components.DefaultScreenLayout import fr.acinq.phoenix.android.components.HSeparator +import fr.acinq.phoenix.android.components.IconPopup import fr.acinq.phoenix.android.components.TextWithIcon import fr.acinq.phoenix.android.utils.Converter.toPrettyString import fr.acinq.phoenix.android.utils.Converter.toRelativeDateString @@ -67,20 +73,40 @@ import kotlin.math.roundToInt fun SwapInWalletInfo( onBackClick: () -> Unit, onViewChannelPolicyClick: () -> Unit, + onAdvancedClick: () -> Unit, ) { val context = LocalContext.current val btcUnit = LocalBitcoinUnit.current val liquidityPolicyInPrefs by UserPrefs.getLiquidityPolicy(context).collectAsState(null) val swapInWallet by business.peerManager.swapInWallet.collectAsState() + var showAdvancedMenuPopIn by remember { mutableStateOf(false) } DefaultScreenLayout(isScrollable = true) { DefaultScreenHeader( onBackClick = onBackClick, - title = stringResource(id = R.string.walletinfo_onchain_swapin), - helpMessage = stringResource(id = R.string.walletinfo_onchain_swapin_help), - helpMessageLink = stringResource(id = R.string.walletinfo_onchain_swapin_help_faq_link) - to "https://phoenix.acinq.co/faq#can-i-deposit-funds-on-chain-to-phoenix-and-how-long-does-it-take-before-i-can-use-it", + content = { + Text(text = stringResource(id = R.string.walletinfo_onchain_swapin)) + IconPopup( + popupMessage = stringResource(id = R.string.walletinfo_onchain_swapin_help), + popupLink = stringResource(id = R.string.walletinfo_onchain_swapin_help_faq_link) + to "https://phoenix.acinq.co/faq#can-i-deposit-funds-on-chain-to-phoenix-and-how-long-does-it-take-before-i-can-use-it" + ) + Spacer(Modifier.weight(1f)) + Box(contentAlignment = Alignment.TopEnd) { + DropdownMenu(expanded = showAdvancedMenuPopIn, onDismissRequest = { showAdvancedMenuPopIn = false }) { + DropdownMenuItem(onClick = onAdvancedClick, contentPadding = PaddingValues(horizontal = 12.dp)) { + Text(stringResource(R.string.swapin_signer_title), style = MaterialTheme.typography.body1) + } + } + Button( + icon = R.drawable.ic_menu_dots, + iconTint = MaterialTheme.colors.onSurface, + padding = PaddingValues(12.dp), + onClick = { showAdvancedMenuPopIn = true } + ) + } + } ) Card { Column(modifier = Modifier.padding(horizontal = 16.dp)) { diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/WalletInfoView.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/WalletInfoView.kt index dc3b8dab2..8ecacc2ae 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/WalletInfoView.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/WalletInfoView.kt @@ -113,7 +113,10 @@ private fun SwapInWalletView(onSwapInWalletClick: () -> Unit) { onClick = onSwapInWalletClick, ) { swapInWallet?.let { wallet -> - OnchainBalanceView(confirmed = (wallet.deeplyConfirmed + wallet.lockedUntilRefund + wallet.readyForRefund).balance, unconfirmed = wallet.unconfirmed.balance + wallet.weaklyConfirmed.balance) + OnchainBalanceView( + confirmed = (wallet.deeplyConfirmed + wallet.lockedUntilRefund + wallet.readyForRefund).balance, + unconfirmed = wallet.unconfirmed.balance + wallet.weaklyConfirmed.balance + ) } ?: ProgressView(text = stringResource(id = R.string.walletinfo_loading_data)) keyManager?.let { HSeparator(modifier = Modifier.padding(start = 16.dp), width = 50.dp) @@ -122,6 +125,11 @@ private fun SwapInWalletView(onSwapInWalletClick: () -> Unit) { value = it.swapInOnChainWallet.descriptor, maxLinesValue = 2 ) + SettingWithCopy( + title = stringResource(id = R.string.walletinfo_swapin_user_pubkey), + value = it.swapInOnChainWallet.userPublicKey.toHex(), + maxLinesValue = 2 + ) } } } @@ -184,7 +192,13 @@ private fun OnchainBalanceView( when (confirmed) { null -> Text(text = stringResource(id = R.string.walletinfo_loading_data), color = mutedTextColor) else -> { - AmountView(amount = confirmed.toMilliSatoshi(), amountTextStyle = MaterialTheme.typography.h4, forceUnit = btcUnit, modifier = Modifier.alignByBaseline(), onClick = null) + AmountView( + amount = confirmed.toMilliSatoshi(), + amountTextStyle = MaterialTheme.typography.h4, + forceUnit = btcUnit, + modifier = Modifier.alignByBaseline(), + onClick = null + ) unconfirmed.takeUnless { it == 0.sat }?.let { Spacer(modifier = Modifier.width(8.dp)) Text( @@ -229,7 +243,12 @@ fun UtxoRow(utxo: WalletState.Utxo, progress: Pair?) { overflow = TextOverflow.Ellipsis, fontSize = 14.sp ) - AmountView(amount = utxo.amount.toMilliSatoshi(), amountTextStyle = MaterialTheme.typography.body1.copy(fontSize = 14.sp), unitTextStyle = MaterialTheme.typography.body1.copy(fontSize = 14.sp), prefix = "+") + AmountView( + amount = utxo.amount.toMilliSatoshi(), + amountTextStyle = MaterialTheme.typography.body1.copy(fontSize = 14.sp), + unitTextStyle = MaterialTheme.typography.body1.copy(fontSize = 14.sp), + prefix = "+" + ) } } diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/utils/AnnotatedText.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/utils/AnnotatedText.kt index fe4924bb1..e5e5f95b4 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/utils/AnnotatedText.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/utils/AnnotatedText.kt @@ -53,7 +53,7 @@ fun annotatedStringResource(@StringRes id: Int): AnnotatedString { } } -private fun spannableStringToAnnotatedString( +fun spannableStringToAnnotatedString( text: CharSequence ): AnnotatedString { return if (text is Spanned) { diff --git a/phoenix-android/src/main/res/values-b+es+419/strings.xml b/phoenix-android/src/main/res/values-b+es+419/strings.xml index 8764a8a5e..243755d5e 100644 --- a/phoenix-android/src/main/res/values-b+es+419/strings.xml +++ b/phoenix-android/src/main/res/values-b+es+419/strings.xml @@ -642,7 +642,6 @@ (incluso) - Causa: %1$s diff --git a/phoenix-android/src/main/res/values-cs/strings.xml b/phoenix-android/src/main/res/values-cs/strings.xml index 3e961d24f..f50694ca2 100644 --- a/phoenix-android/src/main/res/values-cs/strings.xml +++ b/phoenix-android/src/main/res/values-cs/strings.xml @@ -602,7 +602,6 @@ (včetně) - Cause: %1$s diff --git a/phoenix-android/src/main/res/values-de/strings.xml b/phoenix-android/src/main/res/values-de/strings.xml index 43a467638..6ceed2649 100644 --- a/phoenix-android/src/main/res/values-de/strings.xml +++ b/phoenix-android/src/main/res/values-de/strings.xml @@ -590,7 +590,6 @@ (inklusive) - Ursache: %1$s diff --git a/phoenix-android/src/main/res/values/strings.xml b/phoenix-android/src/main/res/values/strings.xml index 16a59e00d..86f3249aa 100644 --- a/phoenix-android/src/main/res/values/strings.xml +++ b/phoenix-android/src/main/res/values/strings.xml @@ -665,12 +665,12 @@ (inclusive) - Cause: %1$s Wallet info Descriptor + User public key Master public key (Path: %1$s) Ready for swap @@ -754,4 +754,17 @@ The wallet has been successfully reset. Reset failed + + + Swap-in signer + This debugging tool lets you sign swap-in inputs. Only use if you understand what it does. + Amount + Unsigned tx + Sign + Signing… + User signature + Invalid unsigned transaction + Check that the input is complete and not missing any character. + Failed to sign input +