ui: wallet list content
This commit is contained in:
parent
a31d38dc1c
commit
18b05fffe9
7 changed files with 199 additions and 103 deletions
|
@ -25,6 +25,8 @@ wallets:
|
|||
invalid_url: Entered URL is invalid
|
||||
open: Open the wallet
|
||||
wrong_pass: Entered password is wrong
|
||||
locked: Locked
|
||||
unlocked: Unlocked
|
||||
network:
|
||||
self: Network
|
||||
node: Integrated node
|
||||
|
|
|
@ -25,6 +25,8 @@ wallets:
|
|||
invalid_url: Введенный URL-адрес недействителен
|
||||
open: Открыть кошелёк
|
||||
wrong_pass: Введён неправильный пароль
|
||||
locked: Заблокирован
|
||||
unlocked: Разблокирован
|
||||
network:
|
||||
self: Сеть
|
||||
node: Встроенный узел
|
||||
|
|
|
@ -19,6 +19,7 @@ pub struct Colors;
|
|||
impl Colors {
|
||||
pub const WHITE: Color32 = Color32::from_gray(253);
|
||||
pub const BLACK: Color32 = Color32::from_gray(2);
|
||||
pub const DARK_TEXT: Color32 = Color32::from_gray(30);
|
||||
pub const TRANSPARENT: Color32 = Color32::from_rgba_premultiplied(0, 0, 0, 0);
|
||||
pub const SEMI_TRANSPARENT: Color32 = Color32::from_black_alpha(100);
|
||||
pub const YELLOW: Color32 = Color32::from_rgb(254, 241, 2);
|
||||
|
@ -36,5 +37,6 @@ impl Colors {
|
|||
pub const STROKE: Color32 = Color32::from_gray(190);
|
||||
pub const INACTIVE_TEXT: Color32 = Color32::from_gray(150);
|
||||
pub const ITEM_STROKE: Color32 = Color32::from_gray(220);
|
||||
pub const ITEM_HOVER_STROKE: Color32 = Color32::from_gray(205);
|
||||
pub const ITEM_HOVER: Color32 = Color32::from_gray(205);
|
||||
pub const ITEM_CURRENT: Color32 = Color32::from_gray(227);
|
||||
}
|
||||
|
|
|
@ -28,10 +28,10 @@ pub struct View;
|
|||
impl View {
|
||||
/// Default stroke around views.
|
||||
pub const DEFAULT_STROKE: Stroke = Stroke { width: 1.0, color: Colors::STROKE };
|
||||
/// Stroke around list items.
|
||||
/// Stroke for items.
|
||||
pub const ITEM_STROKE: Stroke = Stroke { width: 1.0, color: Colors::ITEM_STROKE };
|
||||
/// Stroke around list items.
|
||||
pub const ITEM_HOVER_STROKE: Stroke = Stroke { width: 1.0, color: Colors::ITEM_HOVER_STROKE };
|
||||
/// Stroke for hovered items and buttons.
|
||||
pub const ITEM_HOVER_STROKE: Stroke = Stroke { width: 1.0, color: Colors::ITEM_HOVER };
|
||||
|
||||
/// Callback on Enter key press event.
|
||||
pub fn on_enter_key(ui: &mut egui::Ui, cb: impl FnOnce()) {
|
||||
|
@ -161,45 +161,6 @@ impl View {
|
|||
}
|
||||
}
|
||||
|
||||
/// Draw round [`Button`] with icon.
|
||||
pub fn round_button(ui: &mut egui::Ui, icon: &'static str, action: impl FnOnce()) {
|
||||
ui.scope(|ui| {
|
||||
// Setup colors.
|
||||
ui.visuals_mut().widgets.inactive.bg_fill = Colors::BUTTON;
|
||||
ui.visuals_mut().widgets.hovered.bg_fill = Colors::FILL;
|
||||
ui.visuals_mut().widgets.active.bg_fill = Colors::FILL_DARK;
|
||||
|
||||
// Setup radius.
|
||||
let mut r = 44.0 * 0.5;
|
||||
let size = egui::Vec2::splat(2.0 * r + 5.0);
|
||||
let (rect, br) = ui.allocate_at_least(size, Sense::click_and_drag());
|
||||
|
||||
let mut icon_color = Colors::GRAY;
|
||||
|
||||
// Increase radius and change icon size and color on-hover.
|
||||
if br.hovered() {
|
||||
r = r * 1.05;
|
||||
icon_color = Colors::TEXT_BUTTON;
|
||||
}
|
||||
|
||||
let visuals = ui.style().interact(&br);
|
||||
ui.painter().add(CircleShape {
|
||||
center: rect.center(),
|
||||
radius: r,
|
||||
fill: visuals.bg_fill,
|
||||
stroke: Self::DEFAULT_STROKE,
|
||||
});
|
||||
ui.allocate_ui_at_rect(rect, |ui| {
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.label(RichText::new(icon).color(icon_color).size(25.0));
|
||||
});
|
||||
});
|
||||
if Self::touched(ui, br) {
|
||||
(action)();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw list item [`Button`] with given vertical padding and rounding on left and right sides.
|
||||
pub fn item_button(ui: &mut egui::Ui, r: [bool; 2], icon: &'static str, action: impl FnOnce()) {
|
||||
let rounding = Self::get_rounding([r[0], r[1], r[1], r[0]]);
|
||||
|
@ -235,6 +196,47 @@ impl View {
|
|||
});
|
||||
}
|
||||
|
||||
/// Draw circle [`Button`] with icon.
|
||||
pub fn circle_button(ui: &mut egui::Ui, icon: &'static str, action: impl FnOnce()) {
|
||||
ui.scope(|ui| {
|
||||
// Setup colors.
|
||||
ui.visuals_mut().widgets.inactive.bg_fill = Colors::BUTTON;
|
||||
ui.visuals_mut().widgets.hovered.bg_fill = Colors::FILL;
|
||||
ui.visuals_mut().widgets.active.bg_fill = Colors::FILL_DARK;
|
||||
|
||||
// Setup radius.
|
||||
let mut r = 44.0 * 0.5;
|
||||
let size = egui::Vec2::splat(2.0 * r + 5.0);
|
||||
let (rect, br) = ui.allocate_at_least(size, Sense::click_and_drag());
|
||||
|
||||
let mut icon_color = Colors::GRAY;
|
||||
let mut stroke = Self::DEFAULT_STROKE;
|
||||
|
||||
// Increase radius and change icon size and color on-hover.
|
||||
if br.hovered() {
|
||||
r = r * 1.04;
|
||||
icon_color = Colors::TEXT_BUTTON;
|
||||
stroke = Self::ITEM_HOVER_STROKE;
|
||||
}
|
||||
|
||||
let visuals = ui.style().interact(&br);
|
||||
ui.painter().add(CircleShape {
|
||||
center: rect.center(),
|
||||
radius: r,
|
||||
fill: visuals.bg_fill,
|
||||
stroke,
|
||||
});
|
||||
ui.allocate_ui_at_rect(rect, |ui| {
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.label(RichText::new(icon).color(icon_color).size(25.0));
|
||||
});
|
||||
});
|
||||
if Self::touched(ui, br) {
|
||||
(action)();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Get rounding for provided corners clockwise.
|
||||
fn get_rounding(corners: [bool; 4]) -> Rounding {
|
||||
Rounding {
|
||||
|
@ -276,7 +278,7 @@ impl View {
|
|||
se: if r[3] { 8.0 } else { 0.0 },
|
||||
},
|
||||
fill: Colors::WHITE,
|
||||
stroke: Stroke { width: 1.0, color: Colors::ITEM_STROKE },
|
||||
stroke: Self::ITEM_STROKE,
|
||||
};
|
||||
let bg_idx = ui.painter().add(bg_shape);
|
||||
|
||||
|
|
|
@ -12,13 +12,13 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::cmp::max;
|
||||
use std::cmp::{max, min};
|
||||
|
||||
use egui::{Align2, Margin, RichText, TextStyle, Widget};
|
||||
use egui::{Align, Align2, Layout, Margin, RichText, Rounding, TextStyle, Widget};
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROW_LEFT, EYE, EYE_SLASH, FOLDER_PLUS, GEAR, GLOBE, PLUS};
|
||||
use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, COMPUTER_TOWER, EYE, EYE_SLASH, FOLDER, FOLDER_LOCK, FOLDER_OPEN, GEAR, GLOBE, GLOBE_SIMPLE, PLUS};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, ModalContainer, ModalPosition, Root, TitlePanel, TitleType, View};
|
||||
use crate::gui::views::wallets::creation::{MnemonicSetup, WalletCreation};
|
||||
|
@ -97,79 +97,83 @@ impl WalletsContent {
|
|||
|
||||
// Get wallets.
|
||||
let wallets = Wallets::list();
|
||||
let is_list_empty = wallets.is_empty();
|
||||
let selected = wallets.iter().find(|x| Some(x.config.id) == Wallets::selected_id());
|
||||
|
||||
// Show title panel.
|
||||
self.title_ui(ui, frame);
|
||||
let empty_list = wallets.is_empty();
|
||||
|
||||
// Setup wallet content flags.
|
||||
let is_wallet_creating = self.creation_content.can_go_back();
|
||||
let is_wallet_showing = if let Some(id) = Wallets::selected_id() {
|
||||
let create_wallet = self.creation_content.can_go_back();
|
||||
let show_wallet = if let Some(id) = Wallets::selected_id() {
|
||||
Wallets::is_open(id)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Setup panels parameters.
|
||||
let is_dual_panel = Self::is_dual_panel_mode(ui, frame);
|
||||
let is_wallet_panel_open
|
||||
= is_dual_panel || is_wallet_showing || is_wallet_creating || is_list_empty;
|
||||
let wallet_panel_width
|
||||
= self.wallet_panel_width(ui, is_list_empty, is_dual_panel, is_wallet_showing);
|
||||
let dual_panel = Self::is_dual_panel_mode(ui, frame);
|
||||
let open_wallet_panel = dual_panel || show_wallet || create_wallet || empty_list;
|
||||
let wallet_panel_width = self.wallet_panel_width(ui, empty_list, dual_panel, show_wallet);
|
||||
|
||||
// Show title panel.
|
||||
self.title_ui(ui, frame, dual_panel);
|
||||
|
||||
// Show wallet panel content.
|
||||
egui::SidePanel::right("wallet_panel")
|
||||
.resizable(false)
|
||||
.exact_width(wallet_panel_width)
|
||||
.frame(egui::Frame {
|
||||
fill: if is_list_empty && !is_wallet_creating {
|
||||
fill: if empty_list && !create_wallet {
|
||||
Colors::FILL_DARK
|
||||
} else {
|
||||
Colors::WHITE
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, is_wallet_panel_open, |ui| {
|
||||
if is_wallet_showing {
|
||||
self.wallet_content.ui(ui, frame, selected.unwrap(), cb);
|
||||
} else {
|
||||
.show_animated_inside(ui, open_wallet_panel, |ui| {
|
||||
if create_wallet || !show_wallet {
|
||||
// Show wallet creation content
|
||||
self.creation_content.ui(ui, cb);
|
||||
} else {
|
||||
for w in wallets.iter() {
|
||||
// Show content for selected wallet.
|
||||
if Some(w.config.id) == Wallets::selected_id() {
|
||||
self.wallet_content.ui(ui, frame, &w, cb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Show wallets list.
|
||||
if !is_list_empty && (!is_wallet_panel_open || is_dual_panel) {
|
||||
// Show non-empty list if wallet is not creating and not open at single panel mode.
|
||||
if !empty_list && !create_wallet && (!open_wallet_panel || dual_panel) {
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
stroke: View::DEFAULT_STROKE,
|
||||
fill: Colors::FILL_DARK,
|
||||
inner_margin: Margin {
|
||||
left: View::far_left_inset_margin(ui) + 4.0,
|
||||
right: View::far_right_inset_margin(ui, frame) + 4.0,
|
||||
top: 3.0,
|
||||
bottom: 4.0,
|
||||
left: View::far_left_inset_margin(ui) + 6.0,
|
||||
right: View::far_right_inset_margin(ui, frame) + 6.0,
|
||||
top: 5.0,
|
||||
bottom: View::get_bottom_inset(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
self.list_ui(ui, &wallets, cb);
|
||||
self.wallet_list_ui(ui, dual_panel, &wallets, cb);
|
||||
});
|
||||
|
||||
// Do not show creation button if wallets panel is not showing at root
|
||||
// or if wallet is not showing at dual panel mode.
|
||||
// or if wallet creation is showing at dual panel mode.
|
||||
let root_dual_panel = Root::is_dual_panel_mode(frame);
|
||||
let wallets_panel_not_open = !root_dual_panel && Root::is_network_panel_open();
|
||||
if wallets_panel_not_open || (is_wallet_panel_open && !is_wallet_showing) {
|
||||
if wallets_panel_not_open || (open_wallet_panel && !show_wallet) {
|
||||
return;
|
||||
} else {
|
||||
self.create_wallet_btn_ui(ui, if is_dual_panel { wallet_panel_width } else { 0.0 });
|
||||
self.create_wallet_btn_ui(ui, if dual_panel { wallet_panel_width } else { 0.0 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw [`TitlePanel`] content.
|
||||
fn title_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
fn title_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, dual_panel: bool) {
|
||||
// Setup title text.
|
||||
let title_text = if self.creation_content.can_go_back() {
|
||||
t!("wallets.add")
|
||||
|
@ -180,12 +184,10 @@ impl WalletsContent {
|
|||
|
||||
// Draw title panel.
|
||||
TitlePanel::ui(title_content, |ui, frame| {
|
||||
if Wallets::selected_id().is_some() {
|
||||
if !Self::is_dual_panel_mode(ui, frame) {
|
||||
if Wallets::selected_id().is_some() && !dual_panel {
|
||||
View::title_button(ui, ARROW_LEFT, || {
|
||||
Wallets::select(None);
|
||||
});
|
||||
}
|
||||
} else if self.creation_content.can_go_back() {
|
||||
View::title_button(ui, ARROW_LEFT, || {
|
||||
self.creation_content.back();
|
||||
|
@ -203,29 +205,114 @@ impl WalletsContent {
|
|||
}
|
||||
|
||||
/// Draw list of wallets.
|
||||
fn list_ui(&mut self, ui: &mut egui::Ui, wallets: &Vec<Wallet>, cb: &dyn PlatformCallbacks) {
|
||||
for w in wallets {
|
||||
ui.label(&w.config.name);
|
||||
fn wallet_list_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
dual_panel: bool,
|
||||
wallets: &Vec<Wallet>,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
let available_width = ui.available_width();
|
||||
// Calculate list width.
|
||||
let width = if dual_panel {
|
||||
available_width
|
||||
} else {
|
||||
min(available_width as i64, (Root::SIDE_PANEL_MIN_WIDTH * 1.3) as i64) as f32
|
||||
};
|
||||
|
||||
/// Show open/close button
|
||||
let id = w.config.id;
|
||||
// Draw list of wallets.
|
||||
ui.vertical_centered(|ui| {
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_width(width);
|
||||
ui.allocate_ui(rect.size(), |ui| {
|
||||
for (index, w) in wallets.iter().enumerate() {
|
||||
// Draw wallet list item.
|
||||
self.wallet_item_ui(ui, w, cb);
|
||||
// Add space after last item.
|
||||
if index == wallets.len() - 1 {
|
||||
ui.add_space(36.0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw wallet list item.
|
||||
fn wallet_item_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
let id = wallet.config.id;
|
||||
let is_selected = Some(id) == Wallets::selected_id();
|
||||
let is_open = Wallets::is_open(id);
|
||||
let is_current = is_open && is_selected;
|
||||
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(76.0);
|
||||
let rounding = View::item_rounding(0, 1);
|
||||
let bg_color = if is_current { Colors::ITEM_CURRENT } else { Colors::FILL };
|
||||
let stroke = if is_current { View::ITEM_HOVER_STROKE } else { View::ITEM_HOVER_STROKE };
|
||||
ui.painter().rect(rect, rounding, bg_color, stroke);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Setup padding for item buttons.
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(14.0, 0.0);
|
||||
// Setup rounding for item buttons.
|
||||
ui.style_mut().visuals.widgets.inactive.rounding = Rounding::same(8.0);
|
||||
ui.style_mut().visuals.widgets.hovered.rounding = Rounding::same(8.0);
|
||||
ui.style_mut().visuals.widgets.active.rounding = Rounding::same(8.0);
|
||||
|
||||
if !is_open {
|
||||
View::button(ui, "open me".to_string(), Colors::GOLD, || {
|
||||
// Show button to open closed wallet.
|
||||
View::item_button(ui, [false, true], FOLDER_OPEN, || {
|
||||
Wallets::select(Some(id));
|
||||
self.show_open_wallet_modal(cb);
|
||||
});
|
||||
} else if !is_selected {
|
||||
View::button(ui, "select me".to_string(), Colors::GOLD, || {
|
||||
// Show button to select opened wallet.
|
||||
View::item_button(ui, [false, true], CARET_RIGHT, || {
|
||||
Wallets::select(Some(id));
|
||||
});
|
||||
|
||||
// Show button to close opened wallet.
|
||||
View::item_button(ui, [false, false], FOLDER_LOCK, || {
|
||||
Wallets::close(id).unwrap()
|
||||
});
|
||||
}
|
||||
|
||||
if Wallets::is_open(id) {
|
||||
ui.label("opened!");
|
||||
}
|
||||
}
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(7.0);
|
||||
ui.vertical(|ui| {
|
||||
// Setup wallet name text.
|
||||
let name_text = format!("{} {}", FOLDER, wallet.config.name);
|
||||
let name_color = if is_selected { Colors::DARK_TEXT } else { Colors::TITLE };
|
||||
ui.add_space(3.0);
|
||||
View::ellipsize_text(ui, name_text, 18.0, name_color);
|
||||
ui.add_space(-1.0);
|
||||
|
||||
// Setup wallet connection text.
|
||||
let external_url = wallet.config.get_external_node_url();
|
||||
let conn_text = if let Some(url) = external_url {
|
||||
format!("{} {}", GLOBE_SIMPLE, url)
|
||||
} else {
|
||||
format!("{} {}", COMPUTER_TOWER, t!("network.node"))
|
||||
};
|
||||
View::ellipsize_text(ui, conn_text, 15.0, Colors::TEXT);
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Setup wallet status text.
|
||||
let status_text = if Wallets::is_open(id) {
|
||||
format!("{} {}", FOLDER_OPEN, t!("wallets.unlocked"))
|
||||
} else {
|
||||
format!("{} {}", FOLDER_LOCK, t!("wallets.locked"))
|
||||
};
|
||||
ui.label(RichText::new(status_text).size(15.0).color(Colors::GRAY));
|
||||
ui.add_space(3.0);
|
||||
})
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Draw floating button to show wallet creation [`Modal`].
|
||||
|
@ -237,7 +324,7 @@ impl WalletsContent {
|
|||
.anchor(Align2::RIGHT_BOTTOM, egui::Vec2::new(-6.0 - right_margin, -6.0))
|
||||
.frame(egui::Frame::default())
|
||||
.show(ui.ctx(), |ui| {
|
||||
View::round_button(ui, PLUS, || {
|
||||
View::circle_button(ui, PLUS, || {
|
||||
self.creation_content.show_name_pass_modal();
|
||||
});
|
||||
});
|
||||
|
@ -373,7 +460,7 @@ impl WalletsContent {
|
|||
&self,
|
||||
ui:&mut egui::Ui,
|
||||
is_list_empty: bool,
|
||||
is_dual_panel: bool,
|
||||
dual_panel: bool,
|
||||
is_wallet_showing: bool
|
||||
) -> f32 {
|
||||
let is_wallet_creation = self.creation_content.can_go_back();
|
||||
|
@ -382,7 +469,7 @@ impl WalletsContent {
|
|||
} else {
|
||||
ui.available_width() - Root::SIDE_PANEL_MIN_WIDTH
|
||||
};
|
||||
if is_dual_panel {
|
||||
if dual_panel {
|
||||
let min_width = (Root::SIDE_PANEL_MIN_WIDTH + View::get_right_inset()) as i64;
|
||||
max(min_width, available_width as i64) as f32
|
||||
} else {
|
||||
|
|
|
@ -17,8 +17,8 @@ pub mod selection;
|
|||
pub mod tx;
|
||||
pub mod keys;
|
||||
|
||||
mod wallet;
|
||||
pub use wallet::{Wallet, Wallets};
|
||||
mod wallets;
|
||||
pub use wallets::{Wallet, Wallets};
|
||||
|
||||
mod config;
|
||||
pub use config::*;
|
|
@ -46,7 +46,7 @@ lazy_static! {
|
|||
pub struct Wallets {
|
||||
/// List of wallets.
|
||||
list: Vec<Wallet>,
|
||||
/// Selected [`Wallet`] identifier from config.
|
||||
/// Selected [`Wallet`] identifier.
|
||||
selected_id: Option<i64>,
|
||||
/// Identifiers of opened wallets.
|
||||
opened_ids: BTreeSet<i64>
|
||||
|
@ -61,7 +61,8 @@ impl Wallets {
|
|||
Self {
|
||||
list: Self::load_wallets(&AppConfig::chain_type()),
|
||||
selected_id: None,
|
||||
opened_ids: Default::default() }
|
||||
opened_ids: BTreeSet::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new wallet and add it to state.
|
||||
|
@ -198,7 +199,7 @@ impl Wallet {
|
|||
mnemonic: String,
|
||||
external_node_url: Option<String>
|
||||
) -> Result<Wallet, Error> {
|
||||
let config = WalletConfig::create(name.clone(), external_node_url);
|
||||
let config = WalletConfig::create(name, external_node_url);
|
||||
let wallet = Self::create_wallet_instance(config.clone())?;
|
||||
let w = Wallet {
|
||||
instance: wallet,
|
||||
|
@ -566,7 +567,7 @@ impl Wallet {
|
|||
}
|
||||
|
||||
/// Get summary info about the wallet.
|
||||
pub fn get_info<'a, T: ?Sized, C, K>(
|
||||
fn get_info<'a, T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
parent_key_id: &Identifier,
|
||||
minimum_confirmations: u64,
|
Loading…
Reference in a new issue