Skip to content

Commit d960548

Browse files
committed
(android) Allow manual feerate input in on-chain payments
Removed the blocking spinner on feerate slider when the mempool estimation is not available, and instead show the warning message that was already built-in the slider component. Fixes #743
1 parent 961a2e5 commit d960548

File tree

5 files changed

+80
-89
lines changed

5 files changed

+80
-89
lines changed

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/inputs/FeerateSlider.kt

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,24 @@
1616

1717
package fr.acinq.phoenix.android.components.inputs
1818

19-
import androidx.compose.foundation.layout.*
19+
import androidx.compose.foundation.layout.Column
20+
import androidx.compose.foundation.layout.PaddingValues
21+
import androidx.compose.foundation.layout.Spacer
22+
import androidx.compose.foundation.layout.height
23+
import androidx.compose.foundation.layout.offset
24+
import androidx.compose.foundation.layout.padding
25+
import androidx.compose.foundation.layout.widthIn
26+
import androidx.compose.foundation.shape.RoundedCornerShape
2027
import androidx.compose.material.LocalTextStyle
2128
import androidx.compose.material.MaterialTheme
2229
import androidx.compose.material.Text
23-
import androidx.compose.runtime.*
30+
import androidx.compose.runtime.Composable
31+
import androidx.compose.runtime.CompositionLocalProvider
32+
import androidx.compose.runtime.getValue
33+
import androidx.compose.runtime.mutableStateOf
34+
import androidx.compose.runtime.remember
35+
import androidx.compose.runtime.setValue
2436
import androidx.compose.ui.Modifier
25-
import androidx.compose.ui.graphics.Color
2637
import androidx.compose.ui.res.stringResource
2738
import androidx.compose.ui.unit.dp
2839
import androidx.compose.ui.unit.sp
@@ -32,6 +43,7 @@ import fr.acinq.phoenix.android.R
3243
import fr.acinq.phoenix.android.components.FilledButton
3344
import fr.acinq.phoenix.android.components.TextWithIcon
3445
import fr.acinq.phoenix.android.components.dialogs.Dialog
46+
import fr.acinq.phoenix.android.utils.mutedBgColor
3547
import fr.acinq.phoenix.android.utils.negativeColor
3648
import fr.acinq.phoenix.data.MempoolFeerate
3749

@@ -62,18 +74,21 @@ fun FeerateSlider(
6274
)
6375
}
6476
}
77+
Spacer(Modifier.height(2.dp))
6578
FilledButton(
6679
text = stringResource(id = R.string.mempool_unknown_feerate_title),
6780
icon = R.drawable.ic_alert_triangle,
68-
textStyle = MaterialTheme.typography.body1.copy(color = negativeColor, fontSize = 14.sp),
81+
textStyle = MaterialTheme.typography.body1.copy(fontSize = 14.sp),
6982
iconTint = negativeColor,
7083
maxLines = 1,
7184
space = 6.dp,
72-
backgroundColor = Color.Transparent,
73-
padding = PaddingValues(6.dp),
85+
shape = RoundedCornerShape(10.dp),
86+
backgroundColor = mutedBgColor,
87+
padding = PaddingValues(horizontal = 10.dp, vertical = 6.dp),
7488
modifier = Modifier.offset(x = (-6).dp),
7589
onClick = { showUnknownMempoolStateDialog = true }
7690
)
91+
Spacer(Modifier.height(2.dp))
7792
} else {
7893
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.body1.copy(fontSize = 14.sp)) {
7994
when {

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/send/spliceout/SpliceOutView.kt

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ fun SendSpliceOutView(
7777
val mayDoPayments by business.peerManager.mayDoPayments.collectAsState()
7878
val vm = viewModel<SpliceOutViewModel>(factory = SpliceOutViewModel.Factory(peerManager, business.chain))
7979

80-
var feerate by remember { mutableStateOf(mempoolFeerate?.halfHour?.feerate) }
80+
var feerate by remember { mutableStateOf(mempoolFeerate?.halfHour?.feerate ?: 3.sat) }
8181
var amount by remember { mutableStateOf(requestedAmount) }
8282
var amountErrorMessage by remember { mutableStateOf("") }
8383

@@ -117,22 +117,17 @@ fun SendSpliceOutView(
117117
}
118118
) {
119119
SplashLabelRow(label = stringResource(id = R.string.send_spliceout_feerate_label)) {
120-
feerate?.let { currentFeerate ->
121-
FeerateSlider(
122-
feerate = currentFeerate,
123-
onFeerateChange = { newFeerate ->
124-
if (vm.state != SpliceOutState.Init && feerate != newFeerate) {
125-
vm.state = SpliceOutState.Init
126-
}
127-
feerate = newFeerate
128-
},
129-
mempoolFeerate = mempoolFeerate,
130-
enabled = vm.state !is SpliceOutState.Executing || vm.state !is SpliceOutState.Preparing
131-
)
132-
} ?: run {
133-
ProgressView(text = stringResource(id = R.string.send_spliceout_feerate_waiting_for_value), padding = PaddingValues(0.dp))
134-
Spacer(modifier = Modifier.height(8.dp))
135-
}
120+
FeerateSlider(
121+
feerate = feerate,
122+
onFeerateChange = { newFeerate ->
123+
if (vm.state != SpliceOutState.Init && feerate != newFeerate) {
124+
vm.state = SpliceOutState.Init
125+
}
126+
feerate = newFeerate
127+
},
128+
mempoolFeerate = mempoolFeerate,
129+
enabled = vm.state !is SpliceOutState.Executing || vm.state !is SpliceOutState.Preparing
130+
)
136131
}
137132

138133
Spacer(modifier = Modifier.height(8.dp))
@@ -152,14 +147,11 @@ fun SendSpliceOutView(
152147
enabled = mayDoPayments && amountErrorMessage.isBlank(),
153148
onClick = {
154149
val finalAmount = amount
155-
val finalFeerate = feerate
156150
if (finalAmount == null) {
157151
amountErrorMessage = context.getString(R.string.send_error_amount_invalid)
158-
} else if (finalFeerate == null) {
159-
amountErrorMessage = context.getString(R.string.send_spliceout_error_invalid_feerate)
160152
} else {
161153
keyboardManager?.hide()
162-
vm.prepareSpliceOut(finalAmount, finalFeerate, address)
154+
vm.prepareSpliceOut(finalAmount, feerate, address)
163155
}
164156
}
165157
)

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/MutualCloseView.kt

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ fun MutualCloseView(
9494
var address by remember { mutableStateOf("") }
9595
var addressErrorMessage by remember { mutableStateOf<String?>(null) }
9696
val mempoolFeerate by business.appConfigurationManager.mempoolFeerate.collectAsState()
97-
var feerate by remember { mutableStateOf(mempoolFeerate?.halfHour?.feerate) }
97+
var feerate by remember { mutableStateOf(mempoolFeerate?.halfHour?.feerate ?: 3.sat) }
9898

9999
var showScannerView by remember { mutableStateOf(false) }
100100
var showConfirmationDialog by remember { mutableStateOf(false) }
@@ -160,15 +160,13 @@ fun MutualCloseView(
160160
Row(modifier = Modifier) {
161161
Text(text = stringResource(R.string.send_spliceout_feerate_label), style = MaterialTheme.typography.body2, fontSize = 14.sp, modifier = Modifier.alignByBaseline())
162162
Spacer(modifier = Modifier.width(16.dp))
163-
feerate?.let { currentFeerate ->
164-
FeerateSlider(
165-
modifier = Modifier.alignByBaseline(),
166-
feerate = currentFeerate,
167-
onFeerateChange = { feerate = it },
168-
mempoolFeerate = mempoolFeerate,
169-
enabled = true
170-
)
171-
} ?: ProgressView(text = stringResource(id = R.string.send_spliceout_feerate_waiting_for_value), padding = PaddingValues(0.dp))
163+
FeerateSlider(
164+
modifier = Modifier.alignByBaseline(),
165+
feerate = feerate,
166+
onFeerateChange = { feerate = it },
167+
mempoolFeerate = mempoolFeerate,
168+
enabled = true
169+
)
172170
}
173171
} else {
174172
Text(text = stringResource(id = R.string.mutualclose_no_channels))
@@ -192,8 +190,8 @@ fun MutualCloseView(
192190
modifier = Modifier.fillMaxWidth(),
193191
enabled = peer != null && address.isNotBlank() && model.channels.isNotEmpty() && !isEstimatingFee,
194192
onClick = {
195-
if (peer == null || feerate == null) return@Button
196-
val feeratePerKw = FeeratePerKw(FeeratePerByte(feerate!!))
193+
if (peer == null) return@Button
194+
val feeratePerKw = FeeratePerKw(FeeratePerByte(feerate))
197195
totalFeeEstimate = null
198196
isEstimatingFee = true
199197

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/FinalWalletRefundView.kt

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ fun FinalWalletRefundView(
105105
private fun AddressInputAndFeeSlider(
106106
address: String,
107107
onAddressChange: (String) -> Unit,
108-
feerate: Satoshi?,
108+
feerate: Satoshi,
109109
onFeerateChange: (Satoshi) -> Unit,
110110
onShowScanner: () -> Unit,
111111
mempoolFeerate: MempoolFeerate?,
@@ -130,14 +130,12 @@ private fun AddressInputAndFeeSlider(
130130
Spacer(modifier = Modifier.height(16.dp))
131131

132132
SplashLabelRow(label = stringResource(id = R.string.send_spliceout_feerate_label)) {
133-
feerate?.let { currentFeerate ->
134-
FeerateSlider(
135-
feerate = currentFeerate,
136-
onFeerateChange = onFeerateChange,
137-
mempoolFeerate = mempoolFeerate,
138-
enabled = isEnabled
139-
)
140-
} ?: ProgressView(text = stringResource(id = R.string.send_spliceout_feerate_waiting_for_value), padding = PaddingValues(0.dp))
133+
FeerateSlider(
134+
feerate = feerate,
135+
onFeerateChange = onFeerateChange,
136+
mempoolFeerate = mempoolFeerate,
137+
enabled = isEnabled
138+
)
141139
}
142140
}
143141

@@ -154,7 +152,7 @@ private fun ColumnScope.AvailableForRefund(
154152
val mempoolFeerate by business.appConfigurationManager.mempoolFeerate.collectAsState()
155153

156154
var address by remember { mutableStateOf("") }
157-
var feerate by remember { mutableStateOf(mempoolFeerate?.halfHour?.feerate) }
155+
var feerate by remember { mutableStateOf(mempoolFeerate?.halfHour?.feerate ?: 3.sat) }
158156

159157
var showScannerView by remember { mutableStateOf(false) }
160158

@@ -206,10 +204,8 @@ private fun ColumnScope.AvailableForRefund(
206204
if (showLowFeerateDialog) {
207205
ConfirmLowFeerate(
208206
onConfirm = {
209-
feerate?.let {
210-
onEstimateRefundFee(address, FeeratePerByte(it))
211-
showLowFeerateDialog = false
212-
}
207+
onEstimateRefundFee(address, FeeratePerByte(feerate))
208+
showLowFeerateDialog = false
213209
},
214210
onCancel = { showLowFeerateDialog = false }
215211
)
@@ -218,17 +214,14 @@ private fun ColumnScope.AvailableForRefund(
218214
Button(
219215
text = stringResource(id = R.string.swapinrefund_estimate_button),
220216
icon = R.drawable.ic_inspect,
221-
enabled = feerate != null && address.isNotBlank(),
217+
enabled = address.isNotBlank(),
222218
onClick = {
223219
keyboardManager?.hide()
224-
val finalFeerate = feerate
225-
if (finalFeerate != null) {
226-
val recommendedFeerate = mempoolFeerate?.hour
227-
if (recommendedFeerate != null && finalFeerate < recommendedFeerate.feerate) {
228-
showLowFeerateDialog = true
229-
} else {
230-
onEstimateRefundFee(address, FeeratePerByte(finalFeerate))
231-
}
220+
val recommendedFeerate = mempoolFeerate?.hour
221+
if (recommendedFeerate != null && feerate < recommendedFeerate.feerate) {
222+
showLowFeerateDialog = true
223+
} else {
224+
onEstimateRefundFee(address, FeeratePerByte(feerate))
232225
}
233226
},
234227
backgroundColor = MaterialTheme.colors.primary,

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/walletinfo/SwapInRefundView.kt

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ private fun AvailableForRefundView(
112112
var showScannerView by remember { mutableStateOf(false) }
113113

114114
val mempoolFeerate by business.appConfigurationManager.mempoolFeerate.collectAsState()
115-
var feerate by remember { mutableStateOf(mempoolFeerate?.halfHour?.feerate) }
115+
var feerate by remember { mutableStateOf(mempoolFeerate?.halfHour?.feerate ?: 3.sat) }
116116

117117
if (showScannerView) {
118118
SwapInRefundScanner(
@@ -176,19 +176,17 @@ private fun AvailableForRefundView(
176176
Spacer(modifier = Modifier.height(16.dp))
177177

178178
SplashLabelRow(label = stringResource(id = R.string.send_spliceout_feerate_label)) {
179-
feerate?.let { currentFeerate ->
180-
FeerateSlider(
181-
feerate = currentFeerate,
182-
onFeerateChange = { newFeerate ->
183-
if (vm.state != SwapInRefundState.Init && feerate != newFeerate) {
184-
vm.state = SwapInRefundState.Init
185-
}
186-
feerate = newFeerate
187-
},
188-
mempoolFeerate = mempoolFeerate,
189-
enabled = !(vm.state is SwapInRefundState.Publishing || vm.state is SwapInRefundState.Publishing || vm.state is SwapInRefundState.Done.Success),
190-
)
191-
} ?: ProgressView(text = stringResource(id = R.string.send_spliceout_feerate_waiting_for_value), padding = PaddingValues(0.dp))
179+
FeerateSlider(
180+
feerate = feerate,
181+
onFeerateChange = { newFeerate ->
182+
if (vm.state != SwapInRefundState.Init && feerate != newFeerate) {
183+
vm.state = SwapInRefundState.Init
184+
}
185+
feerate = newFeerate
186+
},
187+
mempoolFeerate = mempoolFeerate,
188+
enabled = !(vm.state is SwapInRefundState.Publishing || vm.state is SwapInRefundState.Publishing || vm.state is SwapInRefundState.Done.Success),
189+
)
192190
}
193191
}
194192
}
@@ -202,10 +200,8 @@ private fun AvailableForRefundView(
202200
if (showLowFeerateDialog) {
203201
ConfirmLowFeerate(
204202
onConfirm = {
205-
feerate?.let {
206-
vm.getFeeForRefund(address = address, feerate = FeeratePerByte(it))
207-
showLowFeerateDialog = false
208-
}
203+
vm.getFeeForRefund(address = address, feerate = FeeratePerByte(feerate))
204+
showLowFeerateDialog = false
209205
},
210206
onCancel = { showLowFeerateDialog = false }
211207
)
@@ -214,17 +210,14 @@ private fun AvailableForRefundView(
214210
Button(
215211
text = stringResource(id = R.string.swapinrefund_estimate_button),
216212
icon = R.drawable.ic_inspect,
217-
enabled = feerate != null && address.isNotBlank(),
213+
enabled = address.isNotBlank(),
218214
onClick = {
219215
keyboardManager?.hide()
220-
val finalFeerate = feerate
221-
if (finalFeerate != null) {
222-
val recommendedFeerate = mempoolFeerate?.hour
223-
if (recommendedFeerate != null && finalFeerate < recommendedFeerate.feerate) {
224-
showLowFeerateDialog = true
225-
} else {
226-
vm.getFeeForRefund(address = address, feerate = FeeratePerByte(finalFeerate))
227-
}
216+
val recommendedFeerate = mempoolFeerate?.hour
217+
if (recommendedFeerate != null && feerate < recommendedFeerate.feerate) {
218+
showLowFeerateDialog = true
219+
} else {
220+
vm.getFeeForRefund(address = address, feerate = FeeratePerByte(feerate))
228221
}
229222
},
230223
padding = PaddingValues(16.dp),

0 commit comments

Comments
 (0)