Update lwsf pin, and added limited account support

This commit is contained in:
Lee Clagett
2025-04-20 21:29:51 -04:00
parent e7f1550808
commit 50ff86c56e
12 changed files with 715 additions and 143 deletions
+1 -1
+1
View File
@@ -27,6 +27,7 @@
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
include_directories(.)
add_subdirectory(components)
add_subdirectory(decorate)
add_subdirectory(views)
+34
View File
@@ -0,0 +1,34 @@
# 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.
set(lwcli-components_sources table.cpp)
set(lwscli-components_headers table.h)
add_library(lwcli-components ${lwcli-components_sources} ${lwcli-components_headers})
target_link_libraries(lwcli-components PRIVATE component dom)
+141
View File
@@ -0,0 +1,141 @@
// 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 <ctime>
#include <ftxui/component/component.hpp>
#include <ftxui/component/event.hpp>
#include <ftxui/dom/table.hpp>
#include "decorate/overlay.h"
#include "table.h"
namespace lwcli { namespace component
{
namespace
{
class table_ final : public ftxui::ComponentBase
{
const table_generator generator_;
const table_enter enter_;
const std::vector<std::vector<std::string>> title_;
std::ptrdiff_t rows_;
std::ptrdiff_t selected_;
static constexpr std::ptrdiff_t min_row() noexcept { return 0; };
bool Focusable() const override final { return true; }
static std::vector<std::vector<std::string>> get_title_bar(std::vector<std::string>&& title)
{
std::vector<std::vector<std::string>> out;
out.reserve(2);
const std::size_t count = title.size();
out.push_back(std::move(title));
out.push_back(std::vector<std::string>(count));
return out;
}
public:
explicit table_(std::vector<std::string>&& title, table_generator&& generator, table_enter&& enter)
: ftxui::ComponentBase(),
generator_(std::move(generator)),
enter_(std::move(enter)),
title_(get_title_bar(std::move(title))),
rows_(0),
selected_(-1)
{
set_size(generator_().size());
}
void set_size(const std::size_t rows)
{
if (std::numeric_limits<std::ptrdiff_t>::max() < rows)
throw std::runtime_error{"lwcli::component::table exceeds max size"};
rows_ = rows;
}
bool OnEvent(ftxui::Event event) override final
{
if (event == ftxui::Event::ArrowDown && selected_ + min_row() < rows_)
++selected_;
else if (event == ftxui::Event::ArrowUp && min_row() <= selected_)
--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_));
return min_row() <= selected_ && selected_ - min_row() < rows_;
}
ftxui::Element OnRender() override final
{
auto rows = generator_();
set_size(rows.size());
rows.reserve(rows.size() + title_.size());
rows.insert(rows.begin(), title_.begin(), title_.end());
ftxui::Table table{std::move(rows)};
{
auto title = table.SelectRow(0);
title.Decorate(ftxui::bold);
title.SeparatorVertical(ftxui::LIGHT);
//title.Border(ftxui::LIGHT);
}
if (Focused())
{
if (selected_ < min_row())
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);
}
}
return table.Render();
}
};
} // anonymous
ftxui::Component table(std::vector<std::string> title, table_generator generator, table_enter enter)
{
if (!generator || !enter)
throw std::invalid_argument{"lwcli::components::table was given nullptr"};
return std::make_shared<table_>(std::move(title), std::move(generator), std::move(enter));
}
}} // lwcli // view
+46
View File
@@ -0,0 +1,46 @@
// 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/dom/elements.hpp>
#include <ftxui/component/component_base.hpp>
#include <functional>
#include <string>
#include <vector>
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);
}} // lwscli // view
+2
View File
@@ -50,6 +50,8 @@ namespace lwcli { namespace config
{ return ( ... && verify_sso(strs)); }
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};
+3 -3
View File
@@ -26,9 +26,9 @@
# 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 history.cpp keys.cpp manager.cpp settings.cpp wallet.cpp)
set(lwscli-views_headers history.h keys.h manager.h settings.h wallet.h)
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)
add_library(lwcli-views ${lwcli-views_sources} ${lwcli-views_headers})
target_link_libraries(lwcli-views PRIVATE component dom lwcli-decorate lwsf-api)
target_link_libraries(lwcli-views PRIVATE component dom lwcli-components lwcli-decorate lwsf-api)
+324
View File
@@ -0,0 +1,324 @@
// 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 <ftxui/component/component.hpp>
#include <ftxui/component/event.hpp>
#include <ftxui/dom/table.hpp>
#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
{
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(); }
class account_detail final : public ftxui::ComponentBase
{
std::string account_name_;
const std::shared_ptr<Monero::Wallet> wal_;
Monero::Subaddress* const acct_;
const ftxui::Element title_;
const ftxui::Elements address_;
const ftxui::Element desc_;
ftxui::Component details_;
ftxui::Component table_;
ftxui::Component buttons_;
const ftxui::Component name_;
ftxui::Component ui_;
std::unordered_map<std::size_t, std::size_t> row_map_;
const std::uint32_t id_;
bool Focusable() const override final { return true; }
ftxui::Component ActiveChild() override final
{
if (details_)
return details_;
return ui_;
}
public:
explicit account_detail(std::shared_ptr<Monero::Wallet> wal, Monero::Subaddress* acct, std::size_t id)
: ftxui::ComponentBase(),
account_name_(wal->getSubaddressLabel(id, 0)),
wal_(std::move(wal)),
acct_(acct),
title_(ftxui::text(_("Account #") + std::to_string(id))),
address_({ftxui::text("Address: "), ftxui::text(wal_->address(id, 0))}),
desc_(ftxui::text(_("Name: "))),
details_(),
table_(),
buttons_(),
name_(last_input(&account_name_)),
ui_(),
id_(id)
{
if (!acct_)
throw std::runtime_error{"lwcli::account_detail given nullptr"};
if (std::numeric_limits<std::uint32_t>::max() < id_)
throw std::runtime_error{"lwcli::account_detail given invalid id"};
buttons_ = ftxui::Container::Horizontal({
ftxui::Button(_("Cancel"), [] () { throw event::close{}; }, ascii()),
ftxui::Button(_("Save"), [this] () {
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())
});
table_ = component::table(
{_("#"), _("Label"), _("Address")},
[this] () { return subaddress_list(); },
[this] (std::size_t i) { return display_details(i); }
);
ui_ = ftxui::Container::Vertical({buttons_, name_, table_});
Add(ui_);
}
bool display_details(const std::size_t index)
{
if (row_map_.find(index) == row_map_.end())
throw std::logic_error{"DEBUG"};
return true;
}
bool OnEvent(ftxui::Event event) override final
{
try
{
if (details_)
return details_->OnEvent(std::move(event));
else if (event == ftxui::Event::CtrlQ)
throw event::close{};
ui_->OnEvent(std::move(event));
}
catch (const event::close&)
{
if (!details_)
throw;
details_->Detach();
details_.reset();
}
return true;
}
std::vector<std::vector<std::string>> subaddress_list()
{
std::vector<std::vector<std::string>> rows{};
acct_->refresh(id_);
auto all = acct_->getAll();
// reverse order sort
std::sort(all.begin(), all.end(), [] (const auto x, const auto y) {
return y->getRowId() < x->getRowId();
});
std::size_t i = 0;
rows.reserve(all.size());
for (Monero::SubaddressRow const* const detail : all)
{
const std::size_t id = detail->getRowId();
row_map_.try_emplace(i++).first->second = id;
rows.push_back({
std::to_string(id),
detail->getLabel(),
detail->getAddress().substr(0, 20) + "..."
});
}
return rows;
}
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
}));
}
};
class accounts_ final : public ftxui::ComponentBase
{
const std::shared_ptr<Monero::Wallet> wal_;
Monero::SubaddressAccount* const wal_accounts_;
std::uint32_t* const account_;
const ftxui::Element title_;
ftxui::Component details_;
ftxui::Component table_;
ftxui::Element table_cached_;
ftxui::Component buttons_;
ftxui::Component ui_;
std::unordered_map<std::size_t, std::size_t> row_map_;
bool Focusable() const override final { return true; }
ftxui::Component ActiveChild() override final
{
if (details_)
return details_;
return ui_;
}
public:
explicit accounts_(std::shared_ptr<Monero::Wallet>&& wal, std::uint32_t* account)
: ftxui::ComponentBase(),
wal_(std::move(wal)),
wal_accounts_(wal_->subaddressAccount()),
account_(account),
title_(ftxui::text(_("Accounts"))),
details_(),
table_(),
table_cached_(),
buttons_(),
ui_(),
row_map_()
{
buttons_ = ftxui::Container::Horizontal({
ftxui::Button(_("Close"), [] () { throw event::close{}; }, ascii()),
ftxui::Button(_("Add Account"), [this] () {
wal_->addSubaddressAccount(std::string{config::default_account_name});
}, ascii()),
});
table_ = component::table(
{_("#"), _("Balance"), _("Label"), _("Address")},
[this] () { return accounts_list(); },
[this] (std::size_t i) { return display_details(i); }
);
ui_ = ftxui::Container::Vertical({buttons_, table_});
Add(ui_);
}
bool display_details(const std::size_t index)
{
if (details_)
details_->Detach();
details_ = std::make_shared<account_detail>(wal_, wal_->subaddress(), row_map_.at(index));
return true;
}
bool OnEvent(ftxui::Event event) override final
{
try
{
if (details_)
return details_->OnEvent(std::move(event));
else if (event == ftxui::Event::CtrlQ)
throw event::close{};
ui_->OnEvent(std::move(event));
}
catch (const event::close&)
{
if (!details_)
throw;
details_->Detach();
details_.reset();
}
return true;
}
std::vector<std::vector<std::string>> accounts_list()
{
std::vector<std::vector<std::string>> rows{};
wal_accounts_->refresh();
auto all = wal_accounts_->getAll();
// reverse order sort
std::sort(all.begin(), all.end(), [] (const auto x, const auto y) {
return y->getRowId() < x->getRowId();
});
std::size_t i = 0;
rows.reserve(all.size());
for (Monero::SubaddressAccountRow const* const detail : all)
{
const std::size_t id = detail->getRowId();
char const* const active = id == *account_ ? "*" : "";
row_map_.try_emplace(i++).first->second = id;
rows.push_back({
active + std::to_string(id),
detail->getBalance() + " XMR",
detail->getLabel(),
detail->getAddress().substr(0, 12) + "..."
});
}
return rows;
}
ftxui::Element OnRender() override final
{
if (!details_)
{
table_cached_ = ftxui::window(title_, ftxui::vbox({
buttons_->Render() | ftxui::hcenter,
ftxui::separator(),
table_->Render() //| ftxui::vscroll_indicator | ftxui::yframe | ftxui::center
}));
return table_cached_;
}
return ftxui::dbox(table_cached_, decorate::overlay(details_->Render()));
}
};
}
ftxui::Component accounts(std::shared_ptr<Monero::Wallet> wal, std::uint32_t* account)
{
if (!wal || !account)
throw std::invalid_argument{"views::accounts cannot be given nullptr"};
return std::make_shared<accounts_>(std::move(wal), account);
}
}} // lwcli //
+39
View File
@@ -0,0 +1,39 @@
// 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>
#include <memory>
namespace Monero { class Wallet; }
namespace lwcli { namespace view
{
ftxui::Component accounts(std::shared_ptr<Monero::Wallet> wallet, std::uint32_t* account);
}} // lwscli // view
+114 -129
View File
@@ -34,6 +34,7 @@
#include <map>
#include <unordered_map>
#include "components/table.h"
#include "decorate/overlay.h"
#include "events.h"
#include "history.h"
@@ -244,27 +245,30 @@ namespace lwcli { namespace view
class history_ final : public ftxui::ComponentBase
{
const std::shared_ptr<Monero::Wallet> wallet_;
ftxui::Component table_;
ftxui::Component overlay_;
ftxui::Element title_;
ftxui::Element table_; //!< Does not redraw when child is enabled
std::vector<std::vector<std::string>> base_rows;
std::ptrdiff_t highlighted_;
std::unordered_map<std::size_t, int> row_map_;
ftxui::Element table_cached_; //!< Does not redraw when child is enabled
std::unordered_map<std::size_t, std::size_t> row_map_;
std::uint64_t balance_;
const std::uint32_t account_;
static constexpr std::size_t min_row() noexcept { return 2; };
bool Focusable() const override final { return true; }
ftxui::Component ActiveChild() override final { return overlay_; }
ftxui::Component ActiveChild() override final
{
if (overlay_)
return overlay_;
return table_;
}
public:
explicit history_(std::shared_ptr<Monero::Wallet>&& wallet, std::uint32_t account)
: ftxui::ComponentBase(),
wallet_(std::move(wallet)),
table_(),
overlay_(nullptr),
title_(nullptr),
table_(nullptr),
base_rows(),
highlighted_(min_row() - 1),
table_cached_(nullptr),
row_map_(),
account_(account)
{
@@ -281,10 +285,22 @@ namespace lwcli { namespace view
title_ = ftxui::text(std::move(address));
// perform transalation lookup once
base_rows = {
table_ = component::table(
{_("Date"), _("Amount"), _("Payment ID"), _("Desription"), _("Block"), _("Fee"), _("Hash")},
{ "", "", "", "", "", "", "" }
};
[this] () { return transaction_list(); },
[this] (std::size_t i) { return add_overlay(i); }
);
Add(table_);
}
bool add_overlay(const std::size_t i)
{
if (overlay_)
overlay_->Detach();
overlay_ = std::make_shared<tx_details>(wallet_->history(), row_map_.at(i));
Add(overlay_);
return true;
}
bool OnEvent(ftxui::Event event) override final
@@ -300,19 +316,8 @@ namespace lwcli { namespace view
throw event::close{};
else if (event.is_character())
return false;
else if (event == ftxui::Event::ArrowDown && highlighted_ < row_map_.size() + min_row() - 1)
++highlighted_;
else if (event == ftxui::Event::ArrowUp && min_row() <= highlighted_)
--highlighted_;
else if (event == ftxui::Event::PageDown)
highlighted_ = std::min(std::ptrdiff_t(row_map_.size() + min_row()), highlighted_ + 10);
else if (event == ftxui::Event::PageUp)
highlighted_ = std::max(std::ptrdiff_t(min_row()), highlighted_ - 10);
else if (event == ftxui::Event::Return)
{
overlay_ = ftxui::Make<tx_details>(wallet_->history(), row_map_.at(highlighted_ - min_row()));
Add(overlay_);
}
else
return table_->OnEvent(std::move(event));
}
catch (const event::close&)
{
@@ -321,121 +326,101 @@ namespace lwcli { namespace view
overlay_->Detach();
overlay_.reset();
}
return min_row() <= highlighted_ && highlighted_ - min_row() < row_map_.size();
return true;
}
std::vector<std::vector<std::string>> transaction_list()
{
Monero::TransactionHistory* tx_history = wallet_->history();
if (!tx_history)
throw std::runtime_error{"unexpected history nullptr"};
std::map<std::pair<std::uint64_t, std::string>, std::pair<const Monero::TransactionInfo*, std::size_t>, std::greater<>> ordered;
tx_history->refresh();
const auto history = tx_history->getAll();
for (std::size_t i = 0; i < history.size(); ++i)
{
const Monero::TransactionInfo* tx = history[i];
if (!tx)
throw std::runtime_error{"unexpected tx_info nullptr"};
if (tx->subaddrAccount() == account_)
ordered.try_emplace({tx->blockHeight(), tx->hash()}, tx, i);
}
balance_ = 0;
std::vector<std::vector<std::string>> rows;
rows.reserve(ordered.size() + 2);
std::size_t i = 0;
row_map_.clear();
for (const auto& entry : ordered)
{
const Monero::TransactionInfo* tx = entry.second.first;
row_map_.emplace(i, entry.second.second);
const std::uint64_t amount = tx->amount();
const int direction = tx->direction();
const std::uint64_t fee = tx->fee();
if (!tx->isFailed())
{
if (direction == Monero::TransactionInfo::Direction_Out)
{
balance_ -= amount;
balance_ -= fee;
}
else
balance_ += amount;
}
std::tm expanded{};
const std::time_t timestamp = tx->timestamp();
if (!gmtime_r(std::addressof(timestamp), std::addressof(expanded)))
throw std::runtime_error{"gmtime failure"};
char date[12] = {0};
if (sizeof(date) - 1 != std::strftime(date, sizeof(date), "%Y/%m/%d ", std::addressof(expanded)))
throw std::runtime_error{"strftime failed"};
std::string payment_id = tx->paymentId();
if (payment_id.size() == 16 && payment_id.find_first_not_of('0') == std::string::npos)
payment_id.clear();
char const* const extended_payment_id = 16 < payment_id.size() ?
"..." : "";
rows.push_back({
std::string{date},
print_amount(amount, direction),
payment_id.substr(0, 16) + extended_payment_id,
tx->description(),
std::to_string(tx->blockHeight()),
print_money(tx->fee()),
tx->hash().substr(0, 16) + "..."
});
++i;
}
return rows;
}
ftxui::Element OnRender() override final
{
if (Focused())
{
if (highlighted_ < min_row())
highlighted_ = min_row();
else if (row_map_.size() <= highlighted_ - min_row())
highlighted_ = row_map_.size() - 1 + min_row();
}
/* Do not redraw table when showing tx. the
history()->refresh() call can invalidate pointers */
if (!overlay_)
{
Monero::TransactionHistory* tx_history = wallet_->history();
if (!tx_history)
throw std::runtime_error{"unexpected history nullptr"};
std::map<std::pair<std::uint64_t, std::string>, std::pair<const Monero::TransactionInfo*, std::size_t>, std::greater<>> ordered;
tx_history->refresh();
const auto history = tx_history->getAll();
for (std::size_t i = 0; i < history.size(); ++i)
{
const Monero::TransactionInfo* tx = history[i];
if (!tx)
throw std::runtime_error{"unexpected tx_info nullptr"};
if (tx->subaddrAccount() == account_)
ordered.try_emplace({tx->blockHeight(), tx->hash()}, tx, i);
}
std::uint64_t balance = 0;
std::vector<std::vector<std::string>> rows;
rows.reserve(ordered.size() + 2);
rows = base_rows;
std::size_t i = 0;
row_map_.clear();
for (const auto& entry : ordered)
{
const Monero::TransactionInfo* tx = entry.second.first;
row_map_.emplace(i, entry.second.second);
const std::uint64_t amount = tx->amount();
const int direction = tx->direction();
const std::uint64_t fee = tx->fee();
if (!tx->isFailed())
{
if (direction == Monero::TransactionInfo::Direction_Out)
{
balance -= amount;
balance -= fee;
}
else
balance += amount;
}
std::tm expanded{};
const std::time_t timestamp = tx->timestamp();
if (!gmtime_r(std::addressof(timestamp), std::addressof(expanded)))
throw std::runtime_error{"gmtime failure"};
char date[12] = {0};
if (sizeof(date) - 1 != std::strftime(date, sizeof(date), "%Y/%m/%d ", std::addressof(expanded)))
throw std::runtime_error{"strftime failed"};
std::string payment_id = tx->paymentId();
if (payment_id.size() == 16 && payment_id.find_first_not_of('0') == std::string::npos)
payment_id.clear();
char const* const extended_payment_id = 16 < payment_id.size() ?
"..." : "";
rows.push_back({
std::string{date},
print_amount(amount, direction),
payment_id.substr(0, 16) + extended_payment_id,
tx->description(),
std::to_string(tx->blockHeight()),
print_money(tx->fee()),
tx->hash().substr(0, 16) + "..."
});
++i;
}
ftxui::Table table{std::move(rows)};
table.SelectRow(0).Decorate(ftxui::bold);
table.SelectRow(0).SeparatorVertical(ftxui::LIGHT);
table.SelectRow(0).Border(ftxui::LIGHT);
//table.SelectColumn(3).DecorateCells(ftxui::xflex_grow);
if (min_row() <= highlighted_ && highlighted_ - min_row() < row_map_.size())
{
auto row = table.SelectRow(highlighted_);
row.Decorate(ftxui::inverted);
if (Focused())
row.Decorate(ftxui::focus);
else if (Active())
row.Decorate(ftxui::select);
}
table_ = ftxui::vbox({
auto table = table_->Render(); // compute balance first in callback
table_cached_ = ftxui::vbox({
title_,
ftxui::text(_("Balance: ") + print_money(balance)),
table.Render() | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center | ftxui::flex
//ftxui::flex(ftxui::yframe(ftxui::vscroll_indicator(table.Render())))
ftxui::text(_("Balance: ") + print_money(balance_)),
std::move(table) | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center | ftxui::flex
});
return table_;
return table_cached_;
}
return ftxui::dbox(table_, decorate::overlay(overlay_->Render()));
return ftxui::dbox(table_cached_, decorate::overlay(overlay_->Render()));
}
};
} // anonymous
+3 -3
View File
@@ -31,7 +31,6 @@
#include <charconv>
#include <ftxui/component/component.hpp>
#include <ftxui/component/event.hpp>
#include <ftxui/dom/table.hpp>
#include <lws_frontend.h>
#include "decorate/overlay.h"
@@ -73,8 +72,7 @@ namespace lwcli { namespace view
const auto is_ssl = from_string(ssl);
if (!is_ssl)
return false;
// no way to forcibly select ssl without another init call. hopefully doesn't break things
wal.init(wal.getCacheAttribute(std::string{config::server::url}), 0, "", "", bool(*is_ssl), true, wal.getCacheAttribute(std::string{config::server::proxy}));
return true;
}
@@ -251,6 +249,8 @@ namespace lwcli { namespace view
ftxui::Component settings(std::shared_ptr<Monero::Wallet> wal)
{
if (!wal)
throw std::invalid_argument{"view::settings cannot be given nullptr"};
return std::make_shared<settings_>(std::move(wal));
}
+7 -7
View File
@@ -39,6 +39,7 @@
#include "events.h"
#include "lwcli_config.h"
#include "translate.h"
#include "views/accounts.h"
#include "views/history.h"
#include "views/settings.h"
@@ -52,6 +53,7 @@ namespace lwcli { namespace view
{
const std::shared_ptr<Monero::Wallet> wal;
ftxui::Component overlay;
std::uint32_t selected_account = 0;
};
ftxui::Component menu_bar(wallet_state* state)
@@ -60,7 +62,7 @@ namespace lwcli { namespace view
return ftxui::Container::Horizontal({
ftxui::Button("[s]end", [] () {}, ascii()),
ftxui::Button("[b]ook", [] () {}, ascii()),
ftxui::Button("[a]ccounts", [] () {}, 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())
});
@@ -71,7 +73,6 @@ namespace lwcli { namespace view
wallet_state state_;
ftxui::Element title_;
std::uint32_t active_account_;
std::uint32_t selected_account_;
ftxui::Component bar_;
ftxui::Component ui_;
ftxui::Component history_;
@@ -112,7 +113,6 @@ namespace lwcli { namespace view
state_{std::move(data)},
title_(nullptr),
active_account_(-1),
selected_account_(0),
bar_(),
ui_(),
history_(nullptr)
@@ -132,15 +132,15 @@ namespace lwcli { namespace view
void update_account()
{
if (active_account_ != selected_account_)
if (active_account_ != state_.selected_account)
{
if (ui_)
ui_->Detach();
history_ = view::history(state_.wal, selected_account_);
history_ = view::history(state_.wal, state_.selected_account);
ui_ = ftxui::Container::Vertical({bar_, history_});
Add(ui_);
}
active_account_ = selected_account_;
active_account_ = state_.selected_account;
}
bool OnEvent(ftxui::Event event) override final
@@ -162,7 +162,7 @@ namespace lwcli { namespace view
else if (event == ftxui::Event::b || event == ftxui::Event::B)
; // state_.overlay = book(state_.wal);
else if (event == ftxui::Event::a || event == ftxui::Event::a)
; // overlay = accounts(state_.wal, &active_account_);
state_.overlay = accounts(state_.wal, &state_.selected_account);
else if (event == ftxui::Event::r || event == ftxui::Event::R)
state_.wal->refreshAsync();
else if (event == ftxui::Event::e || event == ftxui::Event::E)