From 046279023afd5d8093ffd5c0b385765aa6bfdc36 Mon Sep 17 00:00:00 2001 From: Mateusz Kudela Date: Mon, 1 Dec 2025 12:16:18 +0000 Subject: [PATCH 1/4] Partly move humanize_hive_power functionality to humanize_asset --- clive/__private/core/formatters/humanize.py | 56 ++++++++++++--------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/clive/__private/core/formatters/humanize.py b/clive/__private/core/formatters/humanize.py index 8b7e2578c2..eff97afa52 100644 --- a/clive/__private/core/formatters/humanize.py +++ b/clive/__private/core/formatters/humanize.py @@ -322,22 +322,9 @@ def humanize_hive_power(value: Asset.Hive, *, use_short_form: bool = True, show_ Returns: A human-readable data representing the hive power. """ - symbol = "HP" if show_symbol else "" - - if not use_short_form: - return f"{value.pretty_amount()} {symbol}".rstrip() - - formatted_string = humanize.naturalsize(value.pretty_amount(), binary=False) - - if "Byte" in formatted_string: - return f"{value.pretty_amount()} {symbol}".rstrip() - - format_fix_regex = re.compile(r"(\d+\.\d*) (.)B") - matched = format_fix_regex.match(formatted_string) - assert matched is not None, "Given string does not match regex" - new_value = matched[1] - unit = matched[2] - return f"{new_value}{unit} {symbol}".upper().rstrip() + hive_symbol = Asset.get_symbol(Asset.Hive) + result = humanize_asset(value, show_symbol=show_symbol, use_short_form=use_short_form) + return result.replace(hive_symbol, "HP") def humanize_hive_power_with_comma(hive_power: Asset.Hive, *, show_symbol: bool = True) -> str: @@ -649,7 +636,13 @@ def humanize_votes_with_comma(votes: int, data: TotalVestingProtocol) -> str: return f"{humanize.intcomma(hive_power.as_float(), ndigits=Asset.get_precision(Asset.Hive))} HP" -def humanize_asset(asset: Asset.AnyT, *, show_symbol: bool = True, sign_prefix: SignPrefixT = "") -> str: +def humanize_asset( + asset: Asset.AnyT, + *, + show_symbol: bool = True, + sign_prefix: SignPrefixT = "", + use_short_form: bool = False, +) -> str: """ Generate pretty formatted asset. @@ -657,6 +650,7 @@ def humanize_asset(asset: Asset.AnyT, *, show_symbol: bool = True, sign_prefix: asset: An asset to be formatted. show_symbol: Whether to show the asset symbol. sign_prefix: A prefix to be added to the asset amount. + use_short_form: Whether to use short form for the asset amount. Example: >>> asset = Asset.Hive(1035.401) @@ -670,12 +664,28 @@ def humanize_asset(asset: Asset.AnyT, *, show_symbol: bool = True, sign_prefix: Returns: A human-readable data representing the asset amount with an optional symbol. """ - pretty_asset = Asset.pretty_amount(asset) - asset_symbol = Asset.get_symbol(asset) - if sign_prefix and int(asset.amount) != 0: - # To not allow display + or - if balance is equal to zero. - return f"{sign_prefix}{pretty_asset} {asset_symbol if show_symbol else ''}".rstrip() - return f"{pretty_asset} {asset_symbol if show_symbol else ''}".rstrip() + + def apply_prefix(amount_str: str) -> str: + return f"{sign_prefix}{amount_str}" if sign_prefix and int(asset.amount) != 0 else amount_str + + pretty_amount = asset.pretty_amount() + asset_symbol = Asset.get_symbol(asset) if show_symbol else "" + + if not use_short_form: + return f"{apply_prefix(pretty_amount)} {asset_symbol}".rstrip() + + formatted_amount = humanize.naturalsize(pretty_amount, binary=False) + + if "Byte" in formatted_amount: + return f"{apply_prefix(pretty_amount)} {asset_symbol}".rstrip() + + format_fix_regex = re.compile(r"(\d+\.\d*) (.)B") + matched = format_fix_regex.match(formatted_amount) + assert matched is not None, "Given string does not match regex" + new_amount = matched[1] + unit = matched[2] + + return f"{apply_prefix(new_amount)}{unit} {asset_symbol}".upper().rstrip() def humanize_bool(value: bool) -> str: # noqa: FBT001 -- GitLab From 65d613e4b0616b66050e03589b2b19f4a7e80468 Mon Sep 17 00:00:00 2001 From: Mateusz Kudela Date: Fri, 5 Dec 2025 13:04:29 +0000 Subject: [PATCH 2/4] Add ensure_liquid_type and ensure_voting_type to Asset --- clive/__private/models/asset.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/clive/__private/models/asset.py b/clive/__private/models/asset.py index 45fb71badd..8c34acbba9 100644 --- a/clive/__private/models/asset.py +++ b/clive/__private/models/asset.py @@ -3,7 +3,7 @@ from __future__ import annotations import re from collections.abc import Callable from dataclasses import dataclass -from typing import TYPE_CHECKING, Generic, TypeAlias, TypeVar +from typing import TYPE_CHECKING, Final, Generic, TypeAlias, TypeVar, cast, get_args from clive.__private.core.decimal_conventer import ( DecimalConversionNotANumberError, @@ -78,6 +78,9 @@ class Asset: VotingT: TypeAlias = Hive | Vests # noqa: UP040 # used in isinstance check AnyT: TypeAlias = Hive | Hbd | Vests # noqa: UP040 # used in isinstance check + LIQUID_TYPES: Final[tuple[type[LiquidT]]] = get_args(LiquidT) + VOTING_TYPES: Final[tuple[type[VotingT]]] = get_args(VotingT) + @classmethod def hive(cls, amount: AssetAmount) -> Asset.Hive: """ @@ -126,6 +129,16 @@ class Asset: """ return cls.__create(Asset.Vests, amount) + @classmethod + def ensure_liquid_type(cls, asset_type: type[Asset.AnyT]) -> type[Asset.LiquidT]: + assert asset_type in cls.LIQUID_TYPES, f"Asset is not liquid type, given type is {asset_type}" + return cast("type[Asset.LiquidT]", asset_type) + + @classmethod + def ensure_voting_type(cls, asset_type: type[Asset.AnyT]) -> type[Asset.VotingT]: + assert asset_type in cls.VOTING_TYPES, f"Asset is not voting type, given type is {asset_type}" + return cast("type[Asset.VotingT]", asset_type) + @classmethod def __create(cls, asset: type[AssetExplicitT], amount: AssetAmount) -> AssetExplicitT: """ -- GitLab From d2e38314ce2304e0d9e52c7a4cf060f49d2d3bcc Mon Sep 17 00:00:00 2001 From: Mateusz Kudela Date: Fri, 5 Dec 2025 14:03:38 +0000 Subject: [PATCH 3/4] Improve is_hive, is_hbd and is_vests methods in Asset --- clive/__private/models/asset.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/clive/__private/models/asset.py b/clive/__private/models/asset.py index 8c34acbba9..7cdb2495d5 100644 --- a/clive/__private/models/asset.py +++ b/clive/__private/models/asset.py @@ -249,12 +249,21 @@ class Asset: @staticmethod def is_hive(asset: type[Asset.AnyT] | Asset.AnyT) -> bool: + if isinstance(asset, type): + return issubclass(asset, Asset.Hive) + return isinstance(asset, Asset.Hive) @staticmethod def is_hbd(asset: type[Asset.AnyT] | Asset.AnyT) -> bool: + if isinstance(asset, type): + return issubclass(asset, Asset.Hbd) + return isinstance(asset, Asset.Hbd) @staticmethod def is_vests(asset: type[Asset.AnyT] | Asset.AnyT) -> bool: + if isinstance(asset, type): + return issubclass(asset, Asset.Vests) + return isinstance(asset, Asset.Vests) -- GitLab From 1680577021f4ffed37d26108d9143c735f937389 Mon Sep 17 00:00:00 2001 From: Mateusz Kudela Date: Wed, 3 Dec 2025 09:56:36 +0000 Subject: [PATCH 4/4] Rearrange account balance view on dashboard, add owned HP Also add some new properties --- .../ui/screens/dashboard/dashboard.py | 73 +++++++++++++------ .../ui/screens/dashboard/dashboard.scss | 17 ++--- 2 files changed, 59 insertions(+), 31 deletions(-) diff --git a/clive/__private/ui/screens/dashboard/dashboard.py b/clive/__private/ui/screens/dashboard/dashboard.py index 2f73ed939c..55ff6b1cc1 100644 --- a/clive/__private/ui/screens/dashboard/dashboard.py +++ b/clive/__private/ui/screens/dashboard/dashboard.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING, Literal, cast from textual import on from textual.binding import Binding @@ -12,6 +12,7 @@ from textual.widgets import Label, Rule, Static from clive.__private.core.accounts.accounts import TrackedAccount, WorkingAccount from clive.__private.core.formatters.data_labels import MISSING_API_LABEL from clive.__private.core.formatters.humanize import ( + humanize_asset, humanize_datetime, humanize_hive_power, humanize_manabar_regeneration_time, @@ -27,12 +28,12 @@ from clive.__private.ui.get_css import get_relative_css_path from clive.__private.ui.screens.account_details.account_details import AccountDetails from clive.__private.ui.screens.base_screen import BaseScreen from clive.__private.ui.screens.operations import Operations, Savings +from clive.__private.ui.screens.operations.hive_power_management.hive_power_management import HivePowerManagement from clive.__private.ui.widgets.alarm_display import AlarmDisplay from clive.__private.ui.widgets.buttons import OneLineButton, OneLineButtonUnfocusable from clive.__private.ui.widgets.dynamic_widgets.dynamic_one_line_button import ( DynamicOneLineButtonUnfocusable, ) -from clive.__private.ui.widgets.ellipsed_static import EllipsedStatic from clive.__private.ui.widgets.no_content_available import NoContentAvailable from clive.__private.ui.widgets.tracked_account_referencing_widget import TrackedAccountReferencingWidget @@ -130,8 +131,8 @@ class BalanceStatsButton(DynamicOneLineButtonUnfocusable): def __init__( self, account: TrackedAccount, - balance_type: Literal["liquid", "savings"], - asset_type: type[Asset.LiquidT], + balance_type: Literal["liquid", "savings", "stake"], + asset_type: type[Asset.AnyT], classes: str | None = None, variant: CliveButtonVariant = "grey-lighten", ) -> None: @@ -148,37 +149,65 @@ class BalanceStatsButton(DynamicOneLineButtonUnfocusable): self._asset_type = asset_type self._widget.add_class("balance-button") - if balance_type == "savings": + if self.is_savings: self.tooltip = f"Perform transfer from savings as {self._account.name}" - else: + elif self.is_liquid: self.tooltip = f"Choose liquid operation to perform as {self._account.name}" + elif self.is_stake: + self.tooltip = f"Choose stake operation to perform as {self._account.name}" + + @property + def is_liquid(self) -> bool: + return self._balance_type == "liquid" + + @property + def is_savings(self) -> bool: + return self._balance_type == "savings" + + @property + def is_stake(self) -> bool: + return self._balance_type == "stake" + + @property + def is_staking_hive_power(self) -> bool: + return self.is_stake and Asset.is_hive(self._asset_type) def _update_asset_value(self) -> str: asset_value = self._get_account_asset_value() - return Asset.pretty_amount(asset_value) - def _get_account_asset_value(self) -> Asset.LiquidT: - asset_symbol = Asset.get_symbol(self._asset_type).lower() - asset_name = f"{asset_symbol}_{self._balance_type}" + if self.is_staking_hive_power: + return humanize_hive_power(cast("Asset.Hive", asset_value)) + return humanize_asset(asset_value, use_short_form=self.is_stake) - asset_name_to_value: dict[str, Asset.LiquidT] = { + def _get_account_asset_value(self) -> Asset.AnyT: + asset_name_to_value: dict[str, Asset.AnyT] = { "hive_liquid": self._account.data.hive_balance, "hive_savings": self._account.data.hive_savings, "hbd_liquid": self._account.data.hbd_balance, "hbd_savings": self._account.data.hbd_savings, + "hive_stake": self._account.data.owned_hp_balance.hp_balance, + "vests_stake": self._account.data.owned_hp_balance.vests_balance, } - + asset_symbol = Asset.get_symbol(self._asset_type) + asset_name = f"{asset_symbol.lower()}_{self._balance_type}" return asset_name_to_value[asset_name] @CliveScreen.prevent_action_when_no_accounts_node_data() @on(OneLineButton.Pressed, ".balance-button") def push_balance_screen(self) -> None: - if self._balance_type == "liquid": - self.app.push_screen(LiquidNavigationDialog(self._account, asset_type=self._asset_type)) + if self.is_liquid: + self.app.push_screen( + LiquidNavigationDialog(self._account, asset_type=Asset.ensure_liquid_type(self._asset_type)) + ) return - auto_switch_working_account(self, self._account) - self.app.push_screen(Savings("transfer-tab", "from-savings", self._asset_type)) + if self.is_savings: + auto_switch_working_account(self, self._account) + self.app.push_screen(Savings("transfer-tab", "from-savings", Asset.ensure_liquid_type(self._asset_type))) + + elif self.is_stake: + auto_switch_working_account(self, self._account) + self.app.push_screen(HivePowerManagement()) class RemoveTrackedAccountButton(OneLineButtonUnfocusable, CliveWidget): @@ -206,15 +235,15 @@ class RemoveTrackedAccountButton(OneLineButtonUnfocusable, CliveWidget): class BalanceStats(TrackedAccountReferencingWidget): def compose(self) -> ComposeResult: - yield Static("", classes="empty") - yield EllipsedStatic("LIQUID", classes="title") - yield EllipsedStatic("SAVINGS", classes="title") - yield Static("HIVE", classes="token-hive") + yield Static("LIQUID", classes="liquid title") yield BalanceStatsButton(self._account, "liquid", Asset.Hive) - yield BalanceStatsButton(self._account, "savings", Asset.Hive, variant="grey-darken") - yield Static("HBD", classes="token-hbd") yield BalanceStatsButton(self._account, "liquid", Asset.Hbd, variant="grey-darken") + yield Static("SAVINGS", classes="savings title") + yield BalanceStatsButton(self._account, "savings", Asset.Hive, variant="grey-darken") yield BalanceStatsButton(self._account, "savings", Asset.Hbd) + yield Static("STAKE", classes="stake title") + yield BalanceStatsButton(self._account, "stake", Asset.Hive) + yield BalanceStatsButton(self._account, "stake", Asset.Vests, variant="grey-darken") class TrackedAccountInfo(Container, TrackedAccountReferencingWidget): diff --git a/clive/__private/ui/screens/dashboard/dashboard.scss b/clive/__private/ui/screens/dashboard/dashboard.scss index 9e7603f35a..7b3a038a1f 100644 --- a/clive/__private/ui/screens/dashboard/dashboard.scss +++ b/clive/__private/ui/screens/dashboard/dashboard.scss @@ -120,10 +120,6 @@ Dashboard { grid-size: 3; height: 3; - Static { - text-align: center; - } - DynamicLabel { align: center middle; width: 1fr; @@ -131,20 +127,23 @@ Dashboard { BalanceStatsButton OneLineButton { width: 1fr; + text-align: right; } .title { + margin-left: 2; + text-align: center; + } + + .liquid { background: $primary-darken-3; - color: $text; } - .token-hive { - margin-left: 2; + .savings { background: $primary-darken-1; } - .token-hbd { - margin-left: 2; + .stake { background: $primary-lighten-2; } } -- GitLab