Skip to content

Commit 0253c59

Browse files
committed
qt: show sat/sats balance with pluralization in wallet badge
WalletBadge previously showed "s" as a fixed satoshi unit symbol. Add a balanceSatoshi property (typed as qint64 via Q_PROPERTY so the full 64-bit value reaches QML) and wire it from DesktopWallets so the badge can pluralize correctly: "sat" for exactly 1 satoshi, "sats" otherwise. The balance is also moved to a suffix position to match standard display conventions. Add displayUnitLabelForAmount() to OptionsQmlModel as a testable helper for the pluralization logic, and update the QML unit tests accordingly.
1 parent 649c4a2 commit 0253c59

8 files changed

Lines changed: 53 additions & 40 deletions

File tree

qml/models/options_model.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,12 @@ QString OptionsQmlModel::displayUnitLabel() const
283283
return (m_display_unit == 1) ? QStringLiteral("sat") : QStringLiteral("BTC");
284284
}
285285

286+
QString OptionsQmlModel::displayUnitLabelForAmount(qint64 satoshi) const
287+
{
288+
if (m_display_unit != 1) return QStringLiteral("");
289+
return (qAbs(satoshi) == 1) ? QStringLiteral("sat") : QStringLiteral("sats");
290+
}
291+
286292

287293
void OptionsQmlModel::onboard()
288294
{

qml/models/options_model.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class OptionsQmlModel : public QObject
8282
int displayUnit() const { return m_display_unit; }
8383
void setDisplayUnit(int new_display_unit);
8484
QString displayUnitLabel() const;
85+
Q_INVOKABLE QString displayUnitLabelForAmount(qint64 satoshi) const;
8586

8687
public Q_SLOTS:
8788
void setCustomDataDirString(const QString &new_custom_datadir_string) {

qml/models/walletqmlmodel.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ QString WalletQmlModel::balance() const
101101
return QmlBitcoinUnits::format(unit, m_wallet->getBalance());
102102
}
103103

104-
CAmount WalletQmlModel::balanceSatoshi() const
104+
qint64 WalletQmlModel::balanceSatoshi() const
105105
{
106106
if (!m_wallet) {
107107
return 0;

qml/models/walletqmlmodel.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class WalletQmlModel : public QObject
2727
Q_OBJECT
2828
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
2929
Q_PROPERTY(QString balance READ balance NOTIFY balanceChanged)
30+
Q_PROPERTY(qint64 balanceSatoshi READ balanceSatoshi NOTIFY balanceChanged)
3031
Q_PROPERTY(ActivityListModel* activityListModel READ activityListModel CONSTANT)
3132
Q_PROPERTY(CoinsListModel* coinsListModel READ coinsListModel CONSTANT)
3233
Q_PROPERTY(SendRecipientsListModel* recipients READ sendRecipientList CONSTANT)
@@ -43,7 +44,7 @@ class WalletQmlModel : public QObject
4344

4445
QString name() const;
4546
QString balance() const;
46-
CAmount balanceSatoshi() const;
47+
qint64 balanceSatoshi() const;
4748
Q_INVOKABLE void commitPaymentRequest();
4849

4950
ActivityListModel* activityListModel() const { return m_activity_list_model; }

qml/pages/wallet/DesktopWallets.qml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Page {
3737
implicitHeight: 46
3838
text: walletController.selectedWallet.name
3939
balance: walletController.selectedWallet.balance
40+
balanceSatoshi: walletController.selectedWallet.balanceSatoshi
4041
loading: !walletController.initialized
4142
noWalletLoaded: !walletController.isWalletLoaded
4243
noWalletsFound: walletController.noWalletsFound

qml/pages/wallet/WalletBadge.qml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Button {
2222
property bool showBalance: true
2323
property bool showIcon: true
2424
property string balance: "0.0 000 000"
25+
property var balanceSatoshi: 0
2526
property bool loading: false
2627
property bool noWalletLoaded: false
2728
property bool noWalletsFound: false
@@ -143,7 +144,7 @@ Button {
143144
CoreText {
144145
id: balanceText
145146
visible: root.showBalance
146-
text: (optionsModel.displayUnit === 1 ? "s" : "") + " " + root.balance
147+
text: root.balance + " " + (optionsModel.displayUnit === 1 ? (root.balanceSatoshi === 1 ? qsTr("sat") : qsTr("sats")) : "")
147148
color: Theme.color.neutral7
148149
}
149150
}

test/qml/qml_tests_main.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ class MockOptionsModel : public QObject
2525
if (u != m_displayUnit) { m_displayUnit = u; Q_EMIT displayUnitChanged(u); }
2626
}
2727
QString displayUnitLabel() const { return m_displayUnit == 1 ? "sat" : "BTC"; }
28+
Q_INVOKABLE QString displayUnitLabelForAmount(qint64 satoshi) const {
29+
if (m_displayUnit != 1) return QString("");
30+
return (qAbs(satoshi) == 1) ? QString("sat") : QString("sats");
31+
}
2832
QString language() const { return m_language; }
2933
void setLanguage(const QString& l) {
3034
if (l != m_language) { m_language = l; Q_EMIT languageChanged(); }

test/qml/tst_displaysettings.qml

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -97,61 +97,60 @@ TestCase {
9797
compare(optionsModel.displayUnit, 0)
9898
}
9999

100-
// Mirrors the balance prefix expression in WalletBadge.qml.
100+
// Mirrors the balance suffix expression in WalletBadge.qml.
101+
// balanceSatoshi=1000 → plural "sats"; balanceSatoshi=1 → singular "sat".
101102
Component {
102-
id: balancePrefixComponent
103+
id: balanceSuffixComponent
103104
Text {
104105
property string balance: "1 000"
105-
text: (optionsModel.displayUnit === 1 ? "s" : "") + " " + balance
106+
property var balanceSatoshi: 1000
107+
text: balance + " " + (optionsModel.displayUnit === 1 ? (balanceSatoshi === 1 ? "sat" : "sats") : "")
106108
}
107109
}
108110

109-
function test_walletBadge_prefix_is_s_in_sat_mode() {
111+
function test_walletBadge_suffix_is_sats_in_sat_mode() {
110112
optionsModel.displayUnit = 1
111-
const obj = createTemporaryObject(balancePrefixComponent, this)
113+
const obj = createTemporaryObject(balanceSuffixComponent, this)
112114
verify(obj !== null)
113-
compare(obj.text, "s 1 000")
115+
compare(obj.text, "1 000 sats")
114116
optionsModel.displayUnit = 0
115117
}
116118

117-
function test_walletBadge_prefix_is_btc_symbol_in_btc_mode() {
119+
function test_walletBadge_suffix_is_sat_singular_in_sat_mode() {
120+
optionsModel.displayUnit = 1
121+
const obj = createTemporaryObject(balanceSuffixComponent, this)
122+
verify(obj !== null)
123+
obj.balanceSatoshi = 1
124+
compare(obj.text, "1 000 sat")
118125
optionsModel.displayUnit = 0
119-
const obj = createTemporaryObject(balancePrefixComponent, this)
126+
}
127+
128+
function test_walletBadge_suffix_is_btc_symbol_in_btc_mode() {
129+
optionsModel.displayUnit = 0
130+
const obj = createTemporaryObject(balanceSuffixComponent, this)
120131
verify(obj !== null)
121-
compare(obj.text, "1 000")
132+
compare(obj.text, "1 000")
122133
}
123134

124-
// Mirrors the Loader + Component pattern in BitcoinAmountInputField.qml,
125-
// Send.qml, and RequestPayment.qml.
126-
Component {
127-
id: unitLabelComponent
128-
Item {
129-
id: wrapper
130-
property int unit: 0
131-
Loader {
132-
objectName: "unitLabelLoader"
133-
sourceComponent: wrapper.unit === 1 ? satComponent : btcComponent
134-
}
135-
Component { id: btcComponent; Text { text: "" } }
136-
Component { id: satComponent; Text { text: "s" } }
137-
}
135+
// Tests displayUnitLabelForAmount pluralization logic.
136+
function test_displayUnitLabelForAmount_singular_in_sat_mode() {
137+
optionsModel.displayUnit = 1
138+
compare(optionsModel.displayUnitLabelForAmount(1), "sat")
139+
compare(optionsModel.displayUnitLabelForAmount(-1), "sat")
140+
optionsModel.displayUnit = 0
138141
}
139142

140-
function test_unitLabel_shows_s_in_sat_mode() {
141-
const obj = createTemporaryObject(unitLabelComponent, this)
142-
obj.unit = 1
143-
waitForRendering(obj)
144-
const loader = findChild(obj, "unitLabelLoader")
145-
verify(loader.item !== null)
146-
compare(loader.item.text, "s")
143+
function test_displayUnitLabelForAmount_plural_in_sat_mode() {
144+
optionsModel.displayUnit = 1
145+
compare(optionsModel.displayUnitLabelForAmount(0), "sats")
146+
compare(optionsModel.displayUnitLabelForAmount(2), "sats")
147+
compare(optionsModel.displayUnitLabelForAmount(1000), "sats")
148+
optionsModel.displayUnit = 0
147149
}
148150

149-
function test_unitLabel_shows_btc_symbol_in_btc_mode() {
150-
const obj = createTemporaryObject(unitLabelComponent, this)
151-
obj.unit = 0
152-
waitForRendering(obj)
153-
const loader = findChild(obj, "unitLabelLoader")
154-
verify(loader.item !== null)
155-
compare(loader.item.text, "")
151+
function test_displayUnitLabelForAmount_btc_symbol_in_btc_mode() {
152+
optionsModel.displayUnit = 0
153+
compare(optionsModel.displayUnitLabelForAmount(1), "")
154+
compare(optionsModel.displayUnitLabelForAmount(1000), "")
156155
}
157156
}

0 commit comments

Comments
 (0)