mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-03-03 16:56:07 +00:00
refactor(core): convert apps.nem to layouts
This commit is contained in:
parent
dd3b689ded
commit
178b575465
@ -1,39 +1,50 @@
|
|||||||
from trezor import ui
|
|
||||||
from trezor.enums import ButtonRequestType
|
from trezor.enums import ButtonRequestType
|
||||||
from trezor.strings import format_amount
|
from trezor.strings import format_amount
|
||||||
from trezor.ui.components.tt.text import Text
|
from trezor.ui.layouts import confirm_metadata, confirm_properties
|
||||||
|
|
||||||
from apps.common.confirm import require_confirm, require_hold_to_confirm
|
|
||||||
|
|
||||||
from .helpers import NEM_MAX_DIVISIBILITY
|
from .helpers import NEM_MAX_DIVISIBILITY
|
||||||
|
|
||||||
|
|
||||||
async def require_confirm_text(ctx, action: str):
|
async def require_confirm_text(ctx, action: str):
|
||||||
text = Text("Confirm action", ui.ICON_SEND, ui.GREEN, new_lines=False)
|
await confirm_metadata(
|
||||||
text.normal(action)
|
ctx,
|
||||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
"confirm_nem",
|
||||||
|
title="Confirm action",
|
||||||
|
content=action,
|
||||||
|
hide_continue=True,
|
||||||
|
br_code=ButtonRequestType.ConfirmOutput,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def require_confirm_fee(ctx, action: str, fee: int):
|
async def require_confirm_fee(ctx, action: str, fee: int):
|
||||||
content = (
|
await confirm_metadata(
|
||||||
ui.NORMAL,
|
ctx,
|
||||||
action,
|
"confirm_fee",
|
||||||
ui.BOLD,
|
title="Confirm fee",
|
||||||
"%s XEM" % format_amount(fee, NEM_MAX_DIVISIBILITY),
|
content=action + "\n{}",
|
||||||
|
param="{} XEM".format(format_amount(fee, NEM_MAX_DIVISIBILITY)),
|
||||||
|
hide_continue=True,
|
||||||
|
br_code=ButtonRequestType.ConfirmOutput,
|
||||||
)
|
)
|
||||||
await require_confirm_content(ctx, "Confirm fee", content)
|
|
||||||
|
|
||||||
|
|
||||||
async def require_confirm_content(ctx, headline: str, content: list):
|
async def require_confirm_content(ctx, headline: str, content: list):
|
||||||
text = Text(headline, ui.ICON_SEND, ui.GREEN)
|
await confirm_properties(
|
||||||
text.normal(*content)
|
ctx,
|
||||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
"confirm_content",
|
||||||
|
title=headline,
|
||||||
|
props=content,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def require_confirm_final(ctx, fee: int):
|
async def require_confirm_final(ctx, fee: int):
|
||||||
text = Text("Final confirm", ui.ICON_SEND, ui.GREEN)
|
|
||||||
text.normal("Sign this transaction")
|
|
||||||
text.bold("and pay %s XEM" % format_amount(fee, NEM_MAX_DIVISIBILITY))
|
|
||||||
text.normal("for network fee?")
|
|
||||||
# we use SignTx, not ConfirmOutput, for compatibility with T1
|
# we use SignTx, not ConfirmOutput, for compatibility with T1
|
||||||
await require_hold_to_confirm(ctx, text, ButtonRequestType.SignTx)
|
await confirm_metadata(
|
||||||
|
ctx,
|
||||||
|
"confirm_final",
|
||||||
|
title="Final confirm",
|
||||||
|
content="Sign this transaction\n{}\nfor network fee?",
|
||||||
|
param="and pay {} XEM".format(format_amount(fee, NEM_MAX_DIVISIBILITY)),
|
||||||
|
hide_continue=True,
|
||||||
|
hold=True,
|
||||||
|
)
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
from trezor import ui
|
from trezor import ui
|
||||||
from trezor.enums import ButtonRequestType, NEMMosaicLevy, NEMSupplyChangeType
|
from trezor.enums import NEMMosaicLevy, NEMSupplyChangeType
|
||||||
from trezor.messages import (
|
from trezor.messages import (
|
||||||
NEMMosaicCreation,
|
NEMMosaicCreation,
|
||||||
NEMMosaicDefinition,
|
NEMMosaicDefinition,
|
||||||
NEMMosaicSupplyChange,
|
NEMMosaicSupplyChange,
|
||||||
NEMTransactionCommon,
|
NEMTransactionCommon,
|
||||||
)
|
)
|
||||||
from trezor.ui.components.tt.scroll import Paginated
|
from trezor.ui.layouts import confirm_properties
|
||||||
from trezor.ui.components.tt.text import Text
|
|
||||||
|
|
||||||
from apps.common.confirm import require_confirm
|
|
||||||
from apps.common.layout import split_address
|
|
||||||
|
|
||||||
from ..layout import (
|
from ..layout import (
|
||||||
require_confirm_content,
|
require_confirm_content,
|
||||||
@ -47,27 +43,15 @@ async def ask_supply_change(
|
|||||||
|
|
||||||
def _creation_message(mosaic_creation):
|
def _creation_message(mosaic_creation):
|
||||||
return [
|
return [
|
||||||
ui.NORMAL,
|
("Create mosaic", mosaic_creation.definition.mosaic),
|
||||||
"Create mosaic",
|
("under namespace", mosaic_creation.definition.namespace),
|
||||||
ui.BOLD,
|
|
||||||
mosaic_creation.definition.mosaic,
|
|
||||||
ui.NORMAL,
|
|
||||||
"under namespace",
|
|
||||||
ui.BOLD,
|
|
||||||
mosaic_creation.definition.namespace,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def _supply_message(supply_change):
|
def _supply_message(supply_change):
|
||||||
return [
|
return [
|
||||||
ui.NORMAL,
|
("Modify supply for", supply_change.mosaic),
|
||||||
"Modify supply for",
|
("under namespace", supply_change.namespace),
|
||||||
ui.BOLD,
|
|
||||||
supply_change.mosaic,
|
|
||||||
ui.NORMAL,
|
|
||||||
"under namespace",
|
|
||||||
ui.BOLD,
|
|
||||||
supply_change.namespace,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -76,21 +60,14 @@ async def require_confirm_properties(ctx, definition: NEMMosaicDefinition):
|
|||||||
|
|
||||||
# description
|
# description
|
||||||
if definition.description:
|
if definition.description:
|
||||||
t = Text("Confirm properties", ui.ICON_SEND, new_lines=False)
|
properties.append(("Description:", definition.description))
|
||||||
t.bold("Description:")
|
|
||||||
t.br()
|
|
||||||
t.normal(*definition.description.split(" "))
|
|
||||||
properties.append(t)
|
|
||||||
|
|
||||||
# transferable
|
# transferable
|
||||||
if definition.transferable:
|
if definition.transferable:
|
||||||
transferable = "Yes"
|
transferable = "Yes"
|
||||||
else:
|
else:
|
||||||
transferable = "No"
|
transferable = "No"
|
||||||
t = Text("Confirm properties", ui.ICON_SEND)
|
properties.append(("Transferable?", transferable))
|
||||||
t.bold("Transferable?")
|
|
||||||
t.normal(transferable)
|
|
||||||
properties.append(t)
|
|
||||||
|
|
||||||
# mutable_supply
|
# mutable_supply
|
||||||
if definition.mutable_supply:
|
if definition.mutable_supply:
|
||||||
@ -98,45 +75,30 @@ async def require_confirm_properties(ctx, definition: NEMMosaicDefinition):
|
|||||||
else:
|
else:
|
||||||
imm = "immutable"
|
imm = "immutable"
|
||||||
if definition.supply:
|
if definition.supply:
|
||||||
t = Text("Confirm properties", ui.ICON_SEND)
|
properties.append(("Initial supply:", str(definition.supply) + "\n" + imm))
|
||||||
t.bold("Initial supply:")
|
|
||||||
t.normal(str(definition.supply), imm)
|
|
||||||
else:
|
else:
|
||||||
t = Text("Confirm properties", ui.ICON_SEND)
|
properties.append(("Initial supply:", imm))
|
||||||
t.bold("Initial supply:")
|
|
||||||
t.normal(imm)
|
|
||||||
properties.append(t)
|
|
||||||
|
|
||||||
# levy
|
# levy
|
||||||
if definition.levy:
|
if definition.levy:
|
||||||
|
properties.append(("Levy recipient:", definition.levy_address))
|
||||||
|
|
||||||
t = Text("Confirm properties", ui.ICON_SEND)
|
properties.append(("Levy fee:", str(definition.fee)))
|
||||||
t.bold("Levy recipient:")
|
properties.append(("Levy divisibility:", str(definition.divisibility)))
|
||||||
t.mono(*split_address(definition.levy_address))
|
|
||||||
properties.append(t)
|
|
||||||
|
|
||||||
t = Text("Confirm properties", ui.ICON_SEND)
|
properties.append(("Levy namespace:", definition.levy_namespace))
|
||||||
t.bold("Levy fee:")
|
properties.append(("Levy mosaic:", definition.levy_mosaic))
|
||||||
t.normal(str(definition.fee))
|
|
||||||
t.bold("Levy divisibility:")
|
|
||||||
t.normal(str(definition.divisibility))
|
|
||||||
properties.append(t)
|
|
||||||
|
|
||||||
t = Text("Confirm properties", ui.ICON_SEND)
|
|
||||||
t.bold("Levy namespace:")
|
|
||||||
t.normal(definition.levy_namespace)
|
|
||||||
t.bold("Levy mosaic:")
|
|
||||||
t.normal(definition.levy_mosaic)
|
|
||||||
properties.append(t)
|
|
||||||
|
|
||||||
if definition.levy == NEMMosaicLevy.MosaicLevy_Absolute:
|
if definition.levy == NEMMosaicLevy.MosaicLevy_Absolute:
|
||||||
levy_type = "absolute"
|
levy_type = "absolute"
|
||||||
else:
|
else:
|
||||||
levy_type = "percentile"
|
levy_type = "percentile"
|
||||||
t = Text("Confirm properties", ui.ICON_SEND)
|
properties.append(("Levy type:", levy_type))
|
||||||
t.bold("Levy type:")
|
|
||||||
t.normal(levy_type)
|
|
||||||
properties.append(t)
|
|
||||||
|
|
||||||
paginated = Paginated(properties)
|
await confirm_properties(
|
||||||
await require_confirm(ctx, paginated, ButtonRequestType.ConfirmOutput)
|
ctx,
|
||||||
|
"confirm_properties",
|
||||||
|
title="Confirm properties",
|
||||||
|
props=properties,
|
||||||
|
icon_color=ui.ORANGE_ICON,
|
||||||
|
)
|
||||||
|
@ -2,16 +2,10 @@ from trezor import ui
|
|||||||
from trezor.crypto import nem
|
from trezor.crypto import nem
|
||||||
from trezor.enums import ButtonRequestType, NEMModificationType
|
from trezor.enums import ButtonRequestType, NEMModificationType
|
||||||
from trezor.messages import NEMAggregateModification, NEMSignTx, NEMTransactionCommon
|
from trezor.messages import NEMAggregateModification, NEMSignTx, NEMTransactionCommon
|
||||||
from trezor.ui.components.tt.text import Text
|
from trezor.ui.constants import MONO_ADDR_PER_LINE
|
||||||
|
from trezor.ui.layouts import confirm_hex
|
||||||
|
|
||||||
from apps.common.layout import split_address
|
from ..layout import require_confirm_fee, require_confirm_final, require_confirm_text
|
||||||
|
|
||||||
from ..layout import (
|
|
||||||
require_confirm,
|
|
||||||
require_confirm_fee,
|
|
||||||
require_confirm_final,
|
|
||||||
require_confirm_text,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def ask_multisig(ctx, msg: NEMSignTx):
|
async def ask_multisig(ctx, msg: NEMSignTx):
|
||||||
@ -48,7 +42,14 @@ async def ask_aggregate_modification(
|
|||||||
|
|
||||||
|
|
||||||
async def _require_confirm_address(ctx, action: str, address: str):
|
async def _require_confirm_address(ctx, action: str, address: str):
|
||||||
text = Text("Confirm address", ui.ICON_SEND, ui.GREEN)
|
await confirm_hex(
|
||||||
text.normal(action)
|
ctx,
|
||||||
text.mono(*split_address(address))
|
br_type="confirm_multisig",
|
||||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
title="Confirm address",
|
||||||
|
description=action,
|
||||||
|
data=address,
|
||||||
|
br_code=ButtonRequestType.ConfirmOutput,
|
||||||
|
icon=ui.ICON_SEND,
|
||||||
|
width=MONO_ADDR_PER_LINE,
|
||||||
|
truncate=True,
|
||||||
|
)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
from trezor import ui
|
|
||||||
from trezor.messages import NEMProvisionNamespace, NEMTransactionCommon
|
from trezor.messages import NEMProvisionNamespace, NEMTransactionCommon
|
||||||
|
|
||||||
from ..layout import require_confirm_content, require_confirm_fee, require_confirm_final
|
from ..layout import require_confirm_content, require_confirm_fee, require_confirm_final
|
||||||
@ -8,19 +7,13 @@ async def ask_provision_namespace(
|
|||||||
ctx, common: NEMTransactionCommon, namespace: NEMProvisionNamespace
|
ctx, common: NEMTransactionCommon, namespace: NEMProvisionNamespace
|
||||||
):
|
):
|
||||||
if namespace.parent:
|
if namespace.parent:
|
||||||
content = (
|
content = [
|
||||||
ui.NORMAL,
|
("Create namespace", namespace.namespace),
|
||||||
"Create namespace",
|
("under namespace", namespace.parent),
|
||||||
ui.BOLD,
|
]
|
||||||
namespace.namespace,
|
|
||||||
ui.NORMAL,
|
|
||||||
"under namespace",
|
|
||||||
ui.BOLD,
|
|
||||||
namespace.parent,
|
|
||||||
)
|
|
||||||
await require_confirm_content(ctx, "Confirm namespace", content)
|
await require_confirm_content(ctx, "Confirm namespace", content)
|
||||||
else:
|
else:
|
||||||
content = (ui.NORMAL, "Create namespace", ui.BOLD, namespace.namespace)
|
content = [("Create namespace", namespace.namespace)]
|
||||||
await require_confirm_content(ctx, "Confirm namespace", content)
|
await require_confirm_content(ctx, "Confirm namespace", content)
|
||||||
|
|
||||||
await require_confirm_fee(ctx, "Confirm rental fee", namespace.fee)
|
await require_confirm_fee(ctx, "Confirm rental fee", namespace.fee)
|
||||||
|
@ -7,10 +7,7 @@ from trezor.messages import (
|
|||||||
NEMTransfer,
|
NEMTransfer,
|
||||||
)
|
)
|
||||||
from trezor.strings import format_amount
|
from trezor.strings import format_amount
|
||||||
from trezor.ui.components.tt.text import Text
|
from trezor.ui.layouts import confirm_action, confirm_output, confirm_properties
|
||||||
|
|
||||||
from apps.common.confirm import require_confirm
|
|
||||||
from apps.common.layout import split_address
|
|
||||||
|
|
||||||
from ..helpers import (
|
from ..helpers import (
|
||||||
NEM_LEVY_PERCENTILE_DIVISOR_ABSOLUTE,
|
NEM_LEVY_PERCENTILE_DIVISOR_ABSOLUTE,
|
||||||
@ -46,37 +43,52 @@ async def ask_transfer_mosaic(
|
|||||||
mosaic_quantity = mosaic.quantity * transfer.amount / NEM_MOSAIC_AMOUNT_DIVISOR
|
mosaic_quantity = mosaic.quantity * transfer.amount / NEM_MOSAIC_AMOUNT_DIVISOR
|
||||||
|
|
||||||
if definition:
|
if definition:
|
||||||
msg = Text("Confirm mosaic", ui.ICON_SEND, ui.GREEN)
|
await confirm_properties(
|
||||||
msg.normal("Confirm transfer of")
|
ctx,
|
||||||
msg.bold(
|
"confirm_mosaic",
|
||||||
|
title="Confirm mosaic",
|
||||||
|
props=[
|
||||||
|
(
|
||||||
|
"Confirm transfer of",
|
||||||
format_amount(mosaic_quantity, definition["divisibility"])
|
format_amount(mosaic_quantity, definition["divisibility"])
|
||||||
+ definition["ticker"]
|
+ definition["ticker"],
|
||||||
|
),
|
||||||
|
("of", definition["name"]),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
msg.normal("of")
|
|
||||||
msg.bold(definition["name"])
|
|
||||||
await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput)
|
|
||||||
|
|
||||||
if "levy" in definition and "fee" in definition:
|
if "levy" in definition and "fee" in definition:
|
||||||
levy_msg = _get_levy_msg(definition, mosaic_quantity, common.network)
|
levy_msg = _get_levy_msg(definition, mosaic_quantity, common.network)
|
||||||
msg = Text("Confirm mosaic", ui.ICON_SEND, ui.GREEN)
|
await confirm_properties(
|
||||||
msg.normal("Confirm mosaic", "levy fee of")
|
ctx,
|
||||||
msg.bold(levy_msg)
|
"confirm_mosaic_levy",
|
||||||
await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput)
|
title="Confirm mosaic",
|
||||||
|
props=[
|
||||||
|
("Confirm mosaic\nlevy fee of", levy_msg),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msg = Text("Confirm mosaic", ui.ICON_SEND, ui.RED)
|
await confirm_action(
|
||||||
msg.bold("Unknown mosaic!")
|
ctx,
|
||||||
msg.normal("Divisibility and levy")
|
"confirm_mosaic_unknown",
|
||||||
msg.normal("cannot be shown for")
|
title="Confirm mosaic",
|
||||||
msg.normal("unknown mosaics")
|
action="Unknown mosaic!",
|
||||||
await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput)
|
description="Divisibility and levy cannot be shown for unknown mosaics",
|
||||||
|
icon=ui.ICON_SEND,
|
||||||
|
icon_color=ui.RED,
|
||||||
|
br_code=ButtonRequestType.ConfirmOutput,
|
||||||
|
)
|
||||||
|
|
||||||
msg = Text("Confirm mosaic", ui.ICON_SEND, ui.GREEN)
|
await confirm_properties(
|
||||||
msg.normal("Confirm transfer of")
|
ctx,
|
||||||
msg.bold("%s raw units" % mosaic_quantity)
|
"confirm_mosaic_transfer",
|
||||||
msg.normal("of")
|
title="Confirm mosaic",
|
||||||
msg.bold("%s.%s" % (mosaic.namespace, mosaic.mosaic))
|
props=[
|
||||||
await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput)
|
("Confirm transfer of", "%s raw units" % mosaic_quantity),
|
||||||
|
("of", "%s.%s" % (mosaic.namespace, mosaic.mosaic)),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _get_xem_amount(transfer: NEMTransfer):
|
def _get_xem_amount(transfer: NEMTransfer):
|
||||||
@ -119,22 +131,27 @@ async def ask_importance_transfer(
|
|||||||
|
|
||||||
|
|
||||||
async def _require_confirm_transfer(ctx, recipient, value):
|
async def _require_confirm_transfer(ctx, recipient, value):
|
||||||
text = Text("Confirm transfer", ui.ICON_SEND, ui.GREEN)
|
await confirm_output(
|
||||||
text.bold("Send %s XEM" % format_amount(value, NEM_MAX_DIVISIBILITY))
|
ctx,
|
||||||
text.normal("to")
|
recipient,
|
||||||
text.mono(*split_address(recipient))
|
amount="Send {} XEM".format(format_amount(value, NEM_MAX_DIVISIBILITY)),
|
||||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
font_amount=ui.BOLD,
|
||||||
|
title="Confirm transfer",
|
||||||
|
to_str="\nto\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _require_confirm_payload(ctx, payload: bytearray, encrypt=False):
|
async def _require_confirm_payload(ctx, payload: bytearray, encrypt=False):
|
||||||
payload = bytes(payload).decode()
|
payload = bytes(payload).decode()
|
||||||
|
subtitle = "Encrypted:" if encrypt else "Unencrypted:"
|
||||||
|
|
||||||
if encrypt:
|
await confirm_properties(
|
||||||
text = Text("Confirm payload", ui.ICON_SEND, ui.GREEN)
|
ctx,
|
||||||
text.bold("Encrypted:")
|
"confirm_payload",
|
||||||
text.normal(payload)
|
title="Confirm payload",
|
||||||
else:
|
props=[
|
||||||
text = Text("Confirm payload", ui.ICON_SEND, ui.RED)
|
(None, subtitle),
|
||||||
text.bold("Unencrypted:")
|
(payload, None),
|
||||||
text.normal(payload)
|
],
|
||||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
icon_color=ui.GREEN if encrypt else ui.RED,
|
||||||
|
)
|
||||||
|
@ -36,7 +36,7 @@ if False:
|
|||||||
)
|
)
|
||||||
|
|
||||||
ExceptionType = Union[BaseException, Type[BaseException]]
|
ExceptionType = Union[BaseException, Type[BaseException]]
|
||||||
PropertyType = Tuple[str, Optional[str]]
|
PropertyType = Tuple[Optional[str], Optional[str]]
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -590,6 +590,7 @@ async def confirm_properties(
|
|||||||
) -> None:
|
) -> None:
|
||||||
para = []
|
para = []
|
||||||
for p in props:
|
for p in props:
|
||||||
|
if p[0] is not None:
|
||||||
para.append((ui.NORMAL, p[0]))
|
para.append((ui.NORMAL, p[0]))
|
||||||
if p[1] is not None:
|
if p[1] is not None:
|
||||||
para.append((ui.BOLD, p[1]))
|
para.append((ui.BOLD, p[1]))
|
||||||
|
@ -28,10 +28,14 @@ class TestMsgNEMGetaddress:
|
|||||||
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
|
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
|
||||||
def test_nem_getaddress(self, client):
|
def test_nem_getaddress(self, client):
|
||||||
assert (
|
assert (
|
||||||
nem.get_address(client, parse_path("m/44'/1'/0'/0'/0'"), 0x68)
|
nem.get_address(
|
||||||
|
client, parse_path("m/44'/1'/0'/0'/0'"), 0x68, show_display=True
|
||||||
|
)
|
||||||
== "NB3JCHVARQNGDS3UVGAJPTFE22UQFGMCQGHUBWQN"
|
== "NB3JCHVARQNGDS3UVGAJPTFE22UQFGMCQGHUBWQN"
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
nem.get_address(client, parse_path("m/44'/1'/0'/0'/0'"), 0x98)
|
nem.get_address(
|
||||||
|
client, parse_path("m/44'/1'/0'/0'/0'"), 0x98, show_display=True
|
||||||
|
)
|
||||||
== "TB3JCHVARQNGDS3UVGAJPTFE22UQFGMCQHSBNBMF"
|
== "TB3JCHVARQNGDS3UVGAJPTFE22UQFGMCQHSBNBMF"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user