mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-23 06:35:11 +01:00
(gui) User can define a preferred display unit
On startup FxApp reads the configuration from `eclair.conf` and sets a unit to which all the amounts in the UI must be converted. This unit is a CoinUnit object. Only `sat`, `mbtc` and `btc` are accepted. `msat` is not accepted because it's an internal accounting unit which should be invisible to the user. Default unit is `btc` The gui does not expose any ui feature to update this unit at runtime.
This commit is contained in:
parent
7d4edceecf
commit
c248c2cdc3
13 changed files with 218 additions and 78 deletions
|
@ -32,7 +32,7 @@
|
|||
</HBox>
|
||||
|
||||
<ProgressBar fx:id="balanceBar" minHeight="4.0" prefHeight="4.0" maxWidth="1.7976931348623157E308"
|
||||
progress="0.0" snapToPixel="false" focusTraversable="false"
|
||||
progress="0.0" snapToPixel="false"
|
||||
GridPane.columnSpan="4" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1"/>
|
||||
|
||||
<Label styleClass="text-muted" text="Funding tx id" GridPane.columnIndex="0" GridPane.rowIndex="2"/>
|
||||
|
@ -43,12 +43,12 @@
|
|||
<TextField fx:id="nodeId" text="N/A" focusTraversable="false" editable="false" styleClass="noteditable"
|
||||
GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="3"/>
|
||||
|
||||
<Label styleClass="text-muted" text="Your balance (milliBTC)" GridPane.rowIndex="4"/>
|
||||
<Label styleClass="text-muted" text="Your balance" GridPane.rowIndex="4"/>
|
||||
<TextField fx:id="amountUs" text="N/A" focusTraversable="false" editable="false"
|
||||
styleClass="noteditable"
|
||||
GridPane.columnIndex="1" GridPane.rowIndex="4"/>
|
||||
|
||||
<Label styleClass="text-muted" text="Capacity (milliBTC)" GridPane.rowIndex="5"/>
|
||||
<Label styleClass="text-muted" text="Capacity" GridPane.rowIndex="5"/>
|
||||
<TextField fx:id="capacity" text="N/A" focusTraversable="false" editable="false"
|
||||
styleClass="noteditable"
|
||||
GridPane.columnIndex="1" GridPane.rowIndex="5"/>
|
||||
|
|
|
@ -50,10 +50,10 @@
|
|||
}
|
||||
|
||||
.channel .grid {
|
||||
-fx-padding: 5px;
|
||||
-fx-vgap: 5px;
|
||||
-fx-padding: 10px;
|
||||
-fx-vgap: 3px;
|
||||
-fx-hgap: 5px;
|
||||
-fx-font-size: 11px;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
||||
.channel-separator {
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
styleClass="noteditable, description-text" text="N/A"
|
||||
prefHeight="80.0" maxHeight="80.0" GridPane.columnIndex="1" GridPane.rowIndex="5"/>
|
||||
|
||||
<Label text="Amount (msat)" GridPane.halignment="RIGHT" GridPane.valignment="BASELINE" GridPane.rowIndex="6"/>
|
||||
<Label fx:id="amountFieldLabel" text="Amount" GridPane.halignment="RIGHT" GridPane.valignment="BASELINE" GridPane.rowIndex="6"/>
|
||||
<VBox GridPane.columnIndex="1" GridPane.rowIndex="6">
|
||||
<children>
|
||||
<TextField fx:id="amountField"/>
|
||||
|
|
|
@ -15,13 +15,13 @@ import fr.acinq.eclair.blockchain.bitcoind.zmq.ZMQActor._
|
|||
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.ElectrumEvent
|
||||
import fr.acinq.eclair.channel.ChannelEvent
|
||||
import fr.acinq.eclair.gui.controllers.{MainController, NotificationsController}
|
||||
import fr.acinq.eclair.gui.utils.{BtcUnit, CoinUnit, CoinUtils}
|
||||
import fr.acinq.eclair.payment.PaymentEvent
|
||||
import fr.acinq.eclair.router.NetworkEvent
|
||||
import grizzled.slf4j.Logging
|
||||
|
||||
import scala.concurrent.Promise
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
|
||||
|
@ -66,6 +66,14 @@ class FxApp extends Application with Logging {
|
|||
val datadir = new File(getParameters.getUnnamed.get(0))
|
||||
implicit val system = ActorSystem("system")
|
||||
val setup = new Setup(datadir)
|
||||
|
||||
val unitConf = setup.config.getString("gui.unit")
|
||||
FxApp.unit = Try(CoinUtils.getUnitFromString(unitConf, accept_msat = false)) match {
|
||||
case Failure(_) => logger.warn(s"$unitConf is not a valid gui unit, must be sat, mbtc or btc. Defaulting to btc.")
|
||||
BtcUnit
|
||||
case Success(u) => u
|
||||
}
|
||||
|
||||
val guiUpdater = setup.system.actorOf(SimpleSupervisor.props(Props(classOf[GUIUpdater], controller), "gui-updater", SupervisorStrategy.Resume))
|
||||
setup.system.eventStream.subscribe(guiUpdater, classOf[ChannelEvent])
|
||||
setup.system.eventStream.subscribe(guiUpdater, classOf[NetworkEvent])
|
||||
|
@ -135,4 +143,9 @@ class FxApp extends Application with Logging {
|
|||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
object FxApp {
|
||||
private var unit: CoinUnit = BtcUnit
|
||||
def getUnit = FxApp.unit
|
||||
}
|
|
@ -52,8 +52,8 @@ class GUIUpdater(mainController: MainController) extends Actor with ActorLogging
|
|||
|
||||
def updateBalance(channelPaneController: ChannelPaneController, commitments: Commitments) = {
|
||||
val spec = commitments.localCommit.spec
|
||||
channelPaneController.capacity.setText(s"${CoinUtils.MILLI_BTC_FORMAT.format(millisatoshi2millibtc(MilliSatoshi(spec.totalFunds)).amount)}")
|
||||
channelPaneController.amountUs.setText(s"${CoinUtils.MILLI_BTC_FORMAT.format(millisatoshi2millibtc(MilliSatoshi(spec.toLocalMsat)).amount)}")
|
||||
channelPaneController.capacity.setText(CoinUtils.formatAmountInUserUnit(MilliSatoshi(spec.totalFunds), withUnit = true))
|
||||
channelPaneController.amountUs.setText(CoinUtils.formatAmountInUserUnit(MilliSatoshi(spec.toLocalMsat), withUnit = true))
|
||||
channelPaneController.balanceBar.setProgress(spec.toLocalMsat.toDouble / spec.totalFunds)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
package fr.acinq.eclair.gui
|
||||
|
||||
import java.io.{File, FileWriter}
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
import akka.pattern.ask
|
||||
import akka.util.Timeout
|
||||
import fr.acinq.bitcoin.MilliSatoshi
|
||||
import fr.acinq.eclair._
|
||||
import fr.acinq.eclair.gui.controllers._
|
||||
import fr.acinq.eclair.gui.utils.CoinUtils
|
||||
import fr.acinq.eclair.io.{NodeURI, Peer}
|
||||
import fr.acinq.eclair.payment._
|
||||
import grizzled.slf4j.Logging
|
||||
|
@ -71,7 +70,7 @@ class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionConte
|
|||
} yield res)
|
||||
.onComplete {
|
||||
case Success(_: PaymentSucceeded) =>
|
||||
val message = s"${NumberFormat.getInstance(Locale.getDefault).format(amountMsat / 1000)} satoshis"
|
||||
val message = CoinUtils.formatAmountInUserUnit(MilliSatoshi(amountMsat), withUnit = true)
|
||||
notification("Payment Sent", message, NOTIFICATION_SUCCESS)
|
||||
case Success(PaymentFailed(_, failures)) =>
|
||||
val message = s"${
|
||||
|
@ -90,7 +89,7 @@ class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionConte
|
|||
|
||||
def receive(amountMsat_opt: Option[MilliSatoshi], description: String): Future[String] = for {
|
||||
kit <- fKit
|
||||
res <- (kit.paymentHandler ? ReceivePayment(amountMsat_opt, description)).mapTo[PaymentRequest].map(PaymentRequest.write(_))
|
||||
res <- (kit.paymentHandler ? ReceivePayment(amountMsat_opt, description)).mapTo[PaymentRequest].map(PaymentRequest.write)
|
||||
} yield res
|
||||
|
||||
|
||||
|
@ -118,6 +117,6 @@ class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionConte
|
|||
* @param showAppName true if you want the notification title to be preceded by "Eclair - ". True by default
|
||||
*/
|
||||
def notification(title: String, message: String, notificationType: NotificationType = NOTIFICATION_NONE, showAppName: Boolean = true) = {
|
||||
notifsController.map(_.addNotification(if (showAppName) s"Eclair - $title" else title, message, notificationType))
|
||||
notifsController.foreach(_.addNotification(if (showAppName) s"Eclair - $title" else title, message, notificationType))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,13 +28,13 @@ class ChannelPaneController(val theirNodeIdValue: String) extends Logging {
|
|||
|
||||
var contextMenu: ContextMenu = _
|
||||
|
||||
private def buildChannelContextMenu = {
|
||||
private def buildChannelContextMenu() = {
|
||||
Platform.runLater(new Runnable() {
|
||||
override def run = {
|
||||
override def run() = {
|
||||
contextMenu = ContextMenuUtils.buildCopyContext(List(
|
||||
new CopyAction("Copy Channel Id", channelId.getText),
|
||||
new CopyAction("Copy Peer Pubkey", theirNodeIdValue),
|
||||
new CopyAction("Copy Tx Id", txId.getText())
|
||||
CopyAction("Copy Channel Id", channelId.getText),
|
||||
CopyAction("Copy Peer Pubkey", theirNodeIdValue),
|
||||
CopyAction("Copy Tx Id", txId.getText())
|
||||
))
|
||||
contextMenu.getStyleClass.add("context-channel")
|
||||
channelId.setContextMenu(contextMenu)
|
||||
|
@ -47,20 +47,20 @@ class ChannelPaneController(val theirNodeIdValue: String) extends Logging {
|
|||
})
|
||||
}
|
||||
|
||||
@FXML def initialize = {
|
||||
@FXML def initialize() = {
|
||||
channelId.textProperty.addListener(new ChangeListener[String] {
|
||||
override def changed(observable: ObservableValue[_ <: String], oldValue: String, newValue: String) = buildChannelContextMenu
|
||||
override def changed(observable: ObservableValue[_ <: String], oldValue: String, newValue: String) = buildChannelContextMenu()
|
||||
})
|
||||
buildChannelContextMenu
|
||||
buildChannelContextMenu()
|
||||
}
|
||||
|
||||
@FXML def openChannelContext(event: ContextMenuEvent) {
|
||||
contextMenu.show(channelId, event.getScreenX, event.getScreenY)
|
||||
event.consume
|
||||
event.consume()
|
||||
}
|
||||
|
||||
@FXML def closeChannelContext(event: MouseEvent) {
|
||||
if (contextMenu != null) contextMenu.hide
|
||||
if (contextMenu != null) contextMenu.hide()
|
||||
}
|
||||
|
||||
def updateRemoteNodeAlias(alias: String) {
|
||||
|
|
|
@ -2,14 +2,13 @@ package fr.acinq.eclair.gui.controllers
|
|||
|
||||
import java.lang.Boolean
|
||||
import javafx.beans.value.{ChangeListener, ObservableValue}
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.event.ActionEvent
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control._
|
||||
import javafx.stage.Stage
|
||||
|
||||
import fr.acinq.eclair.channel.{Channel, ChannelFlags}
|
||||
import fr.acinq.eclair.gui.Handlers
|
||||
import fr.acinq.eclair.gui.{FxApp, Handlers}
|
||||
import fr.acinq.eclair.gui.utils.{CoinUtils, GUIValidators}
|
||||
import fr.acinq.eclair.io.{NodeURI, Peer}
|
||||
import grizzled.slf4j.Logging
|
||||
|
@ -33,8 +32,8 @@ class OpenChannelController(val handlers: Handlers, val stage: Stage) extends Lo
|
|||
@FXML var button: Button = _
|
||||
|
||||
@FXML def initialize = {
|
||||
unit.setItems(FXCollections.observableArrayList(CoinUtils.SATOSHI_LABEL, CoinUtils.MILLI_BTC_LABEL, CoinUtils.BTC_LABEL))
|
||||
unit.setValue(CoinUtils.MILLI_BTC_LABEL)
|
||||
unit.setItems(CoinUtils.FX_UNITS_ARRAY)
|
||||
unit.setValue(FxApp.getUnit.label)
|
||||
|
||||
simpleConnection.selectedProperty.addListener(new ChangeListener[Boolean] {
|
||||
override def changed(observable: ObservableValue[_ <: Boolean], oldValue: Boolean, newValue: Boolean) = {
|
||||
|
@ -96,5 +95,5 @@ class OpenChannelController(val handlers: Handlers, val stage: Stage) extends Lo
|
|||
pushMsatError.setText("")
|
||||
}
|
||||
|
||||
@FXML def handleClose(event: ActionEvent) = stage.close
|
||||
@FXML def handleClose(event: ActionEvent) = stage.close()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package fr.acinq.eclair.gui.controllers
|
||||
|
||||
import javafx.application.Platform
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.event.ActionEvent
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control._
|
||||
|
@ -10,8 +9,8 @@ import javafx.scene.layout.GridPane
|
|||
import javafx.stage.Stage
|
||||
|
||||
import fr.acinq.bitcoin.MilliSatoshi
|
||||
import fr.acinq.eclair.gui.Handlers
|
||||
import fr.acinq.eclair.gui.utils.{CoinUtils, ContextMenuUtils, GUIValidators, QRCodeUtils}
|
||||
import fr.acinq.eclair.gui.{FxApp, Handlers}
|
||||
import fr.acinq.eclair.gui.utils._
|
||||
import fr.acinq.eclair.payment.PaymentRequest
|
||||
import grizzled.slf4j.Logging
|
||||
|
||||
|
@ -35,8 +34,8 @@ class ReceivePaymentController(val handlers: Handlers, val stage: Stage) extends
|
|||
@FXML var paymentRequestQRCode: ImageView = _
|
||||
|
||||
@FXML def initialize = {
|
||||
unit.setItems(FXCollections.observableArrayList(CoinUtils.MILLI_SATOSHI_LABEL, CoinUtils.SATOSHI_LABEL, CoinUtils.MILLI_BTC_LABEL))
|
||||
unit.setValue(CoinUtils.MILLI_BTC_LABEL)
|
||||
unit.setItems(CoinUtils.FX_UNITS_ARRAY)
|
||||
unit.setValue(FxApp.getUnit.label)
|
||||
resultBox.managedProperty().bind(resultBox.visibleProperty())
|
||||
stage.sizeToScene()
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ import javafx.scene.input.KeyCode.{ENTER, TAB}
|
|||
import javafx.scene.input.KeyEvent
|
||||
import javafx.stage.Stage
|
||||
|
||||
import fr.acinq.eclair.gui.Handlers
|
||||
import fr.acinq.eclair.gui.{FxApp, Handlers}
|
||||
import fr.acinq.eclair.gui.utils.CoinUtils
|
||||
import fr.acinq.eclair.payment.PaymentRequest
|
||||
import grizzled.slf4j.Logging
|
||||
|
||||
|
@ -25,22 +26,27 @@ class SendPaymentController(val handlers: Handlers, val stage: Stage) extends Lo
|
|||
@FXML var nodeIdField: TextField = _
|
||||
@FXML var descriptionLabel: Label = _
|
||||
@FXML var descriptionField: TextArea = _
|
||||
@FXML var amountFieldLabel: Label = _
|
||||
@FXML var amountField: TextField = _
|
||||
@FXML var amountFieldError: Label = _
|
||||
@FXML var paymentHashField: TextField = _
|
||||
@FXML var sendButton: Button = _
|
||||
|
||||
@FXML def initialize(): Unit = {
|
||||
|
||||
// set the user preferred unit
|
||||
amountFieldLabel.setText(s"Amount (${FxApp.getUnit.label})")
|
||||
|
||||
// ENTER or TAB events in the paymentRequest textarea instead fire or focus sendButton
|
||||
paymentRequest.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler[KeyEvent] {
|
||||
def handle(event: KeyEvent) = {
|
||||
event.getCode match {
|
||||
case ENTER =>
|
||||
sendButton.fire
|
||||
event.consume
|
||||
sendButton.fire()
|
||||
event.consume()
|
||||
case TAB =>
|
||||
sendButton.requestFocus()
|
||||
event.consume
|
||||
event.consume()
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +81,7 @@ class SendPaymentController(val handlers: Handlers, val stage: Stage) extends Lo
|
|||
}
|
||||
|
||||
private def setUIFields(pr: PaymentRequest) = {
|
||||
pr.amount.foreach(amount => amountField.setText(amount.amount.toString))
|
||||
pr.amount.foreach(amount => amountField.setText(CoinUtils.rawAmountInUserUnit(amount).bigDecimal.toPlainString))
|
||||
pr.description match {
|
||||
case Left(s) => descriptionField.setText(s)
|
||||
case Right(hash) =>
|
||||
|
@ -87,10 +93,10 @@ class SendPaymentController(val handlers: Handlers, val stage: Stage) extends Lo
|
|||
}
|
||||
|
||||
@FXML def handleSend(event: ActionEvent): Unit = {
|
||||
(Try(amountField.getText().toLong), readPaymentRequest()) match {
|
||||
(Try(CoinUtils.convertStringAmountToMsat(amountField.getText(), FxApp.getUnit.code)), readPaymentRequest()) match {
|
||||
case (Success(amountMsat), Success(pr)) =>
|
||||
// we always override the payment request amount with the one from the UI
|
||||
Try(handlers.send(Some(amountMsat), pr)) match {
|
||||
Try(handlers.send(Some(amountMsat.amount), pr)) match {
|
||||
case Success(_) => stage.close()
|
||||
case Failure(f) => paymentRequestError.setText(s"Invalid Payment Request: ${f.getMessage}")
|
||||
}
|
||||
|
|
|
@ -1,26 +1,58 @@
|
|||
package fr.acinq.eclair.gui.utils
|
||||
|
||||
import java.text.DecimalFormat
|
||||
import java.text.{DecimalFormat, NumberFormat}
|
||||
import javafx.collections.FXCollections
|
||||
|
||||
import fr.acinq.bitcoin.{MilliSatoshi, Satoshi}
|
||||
import fr.acinq.bitcoin.{MilliSatoshi, Satoshi, _}
|
||||
import fr.acinq.eclair.gui.FxApp
|
||||
import grizzled.slf4j.Logging
|
||||
import fr.acinq.bitcoin._
|
||||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
sealed trait CoinUnit {
|
||||
def code: String
|
||||
def shortLabel: String
|
||||
def label: String
|
||||
}
|
||||
|
||||
case object MSatUnit extends CoinUnit {
|
||||
override def code: String = "msat"
|
||||
override def shortLabel: String = "mSat"
|
||||
override def label: String = "MilliSatoshi"
|
||||
}
|
||||
|
||||
case object SatUnit extends CoinUnit {
|
||||
override def code: String = "sat"
|
||||
override def shortLabel: String = "sat"
|
||||
override def label: String = "Satoshi"
|
||||
}
|
||||
case object BitsUnit extends CoinUnit {
|
||||
override def code: String = "bits"
|
||||
override def shortLabel: String = "bits"
|
||||
override def label: String = "Bits"
|
||||
}
|
||||
case object MBtcUnit extends CoinUnit {
|
||||
override def code: String = "mbtc"
|
||||
override def shortLabel: String = "mBTC"
|
||||
override def label: String = "MilliBitcoin"
|
||||
}
|
||||
|
||||
case object BtcUnit extends CoinUnit {
|
||||
override def code: String = "btc"
|
||||
override def shortLabel: String = "BTC"
|
||||
override def label: String = "Bitcoin"
|
||||
}
|
||||
|
||||
object CoinUtils extends Logging {
|
||||
/**
|
||||
* Always 5 decimals
|
||||
*/
|
||||
val MILLI_BTC_PATTERN = "###,##0.00000"
|
||||
|
||||
/**
|
||||
* Localized formatter for milli-bitcoin amounts. Uses `MILLI_BTC_PATTERN`.
|
||||
*/
|
||||
val MILLI_BTC_PATTERN = "###,##0.00######"
|
||||
val BTC_PATTERN = "###,##0.000########"
|
||||
|
||||
val SATOSHI_FORMAT = NumberFormat.getInstance()
|
||||
val MILLI_BTC_FORMAT: DecimalFormat = new DecimalFormat(MILLI_BTC_PATTERN)
|
||||
val BTC_FORMAT: DecimalFormat = new DecimalFormat(BTC_PATTERN)
|
||||
|
||||
val MILLI_SATOSHI_LABEL = "milliSatoshi"
|
||||
val SATOSHI_LABEL = "satoshi"
|
||||
val MILLI_BTC_LABEL = "milliBTC"
|
||||
val BTC_LABEL = "BTC"
|
||||
val FX_UNITS_ARRAY = FXCollections.observableArrayList(SatUnit.label, MBtcUnit.label, BtcUnit.label)
|
||||
|
||||
/**
|
||||
* Converts a string amount denominated in a bitcoin unit to a Millisatoshi amount. The amount might be truncated if
|
||||
|
@ -41,15 +73,106 @@ object CoinUtils extends Logging {
|
|||
}
|
||||
logger.debug(s"amount=$amountDecimal with unit=$unit")
|
||||
// note: we can't use the fr.acinq.bitcoin._ conversion methods because they truncate the sub-satoshi part
|
||||
unit match {
|
||||
case MILLI_SATOSHI_LABEL => MilliSatoshi(amountDecimal.longValue())
|
||||
case SATOSHI_LABEL => MilliSatoshi((amountDecimal * 1000).longValue())
|
||||
case MILLI_BTC_LABEL => MilliSatoshi((amountDecimal * 1000 * 100000).longValue())
|
||||
case BTC_LABEL => MilliSatoshi((amountDecimal * 1000 * 100000 * 1000).longValue())
|
||||
case _ => throw new IllegalArgumentException("unknown unit")
|
||||
getUnitFromString(unit) match {
|
||||
case MSatUnit => MilliSatoshi(amountDecimal.longValue())
|
||||
case SatUnit => MilliSatoshi((amountDecimal * 1000).longValue())
|
||||
case MBtcUnit => MilliSatoshi((amountDecimal * 1000 * 100000).longValue())
|
||||
case BtcUnit => MilliSatoshi((amountDecimal * 1000 * 100000 * 1000).longValue())
|
||||
case _ => throw new IllegalArgumentException("unhandled unit")
|
||||
}
|
||||
}
|
||||
|
||||
def convertStringAmountToSat(amount: String, unit: String): Satoshi =
|
||||
millisatoshi2satoshi(CoinUtils.convertStringAmountToMsat(amount, unit))
|
||||
|
||||
/**
|
||||
* Only BtcUnit, MBtcUnit and SatUnit are supported.
|
||||
* @param unit
|
||||
* @return
|
||||
*/
|
||||
def getUnitFromString(unit: String, accept_msat: Boolean = true): CoinUnit = unit.toLowerCase() match {
|
||||
case u if accept_msat && (u == MSatUnit.code || u == MSatUnit.label.toLowerCase()) => MSatUnit
|
||||
case u if u == SatUnit.code || u == SatUnit.label.toLowerCase() => SatUnit
|
||||
case u if u == MBtcUnit.code || u == MBtcUnit.label.toLowerCase() => MBtcUnit
|
||||
case u if u == BtcUnit.code.toLowerCase() || u == BtcUnit.label => BtcUnit
|
||||
case u => throw new IllegalArgumentException(s"unhandled unit=$u")
|
||||
}
|
||||
|
||||
def convertAmountToUserUnit(amount: BtcAmount): BtcAmount = {
|
||||
val unit = FxApp.getUnit
|
||||
(amount, unit) match {
|
||||
|
||||
// amount is msat
|
||||
case (a: MilliSatoshi, MSatUnit) => a
|
||||
case (a: MilliSatoshi, SatUnit) => millisatoshi2satoshi(a)
|
||||
case (a: MilliSatoshi, MBtcUnit) => millisatoshi2millibtc(a)
|
||||
case (a: MilliSatoshi, BtcUnit) => millisatoshi2btc(a)
|
||||
|
||||
// amount is satoshi
|
||||
case (a: Satoshi, MSatUnit) => satoshi2millisatoshi(a)
|
||||
case (a: Satoshi, SatUnit) => a
|
||||
case (a: Satoshi, MBtcUnit) => satoshi2millibtc(a)
|
||||
case (a: Satoshi, BtcUnit) => satoshi2btc(a)
|
||||
|
||||
// amount is mbtc
|
||||
case (a: MilliBtc, MSatUnit) => millibtc2millisatoshi(a)
|
||||
case (a: MilliBtc, SatUnit) => millibtc2satoshi(a)
|
||||
case (a: MilliBtc, MBtcUnit) => a
|
||||
case (a: MilliBtc, BtcUnit) => millibtc2btc(a)
|
||||
|
||||
// amount is mbtc
|
||||
case (a: Btc, MSatUnit) => btc2millisatoshi(a)
|
||||
case (a: Btc, SatUnit) => btc2satoshi(a)
|
||||
case (a: Btc, MBtcUnit) => btc2millibtc(a)
|
||||
case (a: Btc, BtcUnit) => a
|
||||
|
||||
case (a, _) =>
|
||||
throw new IllegalArgumentException(s"unhandled conversion from $amount to $unit")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the amount to the user preferred unit and returns a localized formatted String.
|
||||
* This method is useful for read only displays.
|
||||
*
|
||||
* @param amount BtcAmount
|
||||
* @param withUnit if true, append the user unit shortLabel (mBTC, BTC, mSat...)
|
||||
* @return formatted amount
|
||||
*/
|
||||
def formatAmountInUserUnit(amount: BtcAmount, withUnit: Boolean = false): String = {
|
||||
Try(convertAmountToUserUnit(amount) match {
|
||||
case a: MilliSatoshi => SATOSHI_FORMAT.format(a.amount)
|
||||
case a: Satoshi => SATOSHI_FORMAT.format(a.amount)
|
||||
case a: MilliBtc => MILLI_BTC_FORMAT.format(a.amount)
|
||||
case a: Btc => BTC_FORMAT.format(a.amount)
|
||||
case a => throw new IllegalArgumentException(s"unhandled unit $a")
|
||||
}) match {
|
||||
case Success(s) => if (withUnit) s"$s ${FxApp.getUnit.shortLabel}" else s
|
||||
case Failure(t) => logger.error("can not convert amount to user unit", t)
|
||||
amount.toString
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the amount to the user preferred unit and returns the Long value.
|
||||
* This method is useful to feed numeric text input without formatting.
|
||||
*
|
||||
* Returns -1 if the given amount can not be converted.
|
||||
*
|
||||
* @param amount BtcAmount
|
||||
* @return Long value of the BtcAmount
|
||||
*/
|
||||
def rawAmountInUserUnit(amount: BtcAmount): BigDecimal = {
|
||||
Try(convertAmountToUserUnit(amount) match {
|
||||
case a: MilliSatoshi => BigDecimal(a.amount)
|
||||
case a: Satoshi => BigDecimal(a.amount)
|
||||
case a: MilliBtc => a.amount
|
||||
case a: Btc => a.amount
|
||||
case a => throw new IllegalArgumentException(s"unhandled unit $a")
|
||||
}) match {
|
||||
case Success(l) => l
|
||||
case Failure(t) => logger.error("can not convert amount to user unit", t)
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,28 +9,28 @@ import org.scalatest.junit.JUnitRunner
|
|||
class CoinUtilsSpec extends FunSuite {
|
||||
|
||||
test("Convert string amount to the correct BtcAmount") {
|
||||
val am_btc: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", CoinUtils.BTC_LABEL)
|
||||
val am_btc: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", BtcUnit.code)
|
||||
assert(am_btc == MilliSatoshi(100000000000L))
|
||||
val am_mbtc: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", CoinUtils.MILLI_BTC_LABEL)
|
||||
val am_mbtc: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", MBtcUnit.code)
|
||||
assert(am_mbtc == MilliSatoshi(100000000L))
|
||||
val am_sat: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", CoinUtils.SATOSHI_LABEL)
|
||||
val am_sat: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", SatUnit.code)
|
||||
assert(am_sat == MilliSatoshi(1000))
|
||||
val am_msat: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", CoinUtils.MILLI_SATOSHI_LABEL)
|
||||
val am_msat: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", MSatUnit.code)
|
||||
assert(am_msat == MilliSatoshi(1))
|
||||
val am_zero: MilliSatoshi = CoinUtils.convertStringAmountToMsat("0", CoinUtils.MILLI_BTC_LABEL)
|
||||
val am_zero: MilliSatoshi = CoinUtils.convertStringAmountToMsat("0", MBtcUnit.code)
|
||||
assert(am_zero == MilliSatoshi(0))
|
||||
}
|
||||
|
||||
test("Convert decimal string amount to the correct BtcAmount") {
|
||||
val am_btc_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789876", CoinUtils.BTC_LABEL)
|
||||
val am_btc_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789876", BtcUnit.code)
|
||||
assert(am_btc_dec == MilliSatoshi(123456789876L))
|
||||
val am_mbtc_dec_nozero: MilliSatoshi = CoinUtils.convertStringAmountToMsat(".25", CoinUtils.MILLI_BTC_LABEL)
|
||||
val am_mbtc_dec_nozero: MilliSatoshi = CoinUtils.convertStringAmountToMsat(".25", MBtcUnit.code)
|
||||
assert(am_mbtc_dec_nozero == MilliSatoshi(25000000L))
|
||||
val am_mbtc_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789", CoinUtils.MILLI_BTC_LABEL)
|
||||
val am_mbtc_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789", MBtcUnit.code)
|
||||
assert(am_mbtc_dec == MilliSatoshi(123456789L))
|
||||
val am_sat_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789", CoinUtils.SATOSHI_LABEL)
|
||||
val am_sat_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789", SatUnit.code)
|
||||
assert(am_sat_dec == MilliSatoshi(1234))
|
||||
val am_msat_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.234", CoinUtils.MILLI_SATOSHI_LABEL)
|
||||
val am_msat_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.234", MSatUnit.code)
|
||||
assert(am_msat_dec == MilliSatoshi(1))
|
||||
}
|
||||
|
||||
|
@ -43,20 +43,21 @@ class CoinUtilsSpec extends FunSuite {
|
|||
}
|
||||
|
||||
test("Convert string amount with a non numerical amount") {
|
||||
intercept[NumberFormatException](CoinUtils.convertStringAmountToMsat("1.abcd", CoinUtils.MILLI_BTC_LABEL))
|
||||
intercept[NumberFormatException](CoinUtils.convertStringAmountToMsat("1.abcd", MBtcUnit.code))
|
||||
}
|
||||
|
||||
test("Convert string amount with an empty amount") {
|
||||
intercept[NumberFormatException](CoinUtils.convertStringAmountToMsat("", CoinUtils.MILLI_BTC_LABEL))
|
||||
intercept[NumberFormatException](CoinUtils.convertStringAmountToMsat("", MBtcUnit.code))
|
||||
}
|
||||
|
||||
test("Convert string amount with a invalid numerical amount") {
|
||||
intercept[NumberFormatException](CoinUtils.convertStringAmountToMsat("1.23.45", CoinUtils.MILLI_BTC_LABEL))
|
||||
intercept[NumberFormatException](CoinUtils.convertStringAmountToMsat("1.23.45", MBtcUnit.code))
|
||||
}
|
||||
|
||||
test("Convert string amount with a negative numerical amount") {
|
||||
intercept[IllegalArgumentException](CoinUtils.convertStringAmountToMsat("-1", CoinUtils.MILLI_BTC_LABEL))
|
||||
intercept[IllegalArgumentException](CoinUtils.convertStringAmountToMsat("-1", MBtcUnit.code))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
<appender-ref ref="CYAN"/>
|
||||
</logger>
|
||||
|
||||
<logger name="fr.acinq.eclair.gui" level="ERROR" additivity="false">
|
||||
<logger name="fr.acinq.eclair.gui" level="WARN" additivity="false">
|
||||
<appender-ref ref="MAGENTA"/>
|
||||
</logger>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue