feat(market): per-group configurable price data
Each portfolio group can now configure how it is valued and which price fields its card shows: - Price basis: Market · USD, Market · BTC, DRGX only, or Manual (a custom price-per-DRGX + currency label). Manual mode values the group independently of the live DRGX market price. - Field toggles: show DRGX amount / show value / show 24h change (24h enabled only for the live-market bases; value disabled for DRGX-only). - The editor's left "Appearance" column gains a Price section (basis combo, manual price + currency inputs, field checkboxes); the live preview and the group cards both render through one pfBuildDisplay() helper so they stay in sync. Defaults preserve the prior behavior (DRGX + USD). Persisted in settings.json (price_basis / manual_price / manual_currency / show_drgx / show_value / show_24h). Note: "price source per exchange/pair" is limited to USD/BTC/Manual — the app only fetches the aggregate DRGX price (USD + BTC); ExchangePair carries no price, so valuing against an arbitrary pair's live price would need a new market fetch (can be added separately). Full-node + Lite build clean; ctest 1/1; hygiene clean. Rendering change — needs a screenshot check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -318,6 +318,18 @@ bool Settings::load(const std::string& path)
|
|||||||
entry.icon = e["icon"].get<std::string>();
|
entry.icon = e["icon"].get<std::string>();
|
||||||
if (e.contains("color") && e["color"].is_number())
|
if (e.contains("color") && e["color"].is_number())
|
||||||
entry.color = e["color"].get<unsigned int>();
|
entry.color = e["color"].get<unsigned int>();
|
||||||
|
if (e.contains("price_basis") && e["price_basis"].is_number_integer())
|
||||||
|
entry.priceBasis = e["price_basis"].get<int>();
|
||||||
|
if (e.contains("manual_price") && e["manual_price"].is_number())
|
||||||
|
entry.manualPrice = e["manual_price"].get<double>();
|
||||||
|
if (e.contains("manual_currency") && e["manual_currency"].is_string())
|
||||||
|
entry.manualCurrency = e["manual_currency"].get<std::string>();
|
||||||
|
if (e.contains("show_drgx") && e["show_drgx"].is_boolean())
|
||||||
|
entry.showDrgx = e["show_drgx"].get<bool>();
|
||||||
|
if (e.contains("show_value") && e["show_value"].is_boolean())
|
||||||
|
entry.showValue = e["show_value"].get<bool>();
|
||||||
|
if (e.contains("show_24h") && e["show_24h"].is_boolean())
|
||||||
|
entry.show24h = e["show_24h"].get<bool>();
|
||||||
if (!entry.label.empty()) portfolio_entries_.push_back(std::move(entry));
|
if (!entry.label.empty()) portfolio_entries_.push_back(std::move(entry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -469,6 +481,12 @@ bool Settings::save(const std::string& path)
|
|||||||
for (const auto& a : e.addresses) entry["addresses"].push_back(a);
|
for (const auto& a : e.addresses) entry["addresses"].push_back(a);
|
||||||
entry["icon"] = e.icon;
|
entry["icon"] = e.icon;
|
||||||
entry["color"] = e.color;
|
entry["color"] = e.color;
|
||||||
|
entry["price_basis"] = e.priceBasis;
|
||||||
|
entry["manual_price"] = e.manualPrice;
|
||||||
|
entry["manual_currency"] = e.manualCurrency;
|
||||||
|
entry["show_drgx"] = e.showDrgx;
|
||||||
|
entry["show_value"] = e.showValue;
|
||||||
|
entry["show_24h"] = e.show24h;
|
||||||
j["portfolio_entries"].push_back(std::move(entry));
|
j["portfolio_entries"].push_back(std::move(entry));
|
||||||
}
|
}
|
||||||
j["font_scale"] = font_scale_;
|
j["font_scale"] = font_scale_;
|
||||||
|
|||||||
@@ -80,6 +80,15 @@ public:
|
|||||||
std::vector<std::string> addresses;
|
std::vector<std::string> addresses;
|
||||||
std::string icon; // project_icons wallet-icon name; empty = no icon
|
std::string icon; // project_icons wallet-icon name; empty = no icon
|
||||||
unsigned int color = 0; // packed IM_COL32 accent; 0 = theme default
|
unsigned int color = 0; // packed IM_COL32 accent; 0 = theme default
|
||||||
|
// Per-group price data. priceBasis: 0 = live market (USD), 1 = live market (BTC),
|
||||||
|
// 2 = DRGX only (no fiat value), 3 = manual price. Defaults preserve prior behavior
|
||||||
|
// (show DRGX + USD value).
|
||||||
|
int priceBasis = 0;
|
||||||
|
double manualPrice = 0.0; // price per DRGX for the Manual basis
|
||||||
|
std::string manualCurrency = "USD";
|
||||||
|
bool showDrgx = true; // show the DRGX amount on the card
|
||||||
|
bool showValue = true; // show the converted/fiat value on the card
|
||||||
|
bool show24h = false; // show the 24h % change (live-market bases only)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Theme
|
// Theme
|
||||||
|
|||||||
@@ -103,6 +103,50 @@ static char s_pf_search[64] = {0}; // address-list filter text
|
|||||||
static int s_pf_type_filter = 0; // 0 = all, 1 = shielded, 2 = transparent
|
static int s_pf_type_filter = 0; // 0 = all, 1 = shielded, 2 = transparent
|
||||||
static bool s_pf_funded_only = false;
|
static bool s_pf_funded_only = false;
|
||||||
static float s_pf_custom_rgb[3] = {0.31f, 0.62f, 1.0f}; // working RGB for the custom color picker
|
static float s_pf_custom_rgb[3] = {0.31f, 0.62f, 1.0f}; // working RGB for the custom color picker
|
||||||
|
// Price-data config (working copy while editing a group).
|
||||||
|
static int s_pf_price_basis = 0;
|
||||||
|
static double s_pf_manual_price = 0.0;
|
||||||
|
static char s_pf_manual_ccy[12] = "USD";
|
||||||
|
static bool s_pf_show_drgx = true;
|
||||||
|
static bool s_pf_show_value = true;
|
||||||
|
static bool s_pf_show_24h = false;
|
||||||
|
|
||||||
|
// Build the right-hand amount strings for a group card / preview from its price-data config.
|
||||||
|
struct PfDisplay {
|
||||||
|
std::string primary; // name-row, top-right (DRGX amount or the value)
|
||||||
|
std::string secondary; // below primary (the value, when DRGX is also shown)
|
||||||
|
std::string change; // "+2.34%" (live-market bases only) or empty
|
||||||
|
ImU32 changeCol = 0;
|
||||||
|
};
|
||||||
|
static PfDisplay pfBuildDisplay(const config::Settings::PortfolioEntry& e, double bal,
|
||||||
|
const MarketInfo& market)
|
||||||
|
{
|
||||||
|
PfDisplay d;
|
||||||
|
char b[80];
|
||||||
|
std::string valueStr; bool hasValue = false;
|
||||||
|
if (e.priceBasis == 0 && market.price_usd > 0) {
|
||||||
|
snprintf(b, sizeof(b), "$%.2f", bal * market.price_usd); valueStr = b; hasValue = true;
|
||||||
|
} else if (e.priceBasis == 1 && market.price_btc > 0) {
|
||||||
|
snprintf(b, sizeof(b), "%.8f BTC", bal * market.price_btc); valueStr = b; hasValue = true;
|
||||||
|
} else if (e.priceBasis == 3 && e.manualPrice > 0.0) {
|
||||||
|
snprintf(b, sizeof(b), "%.2f %s", bal * e.manualPrice, e.manualCurrency.c_str());
|
||||||
|
valueStr = b; hasValue = true;
|
||||||
|
}
|
||||||
|
std::string drgxStr;
|
||||||
|
if (e.showDrgx) { snprintf(b, sizeof(b), "%.4f %s", bal, DRAGONX_TICKER); drgxStr = b; }
|
||||||
|
if (e.showDrgx) {
|
||||||
|
d.primary = drgxStr;
|
||||||
|
if (e.showValue && hasValue) d.secondary = valueStr;
|
||||||
|
} else if (e.showValue && hasValue) {
|
||||||
|
d.primary = valueStr;
|
||||||
|
}
|
||||||
|
if (e.show24h && (e.priceBasis == 0 || e.priceBasis == 1) && market.change_24h != 0.0) {
|
||||||
|
snprintf(b, sizeof(b), "%+.2f%%", market.change_24h);
|
||||||
|
d.change = b;
|
||||||
|
d.changeCol = market.change_24h >= 0 ? material::Success() : material::Error();
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
// Preset accent palette for portfolio groups (0 slot = "default / no color").
|
// Preset accent palette for portfolio groups (0 slot = "default / no color").
|
||||||
static const ImU32 kPfPalette[] = {
|
static const ImU32 kPfPalette[] = {
|
||||||
@@ -122,15 +166,28 @@ static void PortfolioBeginEdit(App* app, int index)
|
|||||||
const auto& entries = app->settings()->getPortfolioEntries();
|
const auto& entries = app->settings()->getPortfolioEntries();
|
||||||
s_pf_editing = index;
|
s_pf_editing = index;
|
||||||
if (index >= 0 && index < (int)entries.size()) {
|
if (index >= 0 && index < (int)entries.size()) {
|
||||||
snprintf(s_pf_label, sizeof(s_pf_label), "%s", entries[index].label.c_str());
|
const auto& src = entries[index];
|
||||||
s_pf_addrs = entries[index].addresses;
|
snprintf(s_pf_label, sizeof(s_pf_label), "%s", src.label.c_str());
|
||||||
s_pf_icon = entries[index].icon;
|
s_pf_addrs = src.addresses;
|
||||||
s_pf_color = entries[index].color;
|
s_pf_icon = src.icon;
|
||||||
|
s_pf_color = src.color;
|
||||||
|
s_pf_price_basis = src.priceBasis;
|
||||||
|
s_pf_manual_price = src.manualPrice;
|
||||||
|
snprintf(s_pf_manual_ccy, sizeof(s_pf_manual_ccy), "%s", src.manualCurrency.c_str());
|
||||||
|
s_pf_show_drgx = src.showDrgx;
|
||||||
|
s_pf_show_value = src.showValue;
|
||||||
|
s_pf_show_24h = src.show24h;
|
||||||
} else {
|
} else {
|
||||||
s_pf_label[0] = '\0';
|
s_pf_label[0] = '\0';
|
||||||
s_pf_addrs.clear();
|
s_pf_addrs.clear();
|
||||||
s_pf_icon.clear();
|
s_pf_icon.clear();
|
||||||
s_pf_color = 0;
|
s_pf_color = 0;
|
||||||
|
s_pf_price_basis = 0;
|
||||||
|
s_pf_manual_price = 0.0;
|
||||||
|
snprintf(s_pf_manual_ccy, sizeof(s_pf_manual_ccy), "USD");
|
||||||
|
s_pf_show_drgx = true;
|
||||||
|
s_pf_show_value = true;
|
||||||
|
s_pf_show_24h = false;
|
||||||
}
|
}
|
||||||
s_pf_search[0] = '\0';
|
s_pf_search[0] = '\0';
|
||||||
s_pf_type_filter = 0;
|
s_pf_type_filter = 0;
|
||||||
@@ -234,18 +291,32 @@ static void RenderPortfolioEditor(App* app)
|
|||||||
pdl->AddText(sub1f, sub1f->LegacySize, ImVec2(nameX, rowY),
|
pdl->AddText(sub1f, sub1f->LegacySize, ImVec2(nameX, rowY),
|
||||||
s_pf_label[0] ? OnSurface() : OnSurfaceDisabled(), nm);
|
s_pf_label[0] ? OnSurface() : OnSurfaceDisabled(), nm);
|
||||||
double pbal = data::SumPortfolioBalance(s_pf_addrs, state.addresses);
|
double pbal = data::SumPortfolioBalance(s_pf_addrs, state.addresses);
|
||||||
char pbuf[64];
|
config::Settings::PortfolioEntry tmp;
|
||||||
snprintf(pbuf, sizeof(pbuf), "%.4f %s", pbal, DRAGONX_TICKER);
|
tmp.priceBasis = s_pf_price_basis;
|
||||||
float pbw = body2f->CalcTextSizeA(body2f->LegacySize, FLT_MAX, 0, pbuf).x;
|
tmp.manualPrice = s_pf_manual_price;
|
||||||
pdl->AddText(body2f, body2f->LegacySize,
|
tmp.manualCurrency = s_pf_manual_ccy;
|
||||||
ImVec2(pMax.x - ppad - pbw, rowY + (sub1f->LegacySize - body2f->LegacySize) * 0.5f),
|
tmp.showDrgx = s_pf_show_drgx;
|
||||||
OnSurface(), pbuf);
|
tmp.showValue = s_pf_show_value;
|
||||||
if (state.market.price_usd > 0) {
|
tmp.show24h = s_pf_show_24h;
|
||||||
snprintf(pbuf, sizeof(pbuf), "$%.2f", pbal * state.market.price_usd);
|
PfDisplay pd = pfBuildDisplay(tmp, pbal, state.market);
|
||||||
float uw = capF->CalcTextSizeA(capF->LegacySize, FLT_MAX, 0, pbuf).x;
|
float primW = pd.primary.empty() ? 0.0f
|
||||||
pdl->AddText(capF, capF->LegacySize,
|
: body2f->CalcTextSizeA(body2f->LegacySize, FLT_MAX, 0, pd.primary.c_str()).x;
|
||||||
ImVec2(pMax.x - ppad - uw, rowY + sub1f->LegacySize + Layout::spacingXs()),
|
if (!pd.primary.empty())
|
||||||
OnSurfaceDisabled(), pbuf);
|
pdl->AddText(body2f, body2f->LegacySize,
|
||||||
|
ImVec2(pMax.x - ppad - primW, rowY + (sub1f->LegacySize - body2f->LegacySize) * 0.5f),
|
||||||
|
OnSurface(), pd.primary.c_str());
|
||||||
|
{
|
||||||
|
float belowY = rowY + sub1f->LegacySize + Layout::spacingXs();
|
||||||
|
float cursorR = pMax.x - ppad;
|
||||||
|
if (!pd.change.empty()) {
|
||||||
|
float cw = capF->CalcTextSizeA(capF->LegacySize, FLT_MAX, 0, pd.change.c_str()).x;
|
||||||
|
pdl->AddText(capF, capF->LegacySize, ImVec2(cursorR - cw, belowY), pd.changeCol, pd.change.c_str());
|
||||||
|
cursorR -= cw + Layout::spacingSm();
|
||||||
|
}
|
||||||
|
if (!pd.secondary.empty()) {
|
||||||
|
float sw2 = capF->CalcTextSizeA(capF->LegacySize, FLT_MAX, 0, pd.secondary.c_str()).x;
|
||||||
|
pdl->AddText(capF, capF->LegacySize, ImVec2(cursorR - sw2, belowY), OnSurfaceDisabled(), pd.secondary.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::Dummy(ImVec2(pw, ph));
|
ImGui::Dummy(ImVec2(pw, ph));
|
||||||
}
|
}
|
||||||
@@ -319,6 +390,37 @@ static void RenderPortfolioEditor(App* app)
|
|||||||
}
|
}
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||||
|
|
||||||
|
// Price data: how this group's value is priced + which fields the card shows.
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted(TR("portfolio_price"));
|
||||||
|
const char* basisItems[4] = { TR("portfolio_price_usd"), TR("portfolio_price_btc"),
|
||||||
|
TR("portfolio_price_drgx"), TR("portfolio_price_manual") };
|
||||||
|
ImGui::SetNextItemWidth(-1);
|
||||||
|
ImGui::Combo("##pfBasis", &s_pf_price_basis, basisItems, 4);
|
||||||
|
|
||||||
|
if (s_pf_price_basis == 3) { // Manual: price-per-DRGX + currency label
|
||||||
|
float half = (ImGui::GetContentRegionAvail().x - Layout::spacingSm()) * 0.62f;
|
||||||
|
ImGui::SetNextItemWidth(half);
|
||||||
|
ImGui::InputDouble("##pfManPrice", &s_pf_manual_price, 0.0, 0.0, "%.6f");
|
||||||
|
if (s_pf_manual_price < 0.0) s_pf_manual_price = 0.0;
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(-1);
|
||||||
|
ImGui::InputTextWithHint("##pfManCcy", TR("portfolio_currency"), s_pf_manual_ccy, sizeof(s_pf_manual_ccy));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field toggles (which price fields appear on the card).
|
||||||
|
ImGui::Checkbox(DRAGONX_TICKER "##pfShowDrgx", &s_pf_show_drgx);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::BeginDisabled(s_pf_price_basis == 2); // DRGX-only -> no value field
|
||||||
|
ImGui::Checkbox(TR("portfolio_show_value"), &s_pf_show_value);
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::BeginDisabled(!(s_pf_price_basis == 0 || s_pf_price_basis == 1)); // 24h: live-market only
|
||||||
|
ImGui::Checkbox(TR("portfolio_show_24h"), &s_pf_show_24h);
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
}
|
||||||
|
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||||
|
|
||||||
// Icon picker — fills the remaining height of the left column.
|
// Icon picker — fills the remaining height of the left column.
|
||||||
{
|
{
|
||||||
float cell = 28.0f * dp, cellGap = 4.0f * dp;
|
float cell = 28.0f * dp, cellGap = 4.0f * dp;
|
||||||
@@ -521,6 +623,12 @@ static void RenderPortfolioEditor(App* app)
|
|||||||
e.addresses = s_pf_addrs;
|
e.addresses = s_pf_addrs;
|
||||||
e.icon = s_pf_icon;
|
e.icon = s_pf_icon;
|
||||||
e.color = s_pf_color;
|
e.color = s_pf_color;
|
||||||
|
e.priceBasis = s_pf_price_basis;
|
||||||
|
e.manualPrice = s_pf_manual_price;
|
||||||
|
e.manualCurrency = s_pf_manual_ccy;
|
||||||
|
e.showDrgx = s_pf_show_drgx;
|
||||||
|
e.showValue = s_pf_show_value;
|
||||||
|
e.show24h = s_pf_show_24h;
|
||||||
if (s_pf_editing >= 0 && s_pf_editing < (int)entries.size()) entries[s_pf_editing] = e;
|
if (s_pf_editing >= 0 && s_pf_editing < (int)entries.size()) entries[s_pf_editing] = e;
|
||||||
else entries.push_back(e);
|
else entries.push_back(e);
|
||||||
persist(entries);
|
persist(entries);
|
||||||
@@ -1194,19 +1302,27 @@ void RenderMarketTab(App* app)
|
|||||||
float rowY = gcMin.y + pfCardPad;
|
float rowY = gcMin.y + pfCardPad;
|
||||||
float gcRight = gcMax.x - pfCardPad;
|
float gcRight = gcMax.x - pfCardPad;
|
||||||
|
|
||||||
// Amounts, top-right corner: DRGX on the name row, fiat directly below it.
|
// Amounts, top-right corner, per the group's price-data config.
|
||||||
snprintf(buf, sizeof(buf), "%.4f %s", bal, DRAGONX_TICKER);
|
PfDisplay disp = pfBuildDisplay(e, bal, market);
|
||||||
float drgxW = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, buf).x;
|
float primaryW = disp.primary.empty() ? 0.0f
|
||||||
dl->AddText(body2, body2->LegacySize,
|
: body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, disp.primary.c_str()).x;
|
||||||
ImVec2(gcRight - drgxW, rowY + (sub1->LegacySize - body2->LegacySize) * 0.5f),
|
if (!disp.primary.empty())
|
||||||
OnSurface(), buf);
|
dl->AddText(body2, body2->LegacySize,
|
||||||
if (market.price_usd > 0) {
|
ImVec2(gcRight - primaryW, rowY + (sub1->LegacySize - body2->LegacySize) * 0.5f),
|
||||||
char usd[48];
|
OnSurface(), disp.primary.c_str());
|
||||||
snprintf(usd, sizeof(usd), "$%.2f", bal * market.price_usd);
|
{
|
||||||
float usdW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, usd).x;
|
// Second line: the value (when DRGX is primary) and/or the 24h change.
|
||||||
dl->AddText(capFont, capFont->LegacySize,
|
float belowY = rowY + sub1->LegacySize + Layout::spacingXs();
|
||||||
ImVec2(gcRight - usdW, rowY + sub1->LegacySize + Layout::spacingXs()),
|
float cursorR = gcRight;
|
||||||
OnSurfaceDisabled(), usd);
|
if (!disp.change.empty()) {
|
||||||
|
float cw = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, disp.change.c_str()).x;
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(gcRight - cw, belowY), disp.changeCol, disp.change.c_str());
|
||||||
|
cursorR = gcRight - cw - Layout::spacingSm();
|
||||||
|
}
|
||||||
|
if (!disp.secondary.empty()) {
|
||||||
|
float sw2 = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, disp.secondary.c_str()).x;
|
||||||
|
dl->AddText(capFont, capFont->LegacySize, ImVec2(cursorR - sw2, belowY), OnSurfaceDisabled(), disp.secondary.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Icon (left) then the group name.
|
// Icon (left) then the group name.
|
||||||
@@ -1221,7 +1337,7 @@ void RenderMarketTab(App* app)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Group name, larger — truncated so it never overlaps the amount.
|
// Group name, larger — truncated so it never overlaps the amount.
|
||||||
float nameMaxW = (gcRight - drgxW - Layout::spacingMd()) - nameX;
|
float nameMaxW = (gcRight - primaryW - Layout::spacingMd()) - nameX;
|
||||||
std::string label = e.label;
|
std::string label = e.label;
|
||||||
bool truncated = false;
|
bool truncated = false;
|
||||||
while (label.size() > 1 &&
|
while (label.size() > 1 &&
|
||||||
|
|||||||
@@ -1124,6 +1124,16 @@ void I18n::loadBuiltinEnglish()
|
|||||||
strings_["portfolio_color"] = "Color";
|
strings_["portfolio_color"] = "Color";
|
||||||
strings_["portfolio_no_icon"] = "None";
|
strings_["portfolio_no_icon"] = "None";
|
||||||
strings_["portfolio_custom_color"] = "Custom color\xE2\x80\xA6";
|
strings_["portfolio_custom_color"] = "Custom color\xE2\x80\xA6";
|
||||||
|
strings_["portfolio_price"] = "Price";
|
||||||
|
strings_["portfolio_price_usd"] = "Market \xC2\xB7 USD";
|
||||||
|
strings_["portfolio_price_btc"] = "Market \xC2\xB7 BTC";
|
||||||
|
strings_["portfolio_price_drgx"] = "DRGX only";
|
||||||
|
strings_["portfolio_price_manual"] = "Manual";
|
||||||
|
strings_["portfolio_manual_price"] = "Price / DRGX";
|
||||||
|
strings_["portfolio_currency"] = "Currency";
|
||||||
|
strings_["portfolio_show"] = "Show:";
|
||||||
|
strings_["portfolio_show_value"] = "Value";
|
||||||
|
strings_["portfolio_show_24h"] = "24h";
|
||||||
strings_["portfolio_appearance"] = "APPEARANCE";
|
strings_["portfolio_appearance"] = "APPEARANCE";
|
||||||
strings_["portfolio_addresses_hdr"] = "ADDRESSES";
|
strings_["portfolio_addresses_hdr"] = "ADDRESSES";
|
||||||
strings_["portfolio_search"] = "Search addresses\xE2\x80\xA6";
|
strings_["portfolio_search"] = "Search addresses\xE2\x80\xA6";
|
||||||
|
|||||||
Reference in New Issue
Block a user