mirror of
https://codeberg.org/wownero/lwcli-wow.git
synced 2026-10-17 05:51:26 +02:00
Meta Update:
* Completed Account Handling * Added QR Code Generation support * Added Tx Send Support WITHOUT address book * Bump version of lwsf
This commit is contained in:
Vendored
+1
-1
Submodule external/lwsf updated: 38f44770b8...68ccb25862
+31
-17
@@ -41,7 +41,7 @@ namespace lwcli { namespace component
|
||||
class table_ final : public ftxui::ComponentBase
|
||||
{
|
||||
const table_generator generator_;
|
||||
const table_enter enter_;
|
||||
const table_on_key key_;
|
||||
const std::vector<std::vector<std::string>> title_;
|
||||
std::ptrdiff_t rows_;
|
||||
std::ptrdiff_t selected_;
|
||||
@@ -62,10 +62,10 @@ namespace lwcli { namespace component
|
||||
}
|
||||
|
||||
public:
|
||||
explicit table_(std::vector<std::string>&& title, table_generator&& generator, table_enter&& enter)
|
||||
explicit table_(std::vector<std::string>&& title, table_generator&& generator, table_on_key&& key)
|
||||
: ftxui::ComponentBase(),
|
||||
generator_(std::move(generator)),
|
||||
enter_(std::move(enter)),
|
||||
key_(std::move(key)),
|
||||
title_(get_title_bar(std::move(title))),
|
||||
rows_(0),
|
||||
selected_(-1)
|
||||
@@ -80,18 +80,31 @@ namespace lwcli { namespace component
|
||||
rows_ = rows;
|
||||
}
|
||||
|
||||
bool can_increment() const noexcept
|
||||
{ return selected_ + min_row() < rows_; }
|
||||
|
||||
bool can_decrement() const noexcept
|
||||
{ return min_row() <= selected_; }
|
||||
|
||||
bool OnEvent(ftxui::Event event) override final
|
||||
{
|
||||
if (event == ftxui::Event::ArrowDown && selected_ + min_row() < rows_)
|
||||
if (event.is_mouse())
|
||||
{
|
||||
if (event.mouse().button == ftxui::Mouse::WheelDown && can_increment())
|
||||
++selected_;
|
||||
else if (event.mouse().button == ftxui::Mouse::WheelUp && can_decrement())
|
||||
--selected_;// = std::max(min_row(), selected_ - 1);
|
||||
}
|
||||
else if (event == ftxui::Event::ArrowDown && can_increment())
|
||||
++selected_;
|
||||
else if (event == ftxui::Event::ArrowUp && min_row() <= selected_)
|
||||
else if (event == ftxui::Event::ArrowUp && can_decrement())
|
||||
--selected_;
|
||||
else if (event == ftxui::Event::PageDown)
|
||||
selected_ = std::min(rows_ + min_row(), selected_ + 15);
|
||||
else if (event == ftxui::Event::PageUp)
|
||||
selected_ = std::max(min_row(), selected_ - 15);
|
||||
else if (event == ftxui::Event::Return && min_row() <= selected_ && selected_ < rows_)
|
||||
return enter_(std::size_t(selected_));
|
||||
else if (min_row() <= selected_ && selected_ < rows_)
|
||||
return key_(std::move(event), std::size_t(selected_));
|
||||
|
||||
return min_row() <= selected_ && selected_ - min_row() < rows_;
|
||||
}
|
||||
@@ -119,23 +132,24 @@ namespace lwcli { namespace component
|
||||
selected_ = min_row();
|
||||
else if (rows_ <= selected_ - min_row())
|
||||
selected_ = rows_ - 1 + min_row();
|
||||
|
||||
if (min_row() <= selected_ && selected_ < rows_)
|
||||
{
|
||||
auto row = table.SelectRow(std::size_t(selected_) + title_.size());
|
||||
row.Decorate(ftxui::inverted);
|
||||
row.Decorate(ftxui::focus);
|
||||
}
|
||||
}
|
||||
|
||||
if (min_row() <= selected_ && selected_ < rows_)
|
||||
{
|
||||
auto row = table.SelectRow(std::size_t(selected_) + title_.size());
|
||||
row.Decorate(ftxui::inverted);
|
||||
row.Decorate(ftxui::focus);
|
||||
}
|
||||
|
||||
return table.Render();
|
||||
}
|
||||
};
|
||||
} // anonymous
|
||||
|
||||
ftxui::Component table(std::vector<std::string> title, table_generator generator, table_enter enter)
|
||||
ftxui::Component table(std::vector<std::string> title, table_generator generator, table_on_key key)
|
||||
{
|
||||
if (!generator || !enter)
|
||||
if (!generator || !key)
|
||||
throw std::invalid_argument{"lwcli::components::table was given nullptr"};
|
||||
return std::make_shared<table_>(std::move(title), std::move(generator), std::move(enter));
|
||||
return std::make_shared<table_>(std::move(title), std::move(generator), std::move(key));
|
||||
}
|
||||
}} // lwcli // view
|
||||
|
||||
@@ -39,8 +39,8 @@ namespace lwcli { namespace component
|
||||
{
|
||||
//! Shows Transaction History
|
||||
using table_generator = std::function<std::vector<std::vector<std::string>>()>;
|
||||
using table_enter = std::function<bool(std::size_t)>;
|
||||
ftxui::Component table(std::vector<std::string> title, table_generator generator, table_enter enter);
|
||||
using table_on_key = std::function<bool(ftxui::Event, std::size_t)>;
|
||||
ftxui::Component table(std::vector<std::string> title, table_generator generator, table_on_key key);
|
||||
|
||||
}} // lwscli // view
|
||||
|
||||
|
||||
@@ -34,4 +34,5 @@ namespace lwcli { namespace event
|
||||
string optmization. */
|
||||
const ftxui::Event lock_wallet = ftxui::Event::Special("lwcli.lockw");
|
||||
const ftxui::Event refresh_wallet = ftxui::Event::Special("lwcli.refresh");
|
||||
const ftxui::Event send_async = ftxui::Event::Special("lwcli.sendasync");
|
||||
}}
|
||||
|
||||
+1
-1
@@ -46,5 +46,5 @@ namespace lwcli { namespace event
|
||||
|
||||
extern const ftxui::Event lock_wallet;
|
||||
extern const ftxui::Event refresh_wallet;
|
||||
|
||||
extern const ftxui::Event send_async;
|
||||
}} // lwcli // event
|
||||
|
||||
@@ -51,7 +51,6 @@ namespace lwcli { namespace config
|
||||
|
||||
constexpr const std::string_view default_language{"English"};
|
||||
constexpr const std::string_view default_account_name{"Untitled Account"};
|
||||
constexpr const std::string_view default_subaddress_name{"Untitled Address"};
|
||||
|
||||
//! Timeout interval for inactivity with open wallet
|
||||
constexpr const std::chrono::minutes wallet_timeout{2};
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
set(lwcli-views_sources accounts.cpp history.cpp keys.cpp manager.cpp settings.cpp wallet.cpp)
|
||||
set(lwscli-views_headers accounts.h history.h keys.h manager.h settings.h wallet.h)
|
||||
set(lwcli-views_sources accounts.cpp history.cpp keys.cpp manager.cpp send.cpp settings.cpp wallet.cpp)
|
||||
set(lwscli-views_headers accounts.h history.h keys.h manager.h send.h settings.h wallet.h)
|
||||
|
||||
add_library(lwcli-views ${lwcli-views_sources} ${lwcli-views_headers})
|
||||
target_link_libraries(lwcli-views PRIVATE component dom lwcli-components lwcli-decorate lwsf-api)
|
||||
|
||||
+143
-21
@@ -54,6 +54,106 @@ namespace lwcli { namespace view
|
||||
|
||||
ftxui::ButtonOption ascii() { return ftxui::ButtonOption::Ascii(); }
|
||||
|
||||
ftxui::Canvas make_qr_code(std::vector<std::vector<std::uint8_t>> raw)
|
||||
{
|
||||
const std::size_t size = raw.size();
|
||||
if (std::numeric_limits<std::size_t>::max() / 4 < size)
|
||||
throw std::runtime_error{"qrcode too large"};
|
||||
if (std::numeric_limits<int>::max() / 4 < size)
|
||||
throw std::runtime_error{"qrcode too large"};
|
||||
|
||||
const std::size_t canvas_size = (size * 2) + ((size % 2) * 2);
|
||||
ftxui::Canvas out{int(canvas_size), int(canvas_size)};
|
||||
for (std::size_t y = 0; y < canvas_size; ++y)
|
||||
for (std::size_t x = 0; x < canvas_size; ++x)
|
||||
out.DrawBlockOff(x, y);
|
||||
|
||||
for (std::size_t y = 0; y < size; ++y)
|
||||
{
|
||||
const auto& row = raw.at(y);
|
||||
for (std::size_t x = 0; x < size; ++x)
|
||||
{
|
||||
if (row.at(x))
|
||||
{
|
||||
const int real_x = x * 2;
|
||||
const int real_y = y * 2;
|
||||
out.DrawBlockOn(real_x, real_y);
|
||||
out.DrawBlockOn(real_x + 1, real_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
class subaccount_ final : public ftxui::ComponentBase
|
||||
{
|
||||
std::string subaccount_name_;
|
||||
const std::shared_ptr<Monero::Wallet> wal_;
|
||||
const ftxui::Element title_;
|
||||
const ftxui::Element desc_;
|
||||
const ftxui::Canvas qr_code_raw_;
|
||||
const ftxui::Element qr_code_;
|
||||
ftxui::Component buttons_;
|
||||
const ftxui::Component name_;
|
||||
ftxui::Component ui_;
|
||||
const std::uint32_t major_;
|
||||
const std::uint32_t minor_;
|
||||
|
||||
bool Focusable() const override final { return true; }
|
||||
ftxui::Component ActiveChild() override final { return ui_; }
|
||||
|
||||
public:
|
||||
explicit subaccount_(std::shared_ptr<Monero::Wallet>&& wal, std::uint32_t major, std::uint32_t minor)
|
||||
: ftxui::ComponentBase(),
|
||||
subaccount_name_(wal->getSubaddressLabel(major, minor)),
|
||||
wal_(std::move(wal)),
|
||||
title_(ftxui::text(wal_->address(major, minor))),
|
||||
desc_(ftxui::text(_("Name: "))),
|
||||
qr_code_raw_(make_qr_code(lwsf::qrcode(wal_.get(), major, minor))),
|
||||
qr_code_(ftxui::canvas(&qr_code_raw_)),
|
||||
buttons_(),
|
||||
name_(last_input(&subaccount_name_)),
|
||||
ui_(),
|
||||
major_(major),
|
||||
minor_(minor)
|
||||
{
|
||||
buttons_ = ftxui::Container::Horizontal({
|
||||
ftxui::Button(_("Cancel"), [] () { throw event::close{}; }, ascii()),
|
||||
ftxui::Button(_("Save"), [this] () {
|
||||
wal_->setSubaddressLabel(major_, minor_, subaccount_name_);
|
||||
throw event::close{};
|
||||
}, ascii())
|
||||
});
|
||||
|
||||
ui_ = ftxui::Container::Vertical({buttons_, name_});
|
||||
Add(ui_);
|
||||
}
|
||||
|
||||
bool OnEvent(ftxui::Event event) override final
|
||||
{
|
||||
if (event == ftxui::Event::CtrlQ)
|
||||
throw event::close{};
|
||||
ui_->OnEvent(std::move(event));
|
||||
return true;
|
||||
}
|
||||
|
||||
ftxui::Element OnRender() override final
|
||||
{
|
||||
return ftxui::window(title_, ftxui::vbox({
|
||||
buttons_->Render() | ftxui::hcenter,
|
||||
ftxui::separator(),
|
||||
ftxui::hbox({desc_, name_->Render()}),
|
||||
ftxui::separator(),
|
||||
qr_code_ | ftxui::hcenter
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
ftxui::Component subaccount(std::shared_ptr<Monero::Wallet> wal, std::uint32_t major, std::uint32_t minor)
|
||||
{
|
||||
return std::make_shared<subaccount_>(std::move(wal), major, minor);
|
||||
}
|
||||
|
||||
class account_detail final : public ftxui::ComponentBase
|
||||
{
|
||||
std::string account_name_;
|
||||
@@ -67,6 +167,7 @@ namespace lwcli { namespace view
|
||||
ftxui::Component buttons_;
|
||||
const ftxui::Component name_;
|
||||
ftxui::Component ui_;
|
||||
ftxui::Element cached_;
|
||||
std::unordered_map<std::size_t, std::size_t> row_map_;
|
||||
const std::uint32_t id_;
|
||||
|
||||
@@ -85,13 +186,15 @@ namespace lwcli { namespace view
|
||||
wal_(std::move(wal)),
|
||||
acct_(acct),
|
||||
title_(ftxui::text(_("Account #") + std::to_string(id))),
|
||||
address_({ftxui::text("Address: "), ftxui::text(wal_->address(id, 0))}),
|
||||
address_({ftxui::text("Primary: "), ftxui::text(wal_->address(id, 0).substr(0, 30) + "...")}),
|
||||
desc_(ftxui::text(_("Name: "))),
|
||||
details_(),
|
||||
table_(),
|
||||
buttons_(),
|
||||
name_(last_input(&account_name_)),
|
||||
ui_(),
|
||||
cached_(),
|
||||
row_map_(),
|
||||
id_(id)
|
||||
{
|
||||
if (!acct_)
|
||||
@@ -105,25 +208,29 @@ namespace lwcli { namespace view
|
||||
wal_->setSubaddressLabel(id_, 0, account_name_);
|
||||
throw event::close{};
|
||||
}, ascii()),
|
||||
ftxui::Button(_("Add Subaddress"), [this] () {
|
||||
acct_->addRow(id_, std::string{config::default_subaddress_name});
|
||||
}, ascii())
|
||||
ftxui::Button(_("Add Subaddress"), [this] () { acct_->addRow(id_, std::string{}); }, ascii())
|
||||
});
|
||||
|
||||
table_ = component::table(
|
||||
{_("#"), _("Label"), _("Address")},
|
||||
[this] () { return subaddress_list(); },
|
||||
[this] (std::size_t i) { return display_details(i); }
|
||||
[this] (ftxui::Event e, std::size_t i) { return display_details(e, i); }
|
||||
);
|
||||
|
||||
ui_ = ftxui::Container::Vertical({buttons_, name_, table_});
|
||||
Add(ui_);
|
||||
}
|
||||
|
||||
bool display_details(const std::size_t index)
|
||||
bool display_details(const ftxui::Event& e, const std::size_t index)
|
||||
{
|
||||
if (row_map_.find(index) == row_map_.end())
|
||||
throw std::logic_error{"DEBUG"};
|
||||
if (e != ftxui::Event::Return)
|
||||
return true;
|
||||
|
||||
const std::size_t minor = row_map_.at(index);
|
||||
if (std::numeric_limits<std::uint32_t>::max() < minor)
|
||||
throw std::runtime_error{"account::display_details invalid minor"};
|
||||
details_ = subaccount(wal_, id_, std::uint32_t(minor));
|
||||
Add(details_);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -143,6 +250,7 @@ namespace lwcli { namespace view
|
||||
throw;
|
||||
details_->Detach();
|
||||
details_.reset();
|
||||
account_name_ = wal_->getSubaddressLabel(id_, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -177,13 +285,18 @@ namespace lwcli { namespace view
|
||||
|
||||
ftxui::Element OnRender() override final
|
||||
{
|
||||
return ftxui::window(title_, ftxui::vbox({
|
||||
buttons_->Render() | ftxui::hcenter,
|
||||
ftxui::separator(),
|
||||
ftxui::gridbox({address_, {desc_, name_->Render()}}),
|
||||
ftxui::separator(),
|
||||
table_->Render() | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center
|
||||
}));
|
||||
if (!details_)
|
||||
{
|
||||
cached_ = ftxui::window(title_, ftxui::vbox({
|
||||
buttons_->Render() | ftxui::hcenter,
|
||||
ftxui::separator(),
|
||||
ftxui::gridbox({address_, {desc_, name_->Render()}}),
|
||||
ftxui::separator(),
|
||||
table_->Render() | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center
|
||||
}));
|
||||
return cached_;
|
||||
}
|
||||
return ftxui::dbox(cached_, decorate::overlay(details_->Render()));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -194,6 +307,7 @@ namespace lwcli { namespace view
|
||||
Monero::SubaddressAccount* const wal_accounts_;
|
||||
std::uint32_t* const account_;
|
||||
const ftxui::Element title_;
|
||||
const ftxui::Element instructions_;
|
||||
ftxui::Component details_;
|
||||
ftxui::Component table_;
|
||||
ftxui::Element table_cached_;
|
||||
@@ -216,6 +330,7 @@ namespace lwcli { namespace view
|
||||
wal_accounts_(wal_->subaddressAccount()),
|
||||
account_(account),
|
||||
title_(ftxui::text(_("Accounts"))),
|
||||
instructions_(decorate::banner(ftxui::text("[l]oad account")) | ftxui::inverted),
|
||||
details_(),
|
||||
table_(),
|
||||
table_cached_(),
|
||||
@@ -233,18 +348,24 @@ namespace lwcli { namespace view
|
||||
table_ = component::table(
|
||||
{_("#"), _("Balance"), _("Label"), _("Address")},
|
||||
[this] () { return accounts_list(); },
|
||||
[this] (std::size_t i) { return display_details(i); }
|
||||
[this] (ftxui::Event e, std::size_t i) { return display_details(e, i); }
|
||||
);
|
||||
|
||||
ui_ = ftxui::Container::Vertical({buttons_, table_});
|
||||
Add(ui_);
|
||||
}
|
||||
|
||||
bool display_details(const std::size_t index)
|
||||
bool display_details(const ftxui::Event& e, const std::size_t index)
|
||||
{
|
||||
if (details_)
|
||||
details_->Detach();
|
||||
details_ = std::make_shared<account_detail>(wal_, wal_->subaddress(), row_map_.at(index));
|
||||
if (e == ftxui::Event::Return)
|
||||
{
|
||||
if (details_)
|
||||
details_->Detach();
|
||||
details_ = std::make_shared<account_detail>(wal_, wal_->subaddress(), row_map_.at(index));
|
||||
Add(details_);
|
||||
}
|
||||
else if (e == ftxui::Event::l || e == ftxui::Event::L)
|
||||
*account_ = row_map_.at(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -305,7 +426,8 @@ namespace lwcli { namespace view
|
||||
table_cached_ = ftxui::window(title_, ftxui::vbox({
|
||||
buttons_->Render() | ftxui::hcenter,
|
||||
ftxui::separator(),
|
||||
table_->Render() //| ftxui::vscroll_indicator | ftxui::yframe | ftxui::center
|
||||
table_->Render() | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center,
|
||||
instructions_
|
||||
}));
|
||||
return table_cached_;
|
||||
}
|
||||
|
||||
+34
-73
@@ -44,61 +44,10 @@ namespace lwcli { namespace view
|
||||
{
|
||||
namespace
|
||||
{
|
||||
// Notice for next two functions
|
||||
|
||||
// Copyright (c) 2014-2024, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||
|
||||
constexpr const unsigned int default_decimal_point = 12;
|
||||
void insert_money_decimal_point(std::string &s, unsigned int decimal_point)
|
||||
{
|
||||
if (decimal_point == (unsigned int)-1)
|
||||
decimal_point = default_decimal_point;
|
||||
if(s.size() < decimal_point+1)
|
||||
{
|
||||
s.insert(0, decimal_point+1 - s.size(), '0');
|
||||
}
|
||||
if (decimal_point > 0)
|
||||
s.insert(s.size() - decimal_point, ".");
|
||||
}
|
||||
std::string print_money(const std::uint64_t amount)
|
||||
{
|
||||
std::string s = std::to_string(amount);
|
||||
insert_money_decimal_point(s, default_decimal_point);
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string print_amount(std::uint64_t amount, int direction)
|
||||
{
|
||||
const bool negate = direction == Monero::TransactionInfo::Direction_Out;
|
||||
return (negate ? "-" : "") + print_money(amount);
|
||||
return (negate ? "-" : "") + lwsf::displayAmount(amount);
|
||||
}
|
||||
|
||||
std::string print_amount(const Monero::TransactionInfo& tx)
|
||||
@@ -154,7 +103,7 @@ namespace lwcli { namespace view
|
||||
text = _("Transfers: ");
|
||||
grid.push_back({
|
||||
ftxui::text(std::move(text)),
|
||||
ftxui::text(print_money(transfer.amount) + address)
|
||||
ftxui::text(lwsf::displayAmount(transfer.amount) + address)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -212,7 +161,7 @@ namespace lwcli { namespace view
|
||||
}
|
||||
payment_id_ = {ftxui::text(_("Payment ID: ")), ftxui::text(info_->paymentId())};
|
||||
amount_ = {ftxui::text(_("Amount: ")), ftxui::text(print_amount(*info_))};
|
||||
fee_ = {ftxui::text(_("Fee: ")), ftxui::text(print_money(info_->fee()))};
|
||||
fee_ = {ftxui::text(_("Fee: ")), ftxui::text(lwsf::displayAmount(info_->fee()))};
|
||||
{
|
||||
std::string minors;
|
||||
for (const std::uint32_t minor : info_->subaddrIndex())
|
||||
@@ -272,34 +221,35 @@ namespace lwcli { namespace view
|
||||
row_map_(),
|
||||
account_(account)
|
||||
{
|
||||
Monero::Subaddress* subaddress = wallet_->subaddress();
|
||||
if (!subaddress)
|
||||
throw std::runtime_error{"unexpected subaddress nullptr"};
|
||||
subaddress->refresh(account_);
|
||||
if (!wallet_)
|
||||
throw std::invalid_argument{"lwcli::view::history given nullptr"};
|
||||
|
||||
std::string address = _("Account ") + std::to_string(account_);
|
||||
const auto subaddresses = subaddress->getAll();
|
||||
if (!subaddresses.empty() && *subaddresses.begin())
|
||||
address += ": " + (*subaddresses.begin())->getAddress();
|
||||
|
||||
title_ = ftxui::text(std::move(address));
|
||||
{
|
||||
std::string address = _("Account #") + std::to_string(account_);
|
||||
address.append(" / ").append(wallet_->getSubaddressLabel(account_, 0)).append(" / ");
|
||||
address.append(wallet_->address(account_, 0).substr(0, 20)).append("...");
|
||||
title_ = ftxui::text(std::move(address));
|
||||
}
|
||||
|
||||
// perform transalation lookup once
|
||||
table_ = component::table(
|
||||
{_("Date"), _("Amount"), _("Payment ID"), _("Desription"), _("Block"), _("Fee"), _("Hash")},
|
||||
{_("Date"), _("Amount"), _("Payment ID"), _("Label"), _("Desription"), _("Block"), _("Fee"), _("Hash")},
|
||||
[this] () { return transaction_list(); },
|
||||
[this] (std::size_t i) { return add_overlay(i); }
|
||||
[this] (ftxui::Event e, std::size_t i) { return add_overlay(e, i); }
|
||||
);
|
||||
|
||||
Add(table_);
|
||||
}
|
||||
|
||||
bool add_overlay(const std::size_t i)
|
||||
bool add_overlay(const ftxui::Event& e, const std::size_t i)
|
||||
{
|
||||
if (overlay_)
|
||||
overlay_->Detach();
|
||||
overlay_ = std::make_shared<tx_details>(wallet_->history(), row_map_.at(i));
|
||||
Add(overlay_);
|
||||
if (e == ftxui::Event::Return)
|
||||
{
|
||||
if (overlay_)
|
||||
overlay_->Detach();
|
||||
overlay_ = std::make_shared<tx_details>(wallet_->history(), row_map_.at(i));
|
||||
Add(overlay_);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -390,13 +340,24 @@ namespace lwcli { namespace view
|
||||
|
||||
char const* const extended_payment_id = 16 < payment_id.size() ?
|
||||
"..." : "";
|
||||
|
||||
std::string label;
|
||||
const std::set<std::uint32_t> subaddrs = tx->subaddrIndex();
|
||||
if (!subaddrs.empty())
|
||||
{
|
||||
const std::uint32_t index = *subaddrs.begin();
|
||||
if (index)
|
||||
label = wallet_->getSubaddressLabel(account_, index);
|
||||
}
|
||||
|
||||
rows.push_back({
|
||||
std::string{date},
|
||||
print_amount(amount, direction),
|
||||
payment_id.substr(0, 16) + extended_payment_id,
|
||||
std::move(label),
|
||||
tx->description(),
|
||||
std::to_string(tx->blockHeight()),
|
||||
print_money(tx->fee()),
|
||||
lwsf::displayAmount(tx->fee()),
|
||||
tx->hash().substr(0, 16) + "..."
|
||||
});
|
||||
|
||||
@@ -415,7 +376,7 @@ namespace lwcli { namespace view
|
||||
auto table = table_->Render(); // compute balance first in callback
|
||||
table_cached_ = ftxui::vbox({
|
||||
title_,
|
||||
ftxui::text(_("Balance: ") + print_money(balance_)),
|
||||
ftxui::text(_("Balance: ") + lwsf::displayAmount(balance_)),
|
||||
std::move(table) | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center | ftxui::flex
|
||||
});
|
||||
return table_cached_;
|
||||
|
||||
@@ -470,7 +470,7 @@ namespace lwcli { namespace view
|
||||
}
|
||||
|
||||
public:
|
||||
explicit start(std::shared_ptr<Monero::WalletManager>&& wm, std::string&& file, std::shared_ptr<Monero::Wallet>* out)
|
||||
explicit start(std::shared_ptr<Monero::WalletManager> wm, std::string&& file, std::shared_ptr<Monero::Wallet>* out)
|
||||
: out_(out),
|
||||
title_(ftxui::text("wmcli")),
|
||||
help_(decorate::banner(ftxui::text(_("Ctrl-Q to close active window, Ctrl-C close app immediately")))),
|
||||
@@ -598,6 +598,7 @@ namespace lwcli { namespace view
|
||||
|
||||
class manager_ final : public ftxui::ComponentBase
|
||||
{
|
||||
const std::shared_ptr<Monero::WalletManager> wm_;
|
||||
std::shared_ptr<Monero::Wallet> data_;
|
||||
const ftxui::Component start_;
|
||||
ftxui::Component wallet_;
|
||||
@@ -613,8 +614,9 @@ namespace lwcli { namespace view
|
||||
public:
|
||||
explicit manager_(std::shared_ptr<Monero::WalletManager>&& wm, std::string&& file)
|
||||
: ftxui::ComponentBase(),
|
||||
wm_(std::move(wm)),
|
||||
data_(nullptr),
|
||||
start_(std::make_shared<start>(std::move(wm), std::move(file), &data_)),
|
||||
start_(std::make_shared<start>(wm_, std::move(file), &data_)),
|
||||
wallet_(nullptr)
|
||||
{}
|
||||
|
||||
@@ -633,7 +635,7 @@ namespace lwcli { namespace view
|
||||
return wallet_->OnEvent(std::move(event));
|
||||
}
|
||||
else if (event != event::lock_wallet && start_->OnEvent(std::move(event)) && data_)
|
||||
wallet_ = view::wallet(std::move(data_));
|
||||
wallet_ = view::wallet(wm_, std::move(data_));
|
||||
data_.reset();
|
||||
}
|
||||
catch (const event::close&)
|
||||
|
||||
@@ -0,0 +1,532 @@
|
||||
// Copyright (c) 2025, Cifro Codes LLC
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "wallet.h"
|
||||
|
||||
#include <array>
|
||||
#include <ftxui/component/animation.hpp>
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/event.hpp>
|
||||
#include <ftxui/component/screen_interactive.hpp>
|
||||
#include <ftxui/dom/table.hpp>
|
||||
#include <future>
|
||||
#include <lws_frontend.h>
|
||||
|
||||
#include "components/table.h"
|
||||
#include "decorate/overlay.h"
|
||||
#include "events.h"
|
||||
#include "lwcli_config.h"
|
||||
#include "translate.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace lwcli { namespace view
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using dest_pair = std::pair<std::string, std::string>;
|
||||
using dest_group = std::pair<std::vector<std::string>, std::vector<std::uint64_t>>;
|
||||
|
||||
constexpr const std::array<char, 4> spinner{{'|', '/', '-', '\\'}};
|
||||
|
||||
ftxui::Component last_input(std::string* str)
|
||||
{
|
||||
auto opt = ftxui::InputOption::Default();
|
||||
opt.cursor_position = str->size();
|
||||
opt.multiline = false;
|
||||
return ftxui::Input(str, std::move(opt));
|
||||
}
|
||||
|
||||
ftxui::ButtonOption ascii() { return ftxui::ButtonOption::Ascii(); }
|
||||
|
||||
struct confirmed final : public std::exception
|
||||
{
|
||||
confirmed() noexcept
|
||||
: std::exception()
|
||||
{}
|
||||
|
||||
virtual ~confirmed() noexcept override = default;
|
||||
virtual const char* what() const noexcept override final { return "confirmed send"; }
|
||||
};
|
||||
|
||||
class confirm_ final : public ftxui::ComponentBase
|
||||
{
|
||||
const std::shared_ptr<Monero::PendingTransaction> tx_;
|
||||
const ftxui::Element title_;
|
||||
ftxui::Element info_;
|
||||
ftxui::Element error_;
|
||||
ftxui::Component buttons_;
|
||||
std::future<bool> sending_;
|
||||
unsigned animation_;
|
||||
bool sent_;
|
||||
bool closing_;
|
||||
|
||||
bool Focusable() const override final { return true; }
|
||||
ftxui::Component ActiveChild() override final { return buttons_; }
|
||||
|
||||
public:
|
||||
explicit confirm_(std::shared_ptr<Monero::PendingTransaction>&& tx, dest_group&& dests)
|
||||
: ftxui::ComponentBase(),
|
||||
tx_(std::move(tx)),
|
||||
title_(ftxui::text(_("Sending Tx(es)"))),
|
||||
info_(),
|
||||
error_(),
|
||||
buttons_(),
|
||||
sending_(),
|
||||
animation_(0),
|
||||
sent_(false),
|
||||
closing_(false)
|
||||
{
|
||||
{
|
||||
std::vector<std::vector<ftxui::Element>> grid;
|
||||
grid.reserve(dests.first.size() + 3);
|
||||
|
||||
grid.push_back({ftxui::text(_("Sending: ")), ftxui::text(lwsf::displayAmount(tx_->amount()) + " XMR")});
|
||||
grid.push_back({ftxui::text(_("Fee: ")), ftxui::text(lwsf::displayAmount(tx_->fee()) + " XMR")});
|
||||
{
|
||||
std::vector<std::string> ids = tx_->txid();
|
||||
|
||||
ftxui::Elements rows;
|
||||
rows.reserve(ids.size());
|
||||
|
||||
for (std::string& id : ids)
|
||||
rows.push_back(ftxui::text(std::move(id)));
|
||||
|
||||
grid.push_back({ftxui::text(_("TX IDs:")), ftxui::vbox(std::move(rows))});
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < dests.first.size(); ++i)
|
||||
grid.push_back({ftxui::text(lwsf::displayAmount(dests.second.at(i)) + " XMR to "), ftxui::text(std::move(dests.first.at(i)))});
|
||||
|
||||
info_ = ftxui::gridbox(std::move(grid));
|
||||
}
|
||||
buttons_ = ftxui::Container::Horizontal({
|
||||
ftxui::Button(_("Cancel"), [] () { throw event::close{}; }, ascii()),
|
||||
ftxui::Button(_("Send/Confirm"), [this] () { send_tx(); }, ascii())
|
||||
});
|
||||
|
||||
Add(buttons_);
|
||||
}
|
||||
|
||||
void send_tx()
|
||||
{
|
||||
const auto tx_commit = [] (std::shared_ptr<Monero::PendingTransaction> tx)
|
||||
{
|
||||
return tx->commit();
|
||||
};
|
||||
|
||||
sending_ = std::async(std::launch::async, tx_commit, tx_);
|
||||
}
|
||||
|
||||
bool OnEvent(ftxui::Event event) override final
|
||||
{
|
||||
const bool send_async = event == event::send_async;
|
||||
if (!event.is_mouse() && !send_async)
|
||||
error_.reset();
|
||||
|
||||
try
|
||||
{
|
||||
if (event == ftxui::Event::CtrlQ)
|
||||
throw event::close{};
|
||||
else if (sent_ && send_async)
|
||||
throw confirmed{};
|
||||
else if (closing_ && send_async)
|
||||
throw event::close{};
|
||||
else if (!closing_)
|
||||
buttons_->OnEvent(std::move(event));
|
||||
}
|
||||
catch (const event::close&)
|
||||
{
|
||||
if (!sending_.valid())
|
||||
throw;
|
||||
closing_ = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ftxui::Element OnRender() override final
|
||||
{
|
||||
ftxui::Elements rows;
|
||||
rows.reserve(4);
|
||||
|
||||
bool animate = false;
|
||||
if (sending_.valid())
|
||||
{
|
||||
if (sending_.wait_for(std::chrono::seconds{0}) == std::future_status::ready)
|
||||
{
|
||||
sent_ = sending_.get();
|
||||
if (!sent_)
|
||||
error_ = ftxui::text(tx_->errorString());
|
||||
else
|
||||
error_.reset();
|
||||
|
||||
ftxui::ScreenInteractive* const active = ftxui::ScreenInteractive::Active();
|
||||
if (active)
|
||||
active->PostEvent(event::send_async);
|
||||
}
|
||||
else
|
||||
{
|
||||
animate = true;
|
||||
animation_ = (animation_ + 1) % spinner.size();
|
||||
error_ = ftxui::text(std::string{spinner[animation_]} + _(" Sending ") + spinner[animation_]);
|
||||
ftxui::animation::RequestAnimationFrame();
|
||||
}
|
||||
}
|
||||
|
||||
if (!closing_ && !animate)
|
||||
rows.push_back(buttons_->Render() | ftxui::hcenter);
|
||||
|
||||
if (error_)
|
||||
rows.push_back(decorate::banner(error_) | ftxui::inverted);
|
||||
else
|
||||
rows.push_back(ftxui::separator());
|
||||
|
||||
if (closing_)
|
||||
rows.push_back(ftxui::text(_("...Waiting for Tx Send...")));
|
||||
rows.push_back(info_);
|
||||
|
||||
return ftxui::window(title_, ftxui::vbox(std::move(rows)));
|
||||
}
|
||||
};
|
||||
|
||||
ftxui::Component confirm(std::shared_ptr<Monero::PendingTransaction> tx, dest_group dests)
|
||||
{
|
||||
return std::make_shared<confirm_>(std::move(tx), std::move(dests));
|
||||
}
|
||||
|
||||
ftxui::Component book(std::shared_ptr<Monero::WalletManager> wm, std::shared_ptr<Monero::Wallet> wal, std::shared_ptr<dest_pair> dest)
|
||||
{
|
||||
return nullptr; //return std::make_shared<book_>(std::move(wm), std::move(wal), std::move(dest));
|
||||
}
|
||||
|
||||
class send_ final : public ftxui::ComponentBase
|
||||
{
|
||||
using buttons_tuple =
|
||||
std::tuple<ftxui::Component, ftxui::Component, ftxui::Component, ftxui::Component>;
|
||||
|
||||
const std::shared_ptr<Monero::WalletManager> wm_;
|
||||
const std::shared_ptr<Monero::Wallet> wal_;
|
||||
const ftxui::Element title_;
|
||||
std::vector<std::shared_ptr<dest_pair>> dests_;
|
||||
std::vector<buttons_tuple> dests_ui_;
|
||||
ftxui::Component overlay_;
|
||||
ftxui::Component buttons_;
|
||||
ftxui::Element error_;
|
||||
ftxui::Component ui_;
|
||||
ftxui::Element cached_;
|
||||
std::future<std::optional<std::pair<std::string, std::shared_ptr<dest_pair>>>> oa_;
|
||||
std::future<std::tuple<std::shared_ptr<Monero::PendingTransaction>, dest_group, std::string>> tx_;
|
||||
const std::uint32_t account_;
|
||||
unsigned animation_;
|
||||
bool closing_;
|
||||
|
||||
bool Focusable() const override final { return true; }
|
||||
ftxui::Component ActiveChild() override final
|
||||
{
|
||||
if (overlay_)
|
||||
return overlay_;
|
||||
return ui_;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit send_(std::shared_ptr<Monero::WalletManager>&& wm, std::shared_ptr<Monero::Wallet>&& wal, const std::uint32_t account)
|
||||
: ftxui::ComponentBase(),
|
||||
wm_(std::move(wm)),
|
||||
wal_(std::move(wal)),
|
||||
title_(ftxui::text(_("Send from account #") + std::to_string(account) + " (" + lwsf::displayAmount(wal_->unlockedBalance(account)) + " XMR available)")),
|
||||
dests_(),
|
||||
dests_ui_(),
|
||||
overlay_(),
|
||||
buttons_(),
|
||||
error_(),
|
||||
ui_(),
|
||||
cached_(),
|
||||
oa_(),
|
||||
tx_(),
|
||||
account_(account),
|
||||
animation_(0),
|
||||
closing_(false)
|
||||
{
|
||||
buttons_ = ftxui::Container::Horizontal({
|
||||
ftxui::Button(_("Cancel"), [] () { throw event::close{}; }, ascii()),
|
||||
ftxui::Button(_("Add Dest"), [this] () { add_dest(); }, ascii()),
|
||||
ftxui::Button(_("Construct Tx"), [this] () { try_construct(); }, ascii())
|
||||
});
|
||||
|
||||
add_dest();
|
||||
}
|
||||
|
||||
void add_dest()
|
||||
{
|
||||
dests_.push_back(std::make_shared<std::pair<std::string, std::string>>());
|
||||
|
||||
const std::shared_ptr<dest_pair> dest = dests_.back();
|
||||
const std::size_t elem = dests_ui_.size();
|
||||
dests_ui_.emplace_back(
|
||||
last_input(&dests_.back()->first),
|
||||
last_input(&dests_.back()->second),
|
||||
ftxui::Button(_("Book"), [this, dest] () { overlay_ = book(wm_, wal_, dest); }, ascii()),
|
||||
ftxui::Button(_("Remove"), [this, elem] () { remove_dest(elem); }, ascii())
|
||||
);
|
||||
|
||||
update_ui();
|
||||
}
|
||||
|
||||
void remove_dest(const std::size_t elem)
|
||||
{
|
||||
dests_ui_.erase(dests_ui_.begin() + elem);
|
||||
dests_.erase(dests_.begin() + elem);
|
||||
|
||||
for (std::size_t i = 0; i < dests_ui_.size(); ++i)
|
||||
std::get<3>(dests_ui_.at(i)) = ftxui::Button(_("Remove"), [this, i] () { remove_dest(i); }, ascii());
|
||||
|
||||
update_ui();
|
||||
}
|
||||
|
||||
void update_ui()
|
||||
{
|
||||
ftxui::Components ui;
|
||||
ui.reserve(dests_ui_.size() + 1);
|
||||
ui.push_back(buttons_);
|
||||
for (const auto& e : dests_ui_)
|
||||
ui.push_back(ftxui::Container::Horizontal({std::get<0>(e), std::get<1>(e), std::get<2>(e), std::get<3>(e)}));
|
||||
|
||||
if (ui_)
|
||||
ui_->Detach();
|
||||
ui_ = ftxui::Container::Vertical(std::move(ui));
|
||||
Add(ui_);
|
||||
}
|
||||
|
||||
void try_construct()
|
||||
{
|
||||
if (oa_.valid() || tx_.valid())
|
||||
return;
|
||||
|
||||
if (dests_.empty())
|
||||
{
|
||||
error_ = ftxui::text("Must have one destination");
|
||||
return;
|
||||
}
|
||||
|
||||
dest_group dests;
|
||||
|
||||
dests.first.reserve(dests_.size());
|
||||
dests.second.reserve(dests_.size());
|
||||
|
||||
for (const auto& dest : dests_)
|
||||
{
|
||||
const std::uint64_t amount = lwsf::amountFromString(dest->first);
|
||||
if (!amount)
|
||||
{
|
||||
error_ = ftxui::text("Invalid amount");
|
||||
return;
|
||||
}
|
||||
dests.second.push_back(amount);
|
||||
|
||||
if (!lwsf::addressValid(dest->second, wal_->nettype()))
|
||||
{
|
||||
if (dest->second.find_first_of(u8'.') == std::string::npos)
|
||||
{
|
||||
error_ = ftxui::text(_("Invalid Address/OpenAlias"));
|
||||
return;
|
||||
}
|
||||
const auto oa_lookup = [dest] (std::shared_ptr<Monero::WalletManager> wm, const std::string& uri)
|
||||
-> std::optional<std::pair<std::string, std::shared_ptr<dest_pair>>>
|
||||
{
|
||||
bool dnssec = false;
|
||||
std::string result = wm->resolveOpenAlias(uri, dnssec);
|
||||
if (!dnssec)
|
||||
return std::nullopt;
|
||||
return {{std::move(result), dest}};
|
||||
};
|
||||
|
||||
if (!wm_)
|
||||
throw std::runtime_error{"WalletManager is nullptr"};
|
||||
oa_ = std::async(std::launch::async, oa_lookup, wm_, dest->second);
|
||||
return;
|
||||
}
|
||||
dests.first.push_back(dest->second);
|
||||
}
|
||||
|
||||
const auto tx_construct = [] (std::shared_ptr<Monero::Wallet> wal, dest_group dests, const std::uint32_t account)
|
||||
-> std::tuple<std::shared_ptr<Monero::PendingTransaction>, dest_group, std::string>
|
||||
{
|
||||
const auto dispose = [wal] (Monero::PendingTransaction* ptr)
|
||||
{
|
||||
if (ptr)
|
||||
wal->disposeTransaction(ptr);
|
||||
};
|
||||
|
||||
std::shared_ptr<Monero::PendingTransaction> tx{
|
||||
wal->createTransactionMultDest(dests.first, {}, dests.second, 0 /*mixin_count*/, Monero::PendingTransaction::Priority_Default, account),
|
||||
dispose
|
||||
};
|
||||
if (!tx)
|
||||
return {nullptr, {}, "Unexpected nullptr tx"};
|
||||
if (tx->status() == Monero::PendingTransaction::Status_Ok)
|
||||
return {std::move(tx), std::move(dests), {}};
|
||||
return {nullptr, {}, tx->errorString()};
|
||||
};
|
||||
|
||||
tx_ = std::async(std::launch::async, tx_construct, wal_, std::move(dests), account_);
|
||||
}
|
||||
|
||||
bool OnEvent(ftxui::Event event) override final
|
||||
{
|
||||
const bool is_waiting = oa_.valid() || tx_.valid();
|
||||
try
|
||||
{
|
||||
if (!event.is_mouse() && event != event::send_async)
|
||||
error_.reset();
|
||||
|
||||
if (overlay_)
|
||||
return overlay_->OnEvent(std::move(event));
|
||||
else if (closing_ && event == event::send_async)
|
||||
throw event::close{};
|
||||
else if (event == ftxui::Event::CtrlQ)
|
||||
throw event::close{};
|
||||
else if (!is_waiting)
|
||||
ui_->OnEvent(std::move(event));
|
||||
}
|
||||
catch (const confirmed&)
|
||||
{ throw event::close{}; }
|
||||
catch (const event::close&)
|
||||
{
|
||||
if (!overlay_ && !is_waiting)
|
||||
throw;
|
||||
if (overlay_)
|
||||
overlay_->Detach();
|
||||
else
|
||||
closing_ = true;
|
||||
overlay_.reset();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ftxui::Element OnRender() override final
|
||||
{
|
||||
bool animate = false;
|
||||
if (oa_.valid() || tx_.valid())
|
||||
{
|
||||
animate = true;
|
||||
if (oa_.valid() && oa_.wait_for(std::chrono::seconds{0}) == std::future_status::ready)
|
||||
{
|
||||
auto oa = oa_.get();
|
||||
error_.reset();
|
||||
|
||||
if (!oa)
|
||||
error_ = ftxui::text(_("dnssec failure for domain"));
|
||||
else if (oa->first.empty())
|
||||
error_ = ftxui::text(_("No XMR OpenAlias found"));
|
||||
else if (!lwsf::addressValid(oa->first, wal_->nettype()))
|
||||
error_ = ftxui::text(_("OpenAlias record is invalid"));
|
||||
else if (!closing_)
|
||||
{
|
||||
oa->second->second = std::move(oa->first);
|
||||
try_construct();
|
||||
}
|
||||
|
||||
animate = oa_.valid();
|
||||
}
|
||||
else if (tx_.valid() && tx_.wait_for(std::chrono::seconds{0}) == std::future_status::ready)
|
||||
{
|
||||
animate = false;
|
||||
auto tx = tx_.get();
|
||||
if (std::get<0>(tx))
|
||||
overlay_ = confirm(std::move(std::get<0>(tx)), std::move(std::get<1>(tx)));
|
||||
else
|
||||
error_ = ftxui::text(std::move(std::get<2>(tx)));
|
||||
}
|
||||
|
||||
if (animate)
|
||||
{
|
||||
char const* const label = oa_.valid() ?
|
||||
_(" OpenAlias Lookup ") : _(" Constructing Transaction ");
|
||||
animation_ = (animation_ + 1) % spinner.size();
|
||||
error_ = ftxui::text(std::string{spinner[animation_]} + label + spinner[animation_]);
|
||||
ftxui::animation::RequestAnimationFrame();
|
||||
}
|
||||
else
|
||||
{
|
||||
ftxui::ScreenInteractive* const active = ftxui::ScreenInteractive::Active();
|
||||
if (active)
|
||||
active->PostEvent(event::send_async);
|
||||
}
|
||||
}
|
||||
|
||||
if (!overlay_)
|
||||
{
|
||||
ftxui::Elements rows;
|
||||
rows.reserve(3); // dests_ui_.size() + 2);
|
||||
|
||||
if (!animate)
|
||||
rows.push_back(buttons_->Render() | ftxui::hcenter);
|
||||
|
||||
if (error_)
|
||||
rows.push_back(decorate::banner(error_) | ftxui::inverted);
|
||||
else
|
||||
rows.push_back(ftxui::separator());
|
||||
|
||||
if (!closing_)
|
||||
{
|
||||
std::vector<std::vector<ftxui::Element>> grid;
|
||||
grid.reserve(dests_ui_.size());
|
||||
for (const auto& e : dests_ui_)
|
||||
{
|
||||
ftxui::Elements row;
|
||||
row.reserve(6);
|
||||
row.push_back(std::get<0>(e)->Render());
|
||||
row.push_back(ftxui::text(" XMR to "));
|
||||
row.push_back(std::get<1>(e)->Render());
|
||||
if (!animate)
|
||||
{
|
||||
row.push_back(ftxui::separator());
|
||||
row.push_back(std::get<2>(e)->Render());
|
||||
row.push_back(std::get<3>(e)->Render());
|
||||
}
|
||||
grid.push_back(std::move(row));
|
||||
}
|
||||
rows.push_back(ftxui::gridbox(std::move(grid)));
|
||||
}
|
||||
else
|
||||
rows.push_back(ftxui::text(_("...Cleaning Up...")) | ftxui::hcenter);
|
||||
|
||||
cached_ = ftxui::window(title_, ftxui::vbox(std::move(rows)));
|
||||
return cached_;
|
||||
}
|
||||
return ftxui::dbox(cached_, decorate::overlay(overlay_->Render()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ftxui::Component send(std::shared_ptr<Monero::WalletManager> wm, std::shared_ptr<Monero::Wallet> wal, const std::uint32_t account)
|
||||
{
|
||||
if (!wal)
|
||||
throw std::invalid_argument{"views::send cannot be given nullptr"};
|
||||
return std::make_shared<send_>(std::move(wm), std::move(wal), account);
|
||||
}
|
||||
}} // lwcli // view
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2025, Cifro Codes LLC
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <ftxui/component/component_base.hpp>
|
||||
// 9sMNg6xAhC15Y2r51xthUaHCuKDiFaojSBaSPUrAeBosZBYahFEii7dDq6y3pgaNUzBhvKmxPpWdnPquzsVLkDWB9JM3tiS
|
||||
namespace Monero
|
||||
{
|
||||
class Wallet;
|
||||
class WalletManager;
|
||||
}
|
||||
namespace lwcli { namespace view
|
||||
{
|
||||
//! Shows Transaction History
|
||||
ftxui::Component send(std::shared_ptr<Monero::WalletManager> wm, std::shared_ptr<Monero::Wallet> wallet, std::uint32_t account);
|
||||
}} // lwscli // view
|
||||
|
||||
@@ -47,13 +47,12 @@ namespace lwcli { namespace view
|
||||
{
|
||||
bool set_proxy(Monero::Wallet& wal, const std::string& proxy)
|
||||
{
|
||||
wal.setProxy(proxy);
|
||||
return true;
|
||||
return wal.setProxy(proxy);
|
||||
}
|
||||
|
||||
bool set_url(Monero::Wallet& wal, const std::string& url)
|
||||
{
|
||||
const bool is_ssl = bool(from_string(wal.getCacheAttribute(std::string{config::server::ssl})));
|
||||
const bool is_ssl = bool(from_string(wal.getCacheAttribute(std::string{config::server::ssl})).value_or(0));
|
||||
wal.init(url, 0, "", "", is_ssl, true, wal.getCacheAttribute(std::string{config::server::proxy}));
|
||||
return true;
|
||||
}
|
||||
|
||||
+9
-10
@@ -41,6 +41,7 @@
|
||||
#include "translate.h"
|
||||
#include "views/accounts.h"
|
||||
#include "views/history.h"
|
||||
#include "views/send.h"
|
||||
#include "views/settings.h"
|
||||
|
||||
namespace lwcli { namespace view
|
||||
@@ -51,6 +52,7 @@ namespace lwcli { namespace view
|
||||
|
||||
struct wallet_state
|
||||
{
|
||||
const std::shared_ptr<Monero::WalletManager> wm;
|
||||
const std::shared_ptr<Monero::Wallet> wal;
|
||||
ftxui::Component overlay;
|
||||
std::uint32_t selected_account = 0;
|
||||
@@ -60,8 +62,7 @@ namespace lwcli { namespace view
|
||||
{
|
||||
const std::shared_ptr<Monero::Wallet> wal = state->wal;
|
||||
return ftxui::Container::Horizontal({
|
||||
ftxui::Button("[s]end", [] () {}, ascii()),
|
||||
ftxui::Button("[b]ook", [] () {}, ascii()),
|
||||
ftxui::Button("[s]end", [state] () { state->overlay = send(state->wm, state->wal, state->selected_account); }, ascii()),
|
||||
ftxui::Button("[a]ccounts", [state] () { state->overlay = accounts(state->wal, &state->selected_account); }, ascii()),
|
||||
ftxui::Button("[r]efresh", [wal] () { wal->refreshAsync(); }, ascii()),
|
||||
ftxui::Button("s[e]ttings", [state] () { state->overlay = settings(state->wal); }, ascii())
|
||||
@@ -108,9 +109,9 @@ namespace lwcli { namespace view
|
||||
}
|
||||
|
||||
public:
|
||||
explicit wallet_(std::shared_ptr<Monero::Wallet>&& data)
|
||||
explicit wallet_(std::shared_ptr<Monero::WalletManager>&& wm, std::shared_ptr<Monero::Wallet>&& data)
|
||||
: ftxui::ComponentBase(),
|
||||
state_{std::move(data)},
|
||||
state_{std::move(wm), std::move(data)},
|
||||
title_(nullptr),
|
||||
active_account_(-1),
|
||||
bar_(),
|
||||
@@ -121,7 +122,7 @@ namespace lwcli { namespace view
|
||||
throw std::runtime_error{"Unexpected nullptr Monero wallet"};
|
||||
state_.wal->setListener(this);
|
||||
bar_ = menu_bar(&state_);
|
||||
title_ = ftxui::text(_("lwcli Wallet (Primary ") + state_.wal->mainAddress() + ")");
|
||||
title_ = ftxui::text(_("lwcli Wallet (Primary ") + state_.wal->mainAddress().substr(0, 40) + "...)");
|
||||
update_account();
|
||||
}
|
||||
|
||||
@@ -158,9 +159,7 @@ namespace lwcli { namespace view
|
||||
else if (!ui_->OnEvent(event))
|
||||
{
|
||||
if (event == ftxui::Event::s || event == ftxui::Event::S)
|
||||
; // state_.overlay = send(state_.wal);
|
||||
else if (event == ftxui::Event::b || event == ftxui::Event::B)
|
||||
; // state_.overlay = book(state_.wal);
|
||||
state_.overlay = send(state_.wm, state_.wal, state_.selected_account);
|
||||
else if (event == ftxui::Event::a || event == ftxui::Event::a)
|
||||
state_.overlay = accounts(state_.wal, &state_.selected_account);
|
||||
else if (event == ftxui::Event::r || event == ftxui::Event::R)
|
||||
@@ -214,8 +213,8 @@ namespace lwcli { namespace view
|
||||
};
|
||||
} // anonymous
|
||||
|
||||
ftxui::Component wallet(std::shared_ptr<Monero::Wallet> data)
|
||||
ftxui::Component wallet(std::shared_ptr<Monero::WalletManager> wm, std::shared_ptr<Monero::Wallet> data)
|
||||
{
|
||||
return std::make_shared<wallet_>(std::move(data));
|
||||
return std::make_shared<wallet_>(std::move(wm), std::move(data));
|
||||
}
|
||||
}} // lwcli // view
|
||||
|
||||
+7
-2
@@ -30,9 +30,14 @@
|
||||
#include <cstdint>
|
||||
#include <ftxui/component/component_base.hpp>
|
||||
|
||||
namespace Monero { class Wallet; }
|
||||
namespace Monero
|
||||
{
|
||||
class Wallet;
|
||||
class WalletManager;
|
||||
}
|
||||
|
||||
namespace lwcli { namespace view
|
||||
{
|
||||
ftxui::Component wallet(std::shared_ptr<Monero::Wallet> wallet);
|
||||
ftxui::Component wallet(std::shared_ptr<Monero::WalletManager> wm, std::shared_ptr<Monero::Wallet> wallet);
|
||||
}} // lwscli // view
|
||||
|
||||
|
||||
Reference in New Issue
Block a user