mirror of
https://code.gri.mw/GUI/grim.git
synced 2026-07-04 05:57:29 +00:00
ui: make list items clickable, ability to delete tx
This commit is contained in:
@@ -142,6 +142,7 @@ wallets:
|
||||
payment_proof_desc: 'Geben Sie den erhaltenen Zahlungsnachweis ein, um die Transaktion zu verifizieren:'
|
||||
payment_proof_valid: 'Der eingegebene Zahlungsnachweis ist gültig:'
|
||||
payment_proof_error: 'Der eingetragene Zahlungsnachweis ist nicht gültig:'
|
||||
tx_delete_confirmation: Bist du sicher, dass du die Transaktion aus dem Verlauf löschen möchtest?
|
||||
transport:
|
||||
desc: 'Transport verwenden, um Nachrichten synchron zu empfangen oder zu senden:'
|
||||
tor_network: Tor Netzwek
|
||||
|
||||
@@ -142,6 +142,7 @@ wallets:
|
||||
payment_proof_desc: 'Enter received payment proof to verify transaction:'
|
||||
payment_proof_valid: 'Entered payment proof is valid:'
|
||||
payment_proof_error: 'Entered payment proof is not valid:'
|
||||
tx_delete_confirmation: Are you sure you want to delete the transaction from history?
|
||||
transport:
|
||||
desc: 'Use transport to receive or send messages synchronously:'
|
||||
tor_network: Tor network
|
||||
|
||||
@@ -142,6 +142,7 @@ wallets:
|
||||
payment_proof_desc: 'Saisissez la preuve de paiement reçue pour vérifier la transaction:'
|
||||
payment_proof_valid: 'La preuve de paiement saisie est valide:'
|
||||
payment_proof_error: "La preuve de paiement saisie n'est pas valide:"
|
||||
tx_delete_confirmation: Êtes-vous sûr de vouloir supprimer la transaction de l'historique?
|
||||
transport:
|
||||
desc: 'Utilisez le transport pour recevoir ou envoyer des messages de manière synchronisée:'
|
||||
tor_network: Réseau Tor
|
||||
|
||||
@@ -142,6 +142,7 @@ wallets:
|
||||
payment_proof_desc: 'Введите полученное подтверждение оплаты для проверки транзакции:'
|
||||
payment_proof_valid: 'Введённое подтверждение оплаты действительно:'
|
||||
payment_proof_error: 'Введённое подтверждение оплаты недействительно:'
|
||||
tx_delete_confirmation: Вы уверены, что хотите удалить транзакцию из истории?
|
||||
transport:
|
||||
desc: 'Используйте транспорт для синхронных получения или отправки сообщений:'
|
||||
tor_network: Сеть Tor
|
||||
|
||||
@@ -142,6 +142,7 @@ wallets:
|
||||
payment_proof_desc: 'Islemi doğrulamak için alinan ödeme kanitini girin:'
|
||||
payment_proof_valid: 'Girilen ödeme kaniti geçerlidir:'
|
||||
payment_proof_error: 'Girilen ödeme kaniti geçerli değildir:'
|
||||
tx_delete_confirmation: Islemi geçmişten silmek istediğinizden emin misiniz?
|
||||
transport:
|
||||
desc: 'Adresten senkronize GONDER veya AL:'
|
||||
tor_network: Tor network
|
||||
|
||||
@@ -142,6 +142,7 @@ wallets:
|
||||
payment_proof_desc: '輸入已收款證明以驗證交易:'
|
||||
payment_proof_valid: '輸入的付款證明有效:'
|
||||
payment_proof_error: '輸入的付款證明無效:'
|
||||
tx_delete_confirmation: 你確定要從歷史紀錄中刪除這筆交易嗎?
|
||||
transport:
|
||||
desc: '使用传输同步接收或发送消息:'
|
||||
tor_network: Tor 网络
|
||||
|
||||
@@ -44,7 +44,7 @@ pub struct Modal {
|
||||
/// Flag to check first content render.
|
||||
first_draw: Arc<AtomicBool>,
|
||||
/// Background color.
|
||||
fill: Color32,
|
||||
fill: Option<Color32>,
|
||||
}
|
||||
|
||||
impl Modal {
|
||||
@@ -63,7 +63,7 @@ impl Modal {
|
||||
closeable: Arc::new(AtomicBool::new(true)),
|
||||
title: None,
|
||||
first_draw: Arc::new(AtomicBool::new(true)),
|
||||
fill: Colors::fill(),
|
||||
fill: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ impl Modal {
|
||||
/// Set custom background color.
|
||||
pub fn set_background_color(&self, color: Color32) {
|
||||
let mut w_state = MODAL_STATE.write();
|
||||
w_state.modal.as_mut().unwrap().fill = color;
|
||||
w_state.modal.as_mut().unwrap().fill = Some(color);
|
||||
}
|
||||
|
||||
/// Draw provided content.
|
||||
@@ -321,7 +321,7 @@ impl Modal {
|
||||
sw: 8.0 as u8,
|
||||
se: 8.0 as u8,
|
||||
}
|
||||
}, self.fill, Stroke::NONE, StrokeKind::Outside);
|
||||
}, self.fill.unwrap_or(Colors::fill_lite()), Stroke::NONE, StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
rect.min += egui::emath::vec2(6.0, 0.0);
|
||||
|
||||
@@ -12,18 +12,19 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Align, Layout, RichText, CornerRadius, StrokeKind};
|
||||
use eframe::epaint::RectShape;
|
||||
use egui::{Align, Color32, CornerRadius, CursorIcon, Layout, RichText, Sense, StrokeKind, UiBuilder};
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CARET_RIGHT, CHECK_CIRCLE, COMPUTER_TOWER, DOTS_THREE_CIRCLE, GLOBE_SIMPLE, PENCIL, PLUS_CIRCLE, POWER, TRASH, WARNING_CIRCLE, X_CIRCLE};
|
||||
use crate::gui::icons::{CHECK_CIRCLE, COMPUTER_TOWER, DOTS_THREE_CIRCLE, GLOBE_SIMPLE, PLUS_CIRCLE, POWER, TRASH, WARNING_CIRCLE, X_CIRCLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::views::network::modals::ExternalConnectionModal;
|
||||
use crate::gui::views::network::NodeSetup;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::node::{Node, NodeConfig};
|
||||
use crate::wallet::{ConnectionsConfig, ExternalConnection};
|
||||
use crate::AppConfig;
|
||||
|
||||
/// Network connections content.
|
||||
pub struct ConnectionsContent {
|
||||
@@ -81,12 +82,9 @@ impl ContentContainer for ConnectionsContent {
|
||||
}
|
||||
|
||||
// Show integrated node info content.
|
||||
Self::integrated_node_item_ui(ui, |ui| {
|
||||
// Draw button to show integrated node info.
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, None, || {
|
||||
AppConfig::toggle_show_connections_network_panel();
|
||||
});
|
||||
});
|
||||
Self::integrated_node_item_ui(ui, Colors::fill_lite(), (true, || {
|
||||
AppConfig::toggle_show_connections_network_panel();
|
||||
}), |_| false);
|
||||
|
||||
// Show external connections.
|
||||
ui.add_space(8.0);
|
||||
@@ -102,19 +100,19 @@ impl ContentContainer for ConnectionsContent {
|
||||
ui.add_space(4.0);
|
||||
|
||||
let ext_conn_list = ConnectionsConfig::ext_conn_list();
|
||||
let ext_conn_size = ext_conn_list.len();
|
||||
if ext_conn_size != 0 {
|
||||
let len = ext_conn_list.len();
|
||||
if len != 0 {
|
||||
ui.add_space(8.0);
|
||||
for (index, conn) in ext_conn_list.iter().enumerate() {
|
||||
for (i, c) in ext_conn_list.iter().enumerate() {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
// Draw connection list item.
|
||||
Self::ext_conn_item_ui(ui, conn, index, ext_conn_size, |ui| {
|
||||
let button_rounding = View::item_rounding(index, ext_conn_size, true);
|
||||
// Draw external connection list item.
|
||||
let bg = Colors::fill_lite();
|
||||
Self::ext_conn_item_ui(ui, bg, c, i, len, (true, || {
|
||||
self.show_add_ext_conn_modal(Some(c.clone()));
|
||||
}), |ui| {
|
||||
let button_rounding = View::item_rounding(i, len, true);
|
||||
View::item_button(ui, button_rounding, TRASH, None, || {
|
||||
ConnectionsConfig::remove_ext_conn(conn.id);
|
||||
});
|
||||
View::item_button(ui, CornerRadius::default(), PENCIL, None, || {
|
||||
self.show_add_ext_conn_modal(Some(conn.clone()));
|
||||
ConnectionsConfig::remove_ext_conn(c.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -125,118 +123,156 @@ impl ContentContainer for ConnectionsContent {
|
||||
|
||||
impl ConnectionsContent {
|
||||
/// Draw integrated node connection item content.
|
||||
pub fn integrated_node_item_ui(ui: &mut egui::Ui, custom_button: impl FnOnce(&mut egui::Ui)) {
|
||||
pub fn integrated_node_item_ui(ui: &mut egui::Ui,
|
||||
bg: Color32,
|
||||
on_click: (bool, impl FnOnce()),
|
||||
custom_button: impl FnOnce(&mut egui::Ui) -> bool) {
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(78.0);
|
||||
let rounding = View::item_rounding(0, 1, false);
|
||||
ui.painter().rect(rect, rounding, Colors::fill(), View::item_stroke(), StrokeKind::Outside);
|
||||
let r = View::item_rounding(0, 1, false);
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw custom button.
|
||||
custom_button(ui);
|
||||
let res = ui.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
// Draw custom button.
|
||||
let extra_button = custom_button(ui);
|
||||
|
||||
// Draw buttons to start/stop node.
|
||||
if Node::get_error().is_none() {
|
||||
if !Node::is_running() {
|
||||
View::item_button(ui, CornerRadius::default(), POWER, Some(Colors::green()), || {
|
||||
Node::start();
|
||||
});
|
||||
} else if !Node::is_starting() && !Node::is_stopping() && !Node::is_restarting() {
|
||||
View::item_button(ui, CornerRadius::default(), POWER, Some(Colors::red()), || {
|
||||
Node::stop(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(3.0);
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
ui.add_space(1.0);
|
||||
ui.label(RichText::new(t!("network.node"))
|
||||
.size(18.0)
|
||||
.color(Colors::title(false)));
|
||||
});
|
||||
|
||||
// Setup node status text.
|
||||
let has_error = Node::get_error().is_some();
|
||||
let status_icon = if has_error {
|
||||
WARNING_CIRCLE
|
||||
} else if !Node::is_running() {
|
||||
X_CIRCLE
|
||||
} else if Node::not_syncing() {
|
||||
CHECK_CIRCLE
|
||||
// Draw buttons to start/stop node.
|
||||
if Node::get_error().is_none() {
|
||||
let rounding = if extra_button {
|
||||
CornerRadius::default()
|
||||
} else {
|
||||
DOTS_THREE_CIRCLE
|
||||
View::item_rounding(0, 1, true)
|
||||
};
|
||||
let status_text = format!("{} {}", status_icon, if has_error {
|
||||
t!("error").into()
|
||||
} else {
|
||||
Node::get_sync_status_text()
|
||||
});
|
||||
View::ellipsize_text(ui, status_text, 15.0, Colors::text(false));
|
||||
ui.add_space(1.0);
|
||||
if !Node::is_running() {
|
||||
View::item_button(ui, rounding, POWER, Some(Colors::green()), || {
|
||||
Node::start();
|
||||
});
|
||||
} else if !Node::is_starting() && !Node::is_stopping() && !Node::is_restarting() {
|
||||
View::item_button(ui, rounding, POWER, Some(Colors::red()), || {
|
||||
Node::stop(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Setup node API address text.
|
||||
let api_address = NodeConfig::get_api_address();
|
||||
let address_text = format!("{} http://{}", COMPUTER_TOWER, api_address);
|
||||
ui.label(RichText::new(address_text).size(15.0).color(Colors::gray()));
|
||||
})
|
||||
});
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(3.0);
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
ui.add_space(1.0);
|
||||
ui.label(RichText::new(t!("network.node"))
|
||||
.size(18.0)
|
||||
.color(Colors::title(false)));
|
||||
});
|
||||
|
||||
// Setup node status text.
|
||||
let has_error = Node::get_error().is_some();
|
||||
let status_icon = if has_error {
|
||||
WARNING_CIRCLE
|
||||
} else if !Node::is_running() {
|
||||
X_CIRCLE
|
||||
} else if Node::not_syncing() {
|
||||
CHECK_CIRCLE
|
||||
} else {
|
||||
DOTS_THREE_CIRCLE
|
||||
};
|
||||
let status_text = format!("{} {}", status_icon, if has_error {
|
||||
t!("error").into()
|
||||
} else {
|
||||
Node::get_sync_status_text()
|
||||
});
|
||||
View::ellipsize_text(ui, status_text, 15.0, Colors::text(false));
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Setup node API address text.
|
||||
let api_address = NodeConfig::get_api_address();
|
||||
let address_text = format!("{} http://{}", COMPUTER_TOWER, api_address);
|
||||
ui.label(RichText::new(address_text).size(15.0).color(Colors::gray()));
|
||||
})
|
||||
});
|
||||
}).response;
|
||||
let (clickable, on_click) = on_click;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if clickable && res.hovered() {
|
||||
res.on_hover_cursor(CursorIcon::PointingHand);
|
||||
bg_shape.fill = Colors::fill();
|
||||
}
|
||||
ui.painter().set(bg_idx, bg_shape);
|
||||
// Handle clicks on layout.
|
||||
if clicked && clickable {
|
||||
on_click();
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw external connection item content.
|
||||
pub fn ext_conn_item_ui(ui: &mut egui::Ui,
|
||||
bg: Color32,
|
||||
conn: &ExternalConnection,
|
||||
index: usize,
|
||||
len: usize,
|
||||
buttons_ui: impl FnOnce(&mut egui::Ui)) {
|
||||
// Setup layout size.
|
||||
on_click: (bool, impl FnOnce()),
|
||||
custom_button: impl FnOnce(&mut egui::Ui)) {
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(52.0);
|
||||
let r = View::item_rounding(index, len, false);
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(index, len, false);
|
||||
ui.painter().rect(bg_rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
let res = ui.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
// Draw custom button.
|
||||
custom_button(ui);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw provided buttons.
|
||||
buttons_ui(ui);
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
// Draw connections URL.
|
||||
ui.add_space(4.0);
|
||||
let conn_text = format!("{} {}", GLOBE_SIMPLE, conn.url);
|
||||
View::ellipsize_text(ui, conn_text, 15.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
// Draw connections URL.
|
||||
ui.add_space(4.0);
|
||||
let conn_text = format!("{} {}", GLOBE_SIMPLE, conn.url);
|
||||
View::ellipsize_text(ui, conn_text, 15.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Setup connection status text.
|
||||
let status_text = if let Some(available) = conn.available {
|
||||
if available {
|
||||
format!("{} {}", CHECK_CIRCLE, t!("network.available"))
|
||||
// Setup connection status text.
|
||||
let status_text = if let Some(available) = conn.available {
|
||||
if available {
|
||||
format!("{} {}", CHECK_CIRCLE, t!("network.available"))
|
||||
} else {
|
||||
format!("{} {}", X_CIRCLE, t!("network.not_available"))
|
||||
}
|
||||
} else {
|
||||
format!("{} {}", X_CIRCLE, t!("network.not_available"))
|
||||
}
|
||||
} else {
|
||||
format!("{} {}", DOTS_THREE_CIRCLE, t!("network.availability_check"))
|
||||
};
|
||||
ui.label(RichText::new(status_text).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(3.0);
|
||||
format!("{} {}", DOTS_THREE_CIRCLE, t!("network.availability_check"))
|
||||
};
|
||||
ui.label(RichText::new(status_text).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
let (clickable, on_click) = on_click;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if clickable && res.hovered() {
|
||||
res.on_hover_cursor(CursorIcon::PointingHand);
|
||||
bg_shape.fill = Colors::fill();
|
||||
}
|
||||
ui.painter().set(bg_idx, bg_shape);
|
||||
// Handle clicks on layout.
|
||||
if clicked && clickable {
|
||||
on_click();
|
||||
}
|
||||
}
|
||||
|
||||
/// Show [`Modal`] to add external connection.
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
use eframe::emath::Align;
|
||||
use eframe::epaint::StrokeKind;
|
||||
use egui::{Id, Layout, RichText};
|
||||
use eframe::epaint::{RectShape, StrokeKind};
|
||||
use egui::{CursorIcon, Id, Layout, RichText, Sense, UiBuilder};
|
||||
use grin_core::global::ChainTypes;
|
||||
|
||||
use crate::gui::icons::{CLOCK_CLOCKWISE, COMPUTER_TOWER, FOLDERS, PENCIL, PLUG, POWER, SHIELD, SHIELD_SLASH};
|
||||
@@ -173,7 +173,7 @@ impl ContentContainer for NodeSetup {
|
||||
// Show data location selection for Desktop when it already started or turned off.
|
||||
if !Node::is_restarting() && !Node::is_stopping() && !Node::is_starting() &&
|
||||
View::is_desktop() {
|
||||
self.pick_data_dir_ui(ui, cb);
|
||||
self.data_dir_ui(ui, cb);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
@@ -236,46 +236,56 @@ impl ContentContainer for NodeSetup {
|
||||
|
||||
impl NodeSetup {
|
||||
/// Draw content to change chain data directory.
|
||||
fn pick_data_dir_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
// Setup layout size.
|
||||
fn data_dir_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(56.0);
|
||||
let r = View::item_rounding(0, 1, false);
|
||||
let bg = Colors::fill_lite();
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(0, 1, false);
|
||||
ui.painter().rect(bg_rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
self.pick_data_dir.ui(ui, cb, |path| {
|
||||
Node::change_data_dir(path);
|
||||
});
|
||||
View::item_button(ui, View::item_rounding(1, 3, true), PENCIL, None, || {
|
||||
self.data_path_edit = NodeConfig::get_chain_data_path();
|
||||
// Show chain data path edit modal.
|
||||
Modal::new(DATA_PATH_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network.node"))
|
||||
.show();
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
let path = NodeConfig::get_chain_data_path();
|
||||
View::ellipsize_text(ui, path, 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let desc = format!("{} {}", FOLDERS, t!("files_location"));
|
||||
ui.label(RichText::new(desc).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
let res = ui.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
self.pick_data_dir.ui(ui, cb, |path| {
|
||||
Node::change_data_dir(path);
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
let path = NodeConfig::get_chain_data_path();
|
||||
View::ellipsize_text(ui, path, 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let desc = format!("{} {}", FOLDERS, t!("files_location"));
|
||||
ui.label(RichText::new(desc).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() {
|
||||
res.on_hover_cursor(CursorIcon::PointingHand);
|
||||
bg_shape.fill = Colors::fill();
|
||||
}
|
||||
ui.painter().set(bg_idx, bg_shape);
|
||||
// Handle clicks on layout.
|
||||
if clicked {
|
||||
self.data_path_edit = NodeConfig::get_chain_data_path();
|
||||
// Show chain data path edit modal.
|
||||
Modal::new(DATA_PATH_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network.node"))
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw data path input [`Modal`] content.
|
||||
|
||||
@@ -786,7 +786,7 @@ fn peer_item_ui(ui: &mut egui::Ui,
|
||||
let item_rounding = View::item_rounding(index, len, false);
|
||||
ui.painter().rect(rect,
|
||||
item_rounding,
|
||||
Colors::white_or_black(false),
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
|
||||
|
||||
@@ -12,12 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Align, Layout, RichText, ScrollArea, StrokeKind};
|
||||
use eframe::epaint::RectShape;
|
||||
use egui::{Align, CursorIcon, Layout, RichText, Sense, StrokeKind, UiBuilder};
|
||||
|
||||
use crate::gui::icons::{CHECK, PENCIL, TRANSLATE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::types::ContentContainer;
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::AppConfig;
|
||||
@@ -28,22 +27,10 @@ pub struct InterfaceSettingsContent {
|
||||
locale: String,
|
||||
}
|
||||
|
||||
/// Identifier for language selection [`Modal`].
|
||||
const LANGUAGE_SELECTION_MODAL: &'static str = "language_selection_modal";
|
||||
|
||||
impl ContentContainer for InterfaceSettingsContent {
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![
|
||||
LANGUAGE_SELECTION_MODAL
|
||||
]
|
||||
}
|
||||
fn modal_ids(&self) -> Vec<&'static str> { vec![] }
|
||||
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, _: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
LANGUAGE_SELECTION_MODAL => self.language_selection_ui(ui),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
fn modal_ui(&mut self, _: &mut egui::Ui, _: &Modal, _: &dyn PlatformCallbacks) {}
|
||||
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, _: &dyn PlatformCallbacks) {
|
||||
ui.add_space(5.0);
|
||||
@@ -72,7 +59,10 @@ impl ContentContainer for InterfaceSettingsContent {
|
||||
}
|
||||
|
||||
// Draw language selection.
|
||||
self.language_item_ui(self.locale.clone().as_str(), ui, true, 0, 1);
|
||||
let locales = rust_i18n::available_locales!();
|
||||
for (index, locale) in locales.iter().enumerate() {
|
||||
self.language_item_ui(locale, ui, index, locales.len());
|
||||
}
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
}
|
||||
@@ -91,96 +81,29 @@ impl Default for InterfaceSettingsContent {
|
||||
}
|
||||
|
||||
impl InterfaceSettingsContent {
|
||||
/// Draw language selection content.
|
||||
fn language_selection_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.add_space(4.0);
|
||||
ScrollArea::vertical()
|
||||
.max_height(373.0)
|
||||
.id_salt("select_language_scroll")
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
.auto_shrink([true; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.add_space(2.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
let locales = rust_i18n::available_locales!();
|
||||
for (index, locale) in locales.iter().enumerate() {
|
||||
self.language_item_ui(locale, ui, false, index, locales.len());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(6.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show button to close modal.
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
Modal::close();
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Draw language selection item content.
|
||||
fn language_item_ui(&mut self, locale: &str, ui: &mut egui::Ui, edit: bool, index: usize, len: usize) {
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
if edit {
|
||||
rect.set_height(56.0);
|
||||
} else {
|
||||
rect.set_height(50.0);
|
||||
}
|
||||
|
||||
fn language_item_ui(&mut self, locale: &str, ui: &mut egui::Ui, index: usize, len: usize) {
|
||||
let is_current = self.locale == locale;
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(index, len, false);
|
||||
ui.painter().rect(bg_rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(50.0);
|
||||
let r = View::item_rounding(index, len, false);
|
||||
let bg = if is_current {
|
||||
Colors::fill()
|
||||
} else {
|
||||
Colors::fill_lite()
|
||||
};
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
if edit {
|
||||
View::item_button(ui, View::item_rounding(index, len, true), PENCIL, None, || {
|
||||
// Show language selection modal.
|
||||
Modal::new(LANGUAGE_SELECTION_MODAL)
|
||||
.position(ModalPosition::Center)
|
||||
.title(t!("language"))
|
||||
.show();
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
View::ellipsize_text(ui,
|
||||
t!("lang_name", locale = locale),
|
||||
18.0,
|
||||
Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let value = format!("{} {}",
|
||||
TRANSLATE,
|
||||
t!("language"));
|
||||
ui.label(RichText::new(value).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Draw button to select language.
|
||||
let is_current = self.locale == locale;
|
||||
if !is_current {
|
||||
View::item_button(ui, View::item_rounding(index, len, true), CHECK, None, || {
|
||||
rust_i18n::set_locale(locale);
|
||||
AppConfig::save_locale(locale);
|
||||
self.locale = locale.to_string();
|
||||
Modal::close();
|
||||
});
|
||||
} else {
|
||||
let res = ui.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
if is_current {
|
||||
View::selected_item_check(ui);
|
||||
}
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
@@ -195,10 +118,23 @@ impl InterfaceSettingsContent {
|
||||
ui.label(RichText::new(t!("lang_name", locale = locale))
|
||||
.size(17.0)
|
||||
.color(color));
|
||||
ui.add_space(3.0);
|
||||
ui.add_space(14.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
).response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() && !is_current {
|
||||
res.on_hover_cursor(CursorIcon::PointingHand);
|
||||
bg_shape.fill = Colors::fill();
|
||||
}
|
||||
ui.painter().set(bg_idx, bg_shape);
|
||||
// Handle clicks on layout.
|
||||
if clicked && !is_current {
|
||||
rust_i18n::set_locale(locale);
|
||||
AppConfig::save_locale(locale);
|
||||
self.locale = locale.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Align, Id, Layout, RichText, StrokeKind};
|
||||
use eframe::epaint::RectShape;
|
||||
use egui::{Align, CursorIcon, Id, Layout, RichText, Sense, StrokeKind, UiBuilder};
|
||||
use url::Url;
|
||||
|
||||
use crate::gui::icons::{CLOUD_CHECK, CLOUD_SLASH, PENCIL};
|
||||
use crate::gui::icons::{CLOUD_CHECK, CLOUD_SLASH};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
@@ -202,63 +203,71 @@ impl NetworkSettingsContent {
|
||||
|
||||
/// Draw proxy item content.
|
||||
fn proxy_item_ui(&mut self, ui: &mut egui::Ui) {
|
||||
// Setup layout size.
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(56.0);
|
||||
let r = View::item_rounding(0, 1, false);
|
||||
let bg = Colors::fill_lite();
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(0, 1, false);
|
||||
ui.painter().rect(bg_rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
let res = ui.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
let use_socks = AppConfig::use_socks_proxy();
|
||||
let proxy_url = if use_socks {
|
||||
AppConfig::socks_proxy_url()
|
||||
} else {
|
||||
AppConfig::http_proxy_url()
|
||||
};
|
||||
let (url, color, icon, text) = if let Some(url) = proxy_url {
|
||||
(url, Colors::title(false), CLOUD_CHECK, t!("network_settings.enabled"))
|
||||
} else {
|
||||
(
|
||||
t!("enter_url").into(),
|
||||
Colors::inactive_text(),
|
||||
CLOUD_SLASH,
|
||||
t!("network_settings.disabled")
|
||||
)
|
||||
};
|
||||
View::ellipsize_text(ui, url, 18.0, color);
|
||||
ui.add_space(1.0);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), PENCIL, None, || {
|
||||
let url = if AppConfig::use_socks_proxy() {
|
||||
AppConfig::socks_proxy_url().unwrap_or("".to_string())
|
||||
} else {
|
||||
AppConfig::http_proxy_url().unwrap_or("".to_string())
|
||||
};
|
||||
self.proxy_url_edit = url;
|
||||
// Show proxy URL edit modal.
|
||||
Modal::new(PROXY_URL_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("app_settings.proxy"))
|
||||
.show();
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
let use_socks = AppConfig::use_socks_proxy();
|
||||
let proxy_url = if use_socks {
|
||||
AppConfig::socks_proxy_url()
|
||||
} else {
|
||||
AppConfig::http_proxy_url()
|
||||
};
|
||||
let (url, color, icon, text) = if let Some(url) = proxy_url {
|
||||
(url, Colors::title(false), CLOUD_CHECK, t!("network_settings.enabled"))
|
||||
} else {
|
||||
(
|
||||
t!("enter_url").into(),
|
||||
Colors::inactive_text(),
|
||||
CLOUD_SLASH,
|
||||
t!("network_settings.disabled")
|
||||
)
|
||||
};
|
||||
View::ellipsize_text(ui, url, 18.0, color);
|
||||
ui.add_space(1.0);
|
||||
|
||||
let value = format!("{} {}", icon, text);
|
||||
ui.label(RichText::new(value).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(3.0);
|
||||
let value = format!("{} {}", icon, text);
|
||||
ui.label(RichText::new(value).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() {
|
||||
res.on_hover_cursor(CursorIcon::PointingHand);
|
||||
bg_shape.fill = Colors::fill();
|
||||
}
|
||||
ui.painter().set(bg_idx, bg_shape);
|
||||
// Handle clicks on layout.
|
||||
if clicked {
|
||||
let url = if AppConfig::use_socks_proxy() {
|
||||
AppConfig::socks_proxy_url().unwrap_or("".to_string())
|
||||
} else {
|
||||
AppConfig::http_proxy_url().unwrap_or("".to_string())
|
||||
};
|
||||
self.proxy_url_edit = url;
|
||||
// Show proxy URL edit modal.
|
||||
Modal::new(PROXY_URL_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("app_settings.proxy"))
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw proxy type selection.
|
||||
|
||||
+169
-154
@@ -12,9 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::os::OperatingSystem;
|
||||
use eframe::epaint::RectShape;
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Align, Id, Layout, RichText, ScrollArea, StrokeKind};
|
||||
use egui::{Align, CursorIcon, Id, Layout, RichText, ScrollArea, Sense, StrokeKind, UiBuilder};
|
||||
use std::fs;
|
||||
use url::Url;
|
||||
|
||||
@@ -194,9 +194,8 @@ impl ContentContainer for TorSettingsContent {
|
||||
|
||||
if bridge.is_some() {
|
||||
ui.add_space(6.0);
|
||||
// Show bridge selection for non-Android.
|
||||
let is_android = OperatingSystem::from_target_os() == OperatingSystem::Android;
|
||||
if !is_android {
|
||||
// Show bridge selection for desktop.
|
||||
if View::is_desktop() {
|
||||
let current_bridge = bridge.unwrap();
|
||||
let mut bridge = current_bridge.clone();
|
||||
|
||||
@@ -234,13 +233,12 @@ impl ContentContainer for TorSettingsContent {
|
||||
}
|
||||
|
||||
if let Some(br) = TorConfig::get_bridge().as_ref() {
|
||||
// Show bridge binary setup for non-Android.
|
||||
if !is_android {
|
||||
self.bridge_bin_ui(ui, br, cb);
|
||||
ui.add_space(10.0);
|
||||
}
|
||||
// Show bridge connection line setup.
|
||||
self.bridge_conn_line_ui(ui, br, cb);
|
||||
self.conn_line_ui(ui, br, cb);
|
||||
// Show bridge binary setup for desktop.
|
||||
if View::is_desktop() {
|
||||
self.bridge_bin_ui(ui, br, cb);
|
||||
}
|
||||
}
|
||||
|
||||
ui.add_space(8.0);
|
||||
@@ -361,153 +359,61 @@ impl TorSettingsContent {
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw bridge binary setup content.
|
||||
fn bridge_bin_ui(&mut self, ui: &mut egui::Ui, bridge: &TorBridge, cb: &dyn PlatformCallbacks) {
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(56.0);
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(0, 1, false);
|
||||
ui.painter().rect(bg_rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
self.bridge_bin_pick_file.ui(ui, cb, |path| {
|
||||
if bridge.binary_path() != path {
|
||||
TorBridge::save_bridge_bin_path(bridge, path);
|
||||
self.settings_changed = true;
|
||||
}
|
||||
});
|
||||
View::item_button(ui, View::item_rounding(1, 3, true), PENCIL, None, || {
|
||||
self.bridge_bin_path_edit = bridge.binary_path();
|
||||
// Show binary path edit modal.
|
||||
let title = bridge.protocol_name();
|
||||
Modal::new(BRIDGE_BIN_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(title)
|
||||
.show();
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
View::ellipsize_text(ui, bridge.binary_path(), 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let value = format!("{} {}",
|
||||
TERMINAL,
|
||||
t!("transport.bin_file").replace(":", ""));
|
||||
ui.label(RichText::new(value).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw bridge binary input [`Modal`] content.
|
||||
fn bridge_bin_edit_modal_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
let on_save = |c: &mut TorSettingsContent| {
|
||||
let bridge = TorConfig::get_bridge().unwrap();
|
||||
let exists = fs::exists(&c.bridge_bin_path_edit).unwrap_or_default();
|
||||
if !exists {
|
||||
return;
|
||||
}
|
||||
if bridge.binary_path() != c.bridge_bin_path_edit {
|
||||
TorBridge::save_bridge_bin_path(&bridge, c.bridge_bin_path_edit.clone());
|
||||
c.settings_changed = true;
|
||||
}
|
||||
Modal::close();
|
||||
};
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.bin_file"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw bridge text edit.
|
||||
let mut edit = TextEdit::new(Id::from(BRIDGE_BIN_EDIT_MODAL)).paste();
|
||||
edit.ui(ui, &mut self.bridge_bin_path_edit, cb);
|
||||
if edit.enter_pressed {
|
||||
on_save(self);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Show modal buttons.
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
on_save(self);
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw bridge connection line setup content.
|
||||
fn bridge_conn_line_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
bridge: &TorBridge,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
// Setup layout size.
|
||||
fn conn_line_ui(&mut self, ui: &mut egui::Ui, bridge: &TorBridge, cb: &dyn PlatformCallbacks) {
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(56.0);
|
||||
let r = if View::is_desktop() {
|
||||
View::item_rounding(0, 2, false)
|
||||
} else {
|
||||
View::item_rounding(0, 1, false)
|
||||
};
|
||||
let bg = Colors::fill_lite();
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(0, 1, false);
|
||||
ui.painter().rect(bg_rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), SCAN, None, || {
|
||||
self.show_qr_scan_bridge_modal(cb);
|
||||
});
|
||||
View::item_button(ui, View::item_rounding(1, 3 , true), PENCIL, None, || {
|
||||
self.bridge_conn_line_edit = bridge.connection_line();
|
||||
// Show connection line edit modal.
|
||||
let title = bridge.protocol_name();
|
||||
Modal::new(BRIDGE_CONN_LINE_EDIT_MODAL)
|
||||
.position(ModalPosition::Center)
|
||||
.title(title)
|
||||
.show();
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
View::ellipsize_text(ui, bridge.connection_line(), 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let value = format!("{} {}",
|
||||
NOTCHES,
|
||||
t!("transport.conn_line").replace(":", ""));
|
||||
ui.label(RichText::new(value).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(3.0);
|
||||
let res = ui.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), SCAN, None, || {
|
||||
self.show_qr_scan_bridge_modal(cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
let line_text = bridge.connection_line();
|
||||
View::ellipsize_text(ui, line_text, 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let line_desc = t!("transport.conn_line").replace(":", "");
|
||||
let value = format!("{} {}", NOTCHES, line_desc);
|
||||
ui.label(RichText::new(value).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() {
|
||||
res.on_hover_cursor(CursorIcon::PointingHand);
|
||||
bg_shape.fill = Colors::fill();
|
||||
}
|
||||
ui.painter().set(bg_idx, bg_shape);
|
||||
// Handle clicks on layout.
|
||||
if clicked {
|
||||
self.bridge_conn_line_edit = bridge.connection_line();
|
||||
// Show connection line edit modal.
|
||||
let title = bridge.protocol_name();
|
||||
Modal::new(BRIDGE_CONN_LINE_EDIT_MODAL)
|
||||
.position(ModalPosition::Center)
|
||||
.title(title)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
/// Show bridge connection line QR code scanner.
|
||||
@@ -557,7 +463,7 @@ impl TorSettingsContent {
|
||||
.id(input_id)
|
||||
.font(egui::TextStyle::Body)
|
||||
.desired_rows(5)
|
||||
.interactive(true)
|
||||
.interactive(false)
|
||||
.desired_width(f32::INFINITY)
|
||||
.show(ui);
|
||||
ui.add_space(6.0);
|
||||
@@ -614,4 +520,113 @@ impl TorSettingsContent {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw bridge binary setup content.
|
||||
fn bridge_bin_ui(&mut self, ui: &mut egui::Ui, bridge: &TorBridge, cb: &dyn PlatformCallbacks) {
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(56.0);
|
||||
let r = View::item_rounding(1, 2, false);
|
||||
let bg = Colors::fill_lite();
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
let res = ui.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
self.bridge_bin_pick_file.ui(ui, cb, |path| {
|
||||
if bridge.binary_path() != path {
|
||||
TorBridge::save_bridge_bin_path(bridge, path);
|
||||
self.settings_changed = true;
|
||||
}
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
let bin_text = bridge.binary_path();
|
||||
View::ellipsize_text(ui, bin_text, 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let bin_desc = t!("transport.bin_file").replace(":", "");
|
||||
let value = format!("{} {}", TERMINAL, bin_desc);
|
||||
ui.label(RichText::new(value).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() {
|
||||
res.on_hover_cursor(CursorIcon::PointingHand);
|
||||
bg_shape.fill = Colors::fill();
|
||||
}
|
||||
ui.painter().set(bg_idx, bg_shape);
|
||||
// Handle clicks on layout.
|
||||
if clicked {
|
||||
self.bridge_bin_path_edit = bridge.binary_path();
|
||||
// Show binary path edit modal.
|
||||
let title = bridge.protocol_name();
|
||||
Modal::new(BRIDGE_BIN_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(title)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw bridge binary input [`Modal`] content.
|
||||
fn bridge_bin_edit_modal_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
let on_save = |c: &mut TorSettingsContent| {
|
||||
let bridge = TorConfig::get_bridge().unwrap();
|
||||
let exists = fs::exists(&c.bridge_bin_path_edit).unwrap_or_default();
|
||||
if !exists {
|
||||
return;
|
||||
}
|
||||
if bridge.binary_path() != c.bridge_bin_path_edit {
|
||||
TorBridge::save_bridge_bin_path(&bridge, c.bridge_bin_path_edit.clone());
|
||||
c.settings_changed = true;
|
||||
}
|
||||
Modal::close();
|
||||
};
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.bin_file"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw bridge text edit.
|
||||
let mut edit = TextEdit::new(Id::from(BRIDGE_BIN_EDIT_MODAL)).paste();
|
||||
edit.ui(ui, &mut self.bridge_bin_path_edit, cb);
|
||||
if edit.enter_pressed {
|
||||
on_save(self);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Show modal buttons.
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
on_save(self);
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -186,6 +186,8 @@ impl View {
|
||||
// Setup padding for title buttons.
|
||||
if !View::is_desktop() {
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(20.0, 8.0);
|
||||
} else {
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(16.0, 8.0);
|
||||
}
|
||||
// Disable strokes.
|
||||
ui.style_mut().visuals.widgets.inactive.bg_stroke = Stroke::NONE;
|
||||
@@ -358,7 +360,7 @@ impl View {
|
||||
ui.scope(|ui| {
|
||||
// Setup padding for item buttons.
|
||||
let padding = if Self::is_desktop() {
|
||||
14.0
|
||||
15.0
|
||||
} else {
|
||||
18.0
|
||||
};
|
||||
|
||||
@@ -12,27 +12,29 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::time::Duration;
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Align, CornerRadius, Id, Layout, Margin, OpenUrl, RichText, ScrollArea, StrokeKind};
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Align, CornerRadius, CursorIcon, Id, Layout, Margin, OpenUrl, RichText, ScrollArea, Sense, StrokeKind, UiBuilder};
|
||||
use egui_async::Bind;
|
||||
use crate::gui::icons::{ARROW_LEFT, BOOKMARKS, CALENDAR_CHECK, CARET_RIGHT, CLOUD_ARROW_DOWN, COMPUTER_TOWER, FOLDER_OPEN, FOLDER_PLUS, GEAR, GEAR_FINE, GLOBE, GLOBE_SIMPLE, LOCK_KEY, NOTEPAD, PLUS, SIDEBAR_SIMPLE, SUITCASE};
|
||||
use std::time::Duration;
|
||||
use eframe::epaint::RectShape;
|
||||
|
||||
use crate::gui::icons::{ARROW_LEFT, BOOKMARKS, CALENDAR_CHECK, CLOUD_ARROW_DOWN, COMPUTER_TOWER, FOLDER_PLUS, GEAR, GEAR_FINE, GLOBE, GLOBE_SIMPLE, LOCK_KEY, NOTEPAD, PLUS, SIDEBAR_SIMPLE, SUITCASE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::settings::SettingsContent;
|
||||
use crate::gui::views::types::{ContentContainer, LinePosition, ModalPosition, TitleContentType, TitleType};
|
||||
use crate::gui::views::wallets::creation::WalletCreationContent;
|
||||
use crate::gui::views::wallets::modals::{AddWalletModal, OpenWalletModal, WalletSettingsModal, WalletListModal, ChangelogContent};
|
||||
use crate::gui::views::wallets::modals::{AddWalletModal, ChangelogContent, OpenWalletModal, WalletListModal, WalletSettingsModal};
|
||||
use crate::gui::views::wallets::wallet::types::{wallet_status_text, WalletContentContainer};
|
||||
use crate::gui::views::wallets::wallet::RecoverySettings;
|
||||
use crate::gui::views::wallets::WalletContent;
|
||||
use crate::gui::views::{Content, Modal, TitlePanel, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::http::{retrieve_release, ReleaseInfo};
|
||||
use crate::settings::AppUpdate;
|
||||
use crate::wallet::types::{ConnectionMethod, WalletTask};
|
||||
use crate::wallet::{Wallet, WalletList};
|
||||
use crate::AppConfig;
|
||||
use crate::gui::views::wallets::wallet::RecoverySettings;
|
||||
use crate::http::{retrieve_release, ReleaseInfo};
|
||||
use crate::settings::AppUpdate;
|
||||
|
||||
/// Wallets content.
|
||||
pub struct WalletsContent {
|
||||
@@ -444,7 +446,12 @@ impl WalletsContent {
|
||||
|| (dual_panel && !show_list)) && !creating_wallet && !showing_settings {
|
||||
let title = self.wallet_content.title().into();
|
||||
let subtitle = self.wallets.selected().unwrap().get_config().name;
|
||||
TitleType::Single(TitleContentType::WithSubTitle(title, subtitle, false))
|
||||
let wallet_title_content = if self.wallet_content.settings_content.is_some() {
|
||||
TitleContentType::Title(title)
|
||||
} else {
|
||||
TitleContentType::WithSubTitle(title, subtitle, false)
|
||||
};
|
||||
TitleType::Single(wallet_title_content)
|
||||
} else {
|
||||
let title_text = if showing_settings {
|
||||
t!("settings")
|
||||
@@ -458,7 +465,11 @@ impl WalletsContent {
|
||||
if dual_title {
|
||||
let title = self.wallet_content.title().into();
|
||||
let subtitle = self.wallets.selected().unwrap().get_config().name;
|
||||
let wallet_title_content = TitleContentType::WithSubTitle(title, subtitle, false);
|
||||
let wallet_title_content = if self.wallet_content.settings_content.is_some() {
|
||||
TitleContentType::Title(title)
|
||||
} else {
|
||||
TitleContentType::WithSubTitle(title, subtitle, false)
|
||||
};
|
||||
TitleType::Dual(TitleContentType::Title(title_text), wallet_title_content)
|
||||
} else {
|
||||
TitleType::Single(TitleContentType::Title(title_text))
|
||||
@@ -579,8 +590,12 @@ impl WalletsContent {
|
||||
} else {
|
||||
false
|
||||
};
|
||||
// Unselect wallet when opening or settings modal was closed.
|
||||
if current && !w.is_open() && Modal::opened().is_none() {
|
||||
self.wallets.select(None);
|
||||
}
|
||||
self.wallet_item_ui(ui, w, current, cb);
|
||||
ui.add_space(5.0);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -593,88 +608,99 @@ impl WalletsContent {
|
||||
current: bool,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
let config = wallet.get_config();
|
||||
let can_open = !wallet.is_open() && !wallet.files_moving();
|
||||
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(78.0);
|
||||
let rounding = View::item_rounding(0, 1, false);
|
||||
let r = View::item_rounding(0, 1, false);
|
||||
let bg = if current {
|
||||
Colors::fill_deep()
|
||||
} else {
|
||||
Colors::fill()
|
||||
};
|
||||
ui.painter().rect(rect, rounding, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
if !wallet.is_open() && !wallet.files_moving() {
|
||||
// Show button to open closed wallet.
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), FOLDER_OPEN, None, || {
|
||||
self.show_opening_modal(wallet, None, cb);
|
||||
});
|
||||
if !wallet.is_repairing() {
|
||||
View::item_button(ui, CornerRadius::default(), GEAR_FINE, None, || {
|
||||
self.select_wallet(wallet, None, cb);
|
||||
let conn = wallet.get_current_connection();
|
||||
self.wallet_settings_content = WalletSettingsModal::new(conn);
|
||||
// Show connection selection modal.
|
||||
Modal::new(WALLET_SETTINGS_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.settings"))
|
||||
.show();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if !current {
|
||||
// Show button to select opened wallet.
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, None, || {
|
||||
self.select_wallet(wallet, None, cb);
|
||||
});
|
||||
}
|
||||
// Show button to close opened wallet.
|
||||
if !wallet.is_closing() {
|
||||
View::item_button(ui, if !current {
|
||||
CornerRadius::default()
|
||||
} else {
|
||||
View::item_rounding(0, 1, true)
|
||||
}, LOCK_KEY, None, || {
|
||||
let res = ui.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
if can_open {
|
||||
if !wallet.is_repairing() {
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), GEAR_FINE, None, || {
|
||||
self.select_wallet(wallet, None, cb);
|
||||
let conn = wallet.get_current_connection();
|
||||
self.wallet_settings_content = WalletSettingsModal::new(conn);
|
||||
// Show connection selection modal.
|
||||
Modal::new(WALLET_SETTINGS_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.settings"))
|
||||
.show();
|
||||
});
|
||||
}
|
||||
} else if !wallet.is_closing() {
|
||||
// Show button to close opened wallet.
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), LOCK_KEY, None, || {
|
||||
wallet.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(3.0);
|
||||
// Show wallet name text.
|
||||
let name_color = if current {
|
||||
Colors::white_or_black(true)
|
||||
} else {
|
||||
Colors::title(false)
|
||||
};
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(3.0);
|
||||
// Show wallet name text.
|
||||
let name_color = if current {
|
||||
Colors::white_or_black(true)
|
||||
} else {
|
||||
Colors::title(false)
|
||||
};
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
ui.add_space(1.0);
|
||||
View::ellipsize_text(ui, config.name, 18.0, name_color);
|
||||
});
|
||||
|
||||
// Show wallet status text.
|
||||
let status_text = wallet_status_text(wallet);
|
||||
View::ellipsize_text(ui, status_text, 15.0, Colors::text(false));
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Show wallet connection text.
|
||||
let connection = wallet.get_current_connection();
|
||||
let conn_text = match connection {
|
||||
ConnectionMethod::Integrated => {
|
||||
format!("{} {}", COMPUTER_TOWER, t!("network.node"))
|
||||
}
|
||||
ConnectionMethod::External(_, url) => {
|
||||
format!("{} {}", GLOBE_SIMPLE, url)
|
||||
}
|
||||
};
|
||||
View::ellipsize_text(ui, conn_text, 15.0, Colors::gray());
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
|
||||
// Show wallet status text.
|
||||
View::ellipsize_text(ui, wallet_status_text(wallet), 15.0, Colors::text(false));
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Show wallet connection text.
|
||||
let connection = wallet.get_current_connection();
|
||||
let conn_text = match connection {
|
||||
ConnectionMethod::Integrated => {
|
||||
format!("{} {}", COMPUTER_TOWER, t!("network.node"))
|
||||
}
|
||||
ConnectionMethod::External(_, url) => format!("{} {}", GLOBE_SIMPLE, url)
|
||||
};
|
||||
View::ellipsize_text(ui, conn_text, 15.0, Colors::gray());
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() && (can_open || !current) {
|
||||
res.on_hover_cursor(CursorIcon::PointingHand);
|
||||
bg_shape.fill = Colors::fill_deep();
|
||||
}
|
||||
ui.painter().set(bg_idx, bg_shape);
|
||||
// Handle clicks on layout.
|
||||
if clicked {
|
||||
if can_open {
|
||||
// Show modal to open the wallet.
|
||||
self.show_opening_modal(wallet, None, cb);
|
||||
} else if !current {
|
||||
// Select opened wallet.
|
||||
self.select_wallet(wallet, None, cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw update information content.
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{RichText, ScrollArea};
|
||||
|
||||
use crate::gui::icons::{CHECK, PLUS_CIRCLE, TRASH};
|
||||
use crate::gui::icons::{PLUS_CIRCLE, TRASH};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::network::modals::ExternalConnectionModal;
|
||||
use crate::gui::views::network::ConnectionsContent;
|
||||
@@ -79,18 +79,20 @@ impl WalletSettingsModal {
|
||||
ui.add_space(2.0);
|
||||
|
||||
// Show integrated node selection.
|
||||
ConnectionsContent::integrated_node_item_ui(ui, |ui| {
|
||||
match self.conn {
|
||||
ConnectionMethod::Integrated => {
|
||||
View::selected_item_check(ui);
|
||||
}
|
||||
_ => {
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), CHECK, None, || {
|
||||
on_select(ConnectionMethod::Integrated);
|
||||
Modal::close();
|
||||
});
|
||||
}
|
||||
let cur_integrated = self.conn == ConnectionMethod::Integrated;
|
||||
let bg = if cur_integrated {
|
||||
Colors::fill()
|
||||
} else {
|
||||
Colors::fill_lite()
|
||||
};
|
||||
ConnectionsContent::integrated_node_item_ui(ui, bg, (!cur_integrated, || {
|
||||
on_select(ConnectionMethod::Integrated);
|
||||
Modal::close();
|
||||
}), |ui| {
|
||||
if cur_integrated {
|
||||
View::selected_item_check(ui);
|
||||
}
|
||||
cur_integrated
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
@@ -109,29 +111,31 @@ impl WalletSettingsModal {
|
||||
|
||||
if !ext_conn_list.is_empty() {
|
||||
ui.add_space(8.0);
|
||||
for (index, conn) in ext_conn_list.iter().enumerate() {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let len = ext_conn_list.len();
|
||||
ConnectionsContent::ext_conn_item_ui(ui, conn, index, len, |ui| {
|
||||
let current_ext_conn = match self.conn {
|
||||
ConnectionMethod::Integrated => false,
|
||||
ConnectionMethod::External(id, _) => id == conn.id
|
||||
};
|
||||
if current_ext_conn {
|
||||
View::selected_item_check(ui);
|
||||
} else {
|
||||
let button_rounding = View::item_rounding(index, len, true);
|
||||
View::item_button(ui, button_rounding, CHECK, None, || {
|
||||
on_select(
|
||||
ConnectionMethod::External(conn.id, conn.url.clone())
|
||||
);
|
||||
Modal::close();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
for (i, c) in ext_conn_list.iter().enumerate() {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let len = ext_conn_list.len();
|
||||
let is_current = match self.conn {
|
||||
ConnectionMethod::External(id, _) => id == c.id,
|
||||
_ => false
|
||||
};
|
||||
let bg = if is_current {
|
||||
Colors::fill()
|
||||
} else {
|
||||
Colors::fill_lite()
|
||||
};
|
||||
ConnectionsContent::ext_conn_item_ui(ui, bg, c, i, len, (!is_current, || {
|
||||
on_select(
|
||||
ConnectionMethod::External(c.id, c.url.clone())
|
||||
);
|
||||
Modal::close();
|
||||
}), |ui| {
|
||||
if is_current {
|
||||
View::selected_item_check(ui);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
ui.add_space(4.0);
|
||||
});
|
||||
|
||||
|
||||
@@ -256,7 +256,7 @@ impl WalletContentContainer for WalletContent {
|
||||
.show_inside(ui, |ui| {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
let show_settings = self.settings_content.is_some();
|
||||
let show_txs = self.txs_content.is_some();
|
||||
let show_txs = self.txs_content.is_some() && !top_panel_expanded;
|
||||
let show_sync = (!show_settings || block_nav) &&
|
||||
sync_ui(ui, &wallet);
|
||||
if !show_sync {
|
||||
@@ -457,7 +457,7 @@ impl WalletContent {
|
||||
None => {
|
||||
// Show transaction modal on wallet task result.
|
||||
if let Some(id) = id {
|
||||
let tx = wallet.get_data().unwrap().tx_by_slate_id(id);
|
||||
let tx = wallet.get_data().unwrap().tx_by_id(id);
|
||||
if tx.is_some() {
|
||||
self.txs_content = Some(WalletTransactionsContent::new(tx));
|
||||
self.settings_content = None;
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::gui::icons::{BROOM, CLIPBOARD_TEXT, COPY, FILE_TEXT, SEAL_CHECK};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{FilePickContent, FilePickContentType, Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::types::{WalletTask, WalletTransaction};
|
||||
use crate::wallet::types::{WalletTask, WalletTx};
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
pub struct PaymentProofContent {
|
||||
@@ -154,7 +154,7 @@ impl PaymentProofContent {
|
||||
/// Draw transaction payment proof content to share.
|
||||
pub fn share_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
tx: &WalletTransaction,
|
||||
tx: &WalletTx,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
// limitations under the License.
|
||||
|
||||
use eframe::emath::Align;
|
||||
use eframe::epaint::StrokeKind;
|
||||
use egui::{Id, Layout, RichText};
|
||||
use eframe::epaint::{RectShape, StrokeKind};
|
||||
use egui::{CursorIcon, Id, Layout, RichText, Sense, UiBuilder};
|
||||
|
||||
use crate::gui::icons::{CLOCK_COUNTDOWN, FOLDERS, FOLDER_USER, PASSWORD, PENCIL};
|
||||
use crate::gui::icons::{CLOCK_COUNTDOWN, FOLDERS, FOLDER_USER, PASSWORD};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::gui::views::wallets::wallet::types::WalletContentContainer;
|
||||
@@ -79,7 +79,11 @@ impl WalletContentContainer for CommonSettings {
|
||||
}
|
||||
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(8.0);
|
||||
if View::is_desktop() {
|
||||
ui.add_space(1.0);
|
||||
} else {
|
||||
ui.add_space(8.0);
|
||||
}
|
||||
ui.vertical_centered(|ui| {
|
||||
let config = wallet.get_config();
|
||||
// Show wallet name.
|
||||
@@ -106,7 +110,7 @@ impl WalletContentContainer for CommonSettings {
|
||||
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Setup ability to post wallet transactions with Dandelion.
|
||||
// Ability to post wallet transactions with Dandelion.
|
||||
View::checkbox(ui, wallet.can_use_dandelion(), t!("wallets.use_dandelion"), || {
|
||||
wallet.update_use_dandelion(!wallet.can_use_dandelion());
|
||||
});
|
||||
@@ -137,60 +141,68 @@ impl Default for CommonSettings {
|
||||
impl CommonSettings {
|
||||
/// Draw content to change wallet name and password.
|
||||
fn name_ui(&mut self, ui: &mut egui::Ui, name: String) {
|
||||
// Setup layout size.
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(56.0);
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = if View::is_desktop() {
|
||||
let r = if View::is_desktop() {
|
||||
View::item_rounding(0, 2, false)
|
||||
} else {
|
||||
View::item_rounding(0, 1, false)
|
||||
};
|
||||
ui.painter().rect(bg_rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
let bg = Colors::fill_lite();
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
let r = if View::is_desktop() {
|
||||
View::item_rounding(0, 2, true)
|
||||
} else {
|
||||
View::item_rounding(0, 1, true)
|
||||
};
|
||||
View::item_button(ui, r, PASSWORD, None, || {
|
||||
self.old_pass_edit = "".to_string();
|
||||
self.new_pass_edit = "".to_string();
|
||||
self.wrong_pass = false;
|
||||
// Show wallet password modal.
|
||||
Modal::new(PASS_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.wallet"))
|
||||
.show();
|
||||
});
|
||||
View::item_button(ui, View::item_rounding(1, 3, true), PENCIL, None, || {
|
||||
self.name_edit = name.clone();
|
||||
// Show wallet name modal.
|
||||
Modal::new(NAME_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.wallet"))
|
||||
.show();
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
View::ellipsize_text(ui, name, 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let desc = format!("{} {}", FOLDER_USER, t!("wallets.name").replace(":", ""));
|
||||
ui.label(RichText::new(desc).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
let res = ui.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
let r = if View::is_desktop() {
|
||||
View::item_rounding(0, 2, true)
|
||||
} else {
|
||||
View::item_rounding(0, 1, true)
|
||||
};
|
||||
View::item_button(ui, r, PASSWORD, None, || {
|
||||
self.old_pass_edit = "".to_string();
|
||||
self.new_pass_edit = "".to_string();
|
||||
self.wrong_pass = false;
|
||||
// Show wallet password modal.
|
||||
Modal::new(PASS_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.wallet"))
|
||||
.show();
|
||||
});
|
||||
});
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
View::ellipsize_text(ui, name.clone(), 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let desc = format!("{} {}", FOLDER_USER, t!("wallets.name").replace(":", ""));
|
||||
ui.label(RichText::new(desc).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() {
|
||||
res.on_hover_cursor(CursorIcon::PointingHand);
|
||||
bg_shape.fill = Colors::fill();
|
||||
}
|
||||
ui.painter().set(bg_idx, bg_shape);
|
||||
// Handle clicks on layout.
|
||||
if clicked {
|
||||
self.name_edit = name;
|
||||
// Show wallet name modal.
|
||||
Modal::new(NAME_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.wallet"))
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw wallet name [`Modal`] content.
|
||||
@@ -340,45 +352,53 @@ impl CommonSettings {
|
||||
|
||||
/// Draw content to change wallet data directory.
|
||||
fn data_dir_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||
// Setup layout size.
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(56.0);
|
||||
let r = View::item_rounding(1, 2, false);
|
||||
let bg = Colors::fill_lite();
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(1, 2, false);
|
||||
ui.painter().rect(bg_rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
self.pick_data_dir.ui(ui, cb, |path| {
|
||||
wallet.change_data_path(path);
|
||||
});
|
||||
View::item_button(ui, View::item_rounding(1, 3, true), PENCIL, None, || {
|
||||
self.data_path_edit = wallet.get_config().data_path.unwrap_or_default();
|
||||
// Show chain data path edit modal.
|
||||
Modal::new(DATA_PATH_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.wallet"))
|
||||
.show();
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
let path = wallet.get_config().data_path.unwrap_or_default();
|
||||
View::ellipsize_text(ui, path, 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let desc = format!("{} {}", FOLDERS, t!("files_location"));
|
||||
ui.label(RichText::new(desc).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
let res = ui.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
self.pick_data_dir.ui(ui, cb, |path| {
|
||||
wallet.change_data_path(path);
|
||||
});
|
||||
});
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
let path = wallet.get_config().data_path.unwrap_or_default();
|
||||
View::ellipsize_text(ui, path, 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let desc = format!("{} {}", FOLDERS, t!("files_location"));
|
||||
ui.label(RichText::new(desc).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() {
|
||||
res.on_hover_cursor(CursorIcon::PointingHand);
|
||||
bg_shape.fill = Colors::fill();
|
||||
}
|
||||
ui.painter().set(bg_idx, bg_shape);
|
||||
// Handle clicks on layout.
|
||||
if clicked {
|
||||
self.data_path_edit = wallet.get_config().data_path.unwrap_or_default();
|
||||
// Show chain data path edit modal.
|
||||
Modal::new(DATA_PATH_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.wallet"))
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw data path input [`Modal`] content.
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Align, Layout, RichText, StrokeKind};
|
||||
use egui::RichText;
|
||||
|
||||
use crate::gui::icons::{CHECK, CHECK_CIRCLE, DOTS_THREE_CIRCLE, GLOBE, GLOBE_SIMPLE, PLUS_CIRCLE, X_CIRCLE};
|
||||
use crate::gui::icons::{GLOBE, PLUS_CIRCLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::network::modals::ExternalConnectionModal;
|
||||
use crate::gui::views::network::ConnectionsContent;
|
||||
@@ -70,15 +70,19 @@ impl ContentContainer for ConnectionSettings {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add_space(6.0);
|
||||
// Show integrated node selection.
|
||||
ConnectionsContent::integrated_node_item_ui(ui, |ui| {
|
||||
let is_current_method = self.method == ConnectionMethod::Integrated;
|
||||
if !is_current_method {
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), CHECK, None, || {
|
||||
self.method = ConnectionMethod::Integrated;
|
||||
});
|
||||
} else {
|
||||
let cur_integrated = self.method == ConnectionMethod::Integrated;
|
||||
let bg = if cur_integrated {
|
||||
Colors::fill_deep()
|
||||
} else {
|
||||
Colors::fill_lite()
|
||||
};
|
||||
ConnectionsContent::integrated_node_item_ui(ui, bg, (!cur_integrated, || {
|
||||
self.method = ConnectionMethod::Integrated;
|
||||
}), |ui| {
|
||||
if cur_integrated {
|
||||
View::selected_item_check(ui);
|
||||
}
|
||||
cur_integrated
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
@@ -119,8 +123,8 @@ impl ContentContainer for ConnectionSettings {
|
||||
}
|
||||
}
|
||||
|
||||
let ext_size = ext_conn_list.len();
|
||||
if ext_size != 0 {
|
||||
let len = ext_conn_list.len();
|
||||
if len != 0 {
|
||||
ui.add_space(8.0);
|
||||
for (i, c) in ext_conn_list.iter().enumerate() {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
@@ -129,73 +133,21 @@ impl ContentContainer for ConnectionSettings {
|
||||
ConnectionMethod::External(id, url) => id == &c.id || url == &c.url,
|
||||
_ => false
|
||||
};
|
||||
Self::ext_conn_item_ui(ui, c, is_current, i, ext_size, || {
|
||||
let bg = if is_current {
|
||||
Colors::fill()
|
||||
} else {
|
||||
Colors::fill_lite()
|
||||
};
|
||||
ConnectionsContent::ext_conn_item_ui(ui, bg, c, i, len, (!is_current, || {
|
||||
self.method = ConnectionMethod::External(c.id, c.url.clone());
|
||||
}), |ui| {
|
||||
if is_current {
|
||||
View::selected_item_check(ui);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionSettings {
|
||||
/// Draw external connection item content.
|
||||
fn ext_conn_item_ui(ui: &mut egui::Ui,
|
||||
conn: &ExternalConnection,
|
||||
is_current: bool,
|
||||
index: usize,
|
||||
len: usize,
|
||||
mut on_select: impl FnMut()) {
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(52.0);
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(index, len, false);
|
||||
ui.painter().rect(bg_rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
if is_current {
|
||||
View::selected_item_check(ui);
|
||||
} else {
|
||||
// Draw button to select connection.
|
||||
let button_rounding = View::item_rounding(index, len, true);
|
||||
View::item_button(ui, button_rounding, CHECK, None, || {
|
||||
on_select();
|
||||
});
|
||||
}
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
// Draw connections URL.
|
||||
ui.add_space(4.0);
|
||||
let conn_text = format!("{} {}", GLOBE_SIMPLE, conn.url);
|
||||
View::ellipsize_text(ui, conn_text, 15.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
// Setup connection status text.
|
||||
let status_text = if let Some(available) = conn.available {
|
||||
if available {
|
||||
format!("{} {}", CHECK_CIRCLE, t!("network.available"))
|
||||
} else {
|
||||
format!("{} {}", X_CIRCLE, t!("network.not_available"))
|
||||
}
|
||||
} else {
|
||||
format!("{} {}", DOTS_THREE_CIRCLE, t!("network.availability_check"))
|
||||
};
|
||||
ui.label(RichText::new(status_text).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -14,21 +14,21 @@
|
||||
|
||||
use egui::epaint::RectShape;
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Align, CornerRadius, Id, Layout, Rect, RichText, ScrollArea, StrokeKind};
|
||||
use egui::{Align, Color32, CornerRadius, CursorIcon, Id, Layout, Rect, RichText, ScrollArea, Sense, StrokeKind, UiBuilder};
|
||||
use grin_core::consensus::COINBASE_MATURITY;
|
||||
use grin_core::core::amount_to_hr_string;
|
||||
use grin_wallet_libwallet::TxLogEntryType;
|
||||
use std::ops::Range;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::gui::icons::{ARROWS_CLOCKWISE, ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, CALENDAR_CHECK, DOTS_THREE_CIRCLE, FILE_ARROW_DOWN, FILE_TEXT, GEAR_FINE, PROHIBIT, WARNING, X_CIRCLE};
|
||||
use crate::gui::icons::{ARROWS_CLOCKWISE, ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, CALENDAR_CHECK, DOTS_THREE_CIRCLE, FILE_ARROW_DOWN, FILE_TEXT, FILE_X, GEAR_FINE, PROHIBIT, WARNING, X_CIRCLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::{LinePosition, ModalPosition};
|
||||
use crate::gui::views::wallets::wallet::types::{WalletContentContainer, GRIN};
|
||||
use crate::gui::views::wallets::wallet::WalletTransactionContent;
|
||||
use crate::gui::views::{Content, Modal, PullToRefresh, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::types::{WalletData, WalletTask, WalletTransaction, WalletTransactionAction};
|
||||
use crate::wallet::types::{WalletData, WalletTask, WalletTx, WalletTxAction};
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Wallet transactions tab content.
|
||||
@@ -36,8 +36,10 @@ pub struct WalletTransactionsContent {
|
||||
/// Transaction information [`Modal`] content.
|
||||
pub tx_info_content: Option<WalletTransactionContent>,
|
||||
|
||||
/// Transaction identifier to use at confirmation [`Modal`].
|
||||
/// Transaction identifier to use at confirmation [`Modal`] to cancel.
|
||||
confirm_cancel_tx_id: Option<u32>,
|
||||
/// Transaction identifier to use at confirmation [`Modal`] to delete.
|
||||
confirm_delete_tx_id: Option<u32>,
|
||||
|
||||
/// Flag to check if sync of wallet was initiated manually at time.
|
||||
manual_sync: Option<u128>
|
||||
@@ -45,19 +47,28 @@ pub struct WalletTransactionsContent {
|
||||
|
||||
impl WalletContentContainer for WalletTransactionsContent {
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![TX_INFO_MODAL, CANCEL_TX_CONFIRMATION_MODAL]
|
||||
vec![TX_INFO_MODAL, CANCEL_TX_CONFIRMATION_MODAL, DELETE_TX_CONFIRMATION_MODAL]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, w: &Wallet, m: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
match m.id {
|
||||
TX_INFO_MODAL => {
|
||||
if let Some(content) = self.tx_info_content.as_mut() {
|
||||
content.ui(ui, m, w, cb);
|
||||
let mut on_delete_id = None;
|
||||
content.ui(ui, m, w, cb, |id| {
|
||||
on_delete_id = Some(id);
|
||||
});
|
||||
if let Some(id) = on_delete_id {
|
||||
self.show_delete_confirmation_modal(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
CANCEL_TX_CONFIRMATION_MODAL => {
|
||||
self.cancel_confirmation_modal(ui, w);
|
||||
}
|
||||
DELETE_TX_CONFIRMATION_MODAL => {
|
||||
self.delete_confirmation_modal(ui, w);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -69,19 +80,21 @@ impl WalletContentContainer for WalletTransactionsContent {
|
||||
|
||||
/// Identifier for transaction information [`Modal`].
|
||||
const TX_INFO_MODAL: &'static str = "tx_info_modal";
|
||||
|
||||
/// Identifier for transaction cancellation confirmation [`Modal`].
|
||||
const CANCEL_TX_CONFIRMATION_MODAL: &'static str = "cancel_tx_conf_modal";
|
||||
/// Identifier for transaction deletion confirmation [`Modal`].
|
||||
const DELETE_TX_CONFIRMATION_MODAL: &'static str = "delete_tx_conf_modal";
|
||||
|
||||
impl WalletTransactionsContent {
|
||||
/// Height of transaction list item.
|
||||
pub const TX_ITEM_HEIGHT: f32 = 75.0;
|
||||
pub const TX_ITEM_HEIGHT: f32 = 73.0;
|
||||
|
||||
/// Create new content instance with opening tx info.
|
||||
pub fn new(tx: Option<WalletTransaction>) -> Self {
|
||||
pub fn new(tx: Option<WalletTx>) -> Self {
|
||||
let mut content = Self {
|
||||
tx_info_content: None,
|
||||
confirm_cancel_tx_id: None,
|
||||
confirm_delete_tx_id: None,
|
||||
manual_sync: None,
|
||||
};
|
||||
if let Some(tx) = &tx {
|
||||
@@ -100,7 +113,10 @@ impl WalletTransactionsContent {
|
||||
});
|
||||
return;
|
||||
}
|
||||
let txs = data.txs.as_ref().unwrap();
|
||||
let txs = data.txs.as_ref().unwrap()
|
||||
.iter()
|
||||
.filter(|tx| !tx.deleting())
|
||||
.collect::<Vec<&WalletTx>>();
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
// Show message when txs are empty.
|
||||
if txs.is_empty() {
|
||||
@@ -162,7 +178,7 @@ impl WalletTransactionsContent {
|
||||
ui: &mut egui::Ui,
|
||||
row_range: Range<usize>,
|
||||
wallet: &Wallet,
|
||||
txs: &Vec<WalletTransaction>) {
|
||||
txs: Vec<&WalletTx>) {
|
||||
let data = wallet.get_data().unwrap();
|
||||
for index in row_range {
|
||||
if index == txs.len() && ui.is_visible() {
|
||||
@@ -183,34 +199,40 @@ impl WalletTransactionsContent {
|
||||
}
|
||||
return;
|
||||
}
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.min += egui::emath::vec2(6.0, 0.0);
|
||||
rect.max -= egui::emath::vec2(6.0, 0.0);
|
||||
rect.set_height(Self::TX_ITEM_HEIGHT);
|
||||
|
||||
// Draw tx item background.
|
||||
let mut r = View::item_rounding(index, txs.len(), false);
|
||||
let p = ui.painter();
|
||||
p.rect(rect, r, Colors::fill(), View::item_stroke(), StrokeKind::Outside);
|
||||
|
||||
// Transaction item background setup.
|
||||
let rect = {
|
||||
let mut r = ui.available_rect_before_wrap();
|
||||
r.min += egui::emath::vec2(6.0, 0.0);
|
||||
r.max -= egui::emath::vec2(6.0, 0.0);
|
||||
r.set_height(Self::TX_ITEM_HEIGHT);
|
||||
r
|
||||
};
|
||||
let rounding = View::item_rounding(index, txs.len(), false);
|
||||
let bg = Colors::fill();
|
||||
let tx = txs.get(index).unwrap();
|
||||
Self::tx_item_ui(ui, tx, rect, &data, |ui| {
|
||||
// Draw button to show transaction info.
|
||||
if tx.data.tx_slate_id.is_some() || tx.data.payment_proof.is_some() {
|
||||
let mut show_tx_info = false;
|
||||
// Draw transaction list item.
|
||||
Self::tx_item_ui(ui, tx, rect, bg, rounding, &data, (true, || {
|
||||
show_tx_info = true;
|
||||
}), |ui| {
|
||||
let btn_rounding = {
|
||||
let mut r = rounding.clone();
|
||||
r.nw = 0.0 as u8;
|
||||
r.sw = 0.0 as u8;
|
||||
View::item_button(ui, r, FILE_TEXT, None, || {
|
||||
self.show_tx_info_modal(tx.data.id);
|
||||
r
|
||||
};
|
||||
// Draw button to delete transaction.
|
||||
if tx.data.confirmed || tx.cancelled() {
|
||||
View::item_button(ui, btn_rounding, FILE_X, Some(Colors::inactive_text()), || {
|
||||
self.show_delete_confirmation_modal(tx.data.id);
|
||||
});
|
||||
}
|
||||
|
||||
if wallet.synced_from_node() && !tx.cancelled() && !tx.cancelling() && !tx.posting() {
|
||||
let resend = tx.broadcasting_timed_out(wallet);
|
||||
|
||||
} else if !tx.cancelled() && !tx.cancelling() && !tx.posting() &&
|
||||
wallet.synced_from_node() {
|
||||
let repeat = tx.broadcasting_timed_out(wallet);
|
||||
// Draw button to cancel transaction.
|
||||
if tx.can_cancel() || resend {
|
||||
if tx.can_cancel() || repeat {
|
||||
let (icon, color) = (PROHIBIT, Some(Colors::red()));
|
||||
View::item_button(ui, CornerRadius::default(), icon, color, || {
|
||||
View::item_button(ui, btn_rounding, icon, color, || {
|
||||
self.confirm_cancel_tx_id = Some(tx.data.id);
|
||||
// Show transaction cancellation confirmation modal.
|
||||
Modal::new(CANCEL_TX_CONFIRMATION_MODAL)
|
||||
@@ -219,13 +241,16 @@ impl WalletTransactionsContent {
|
||||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
// Draw button to repeat transaction action.
|
||||
if tx.can_repeat_action() || resend {
|
||||
Self::tx_repeat_button_ui(ui, CornerRadius::default(), tx, wallet, resend);
|
||||
if tx.can_repeat_action() || repeat {
|
||||
Self::tx_repeat_button_ui(ui, CornerRadius::default(), tx, wallet, repeat);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Show transaction info on click.
|
||||
if show_tx_info {
|
||||
self.show_tx_info_modal(tx.data.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,92 +297,135 @@ impl WalletTransactionsContent {
|
||||
|
||||
/// Draw transaction item.
|
||||
pub fn tx_item_ui(ui: &mut egui::Ui,
|
||||
tx: &WalletTransaction,
|
||||
tx: &WalletTx,
|
||||
rect: Rect,
|
||||
bg: Color32,
|
||||
r: CornerRadius,
|
||||
data: &WalletData,
|
||||
on_click: (bool, impl FnOnce()),
|
||||
buttons_ui: impl FnOnce(&mut egui::Ui)) {
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Max), |ui| {
|
||||
ui.horizontal_centered(|ui| {
|
||||
// Draw buttons.
|
||||
buttons_ui(ui);
|
||||
});
|
||||
// Draw background.
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
let res = ui.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
ui.horizontal_centered(|ui| {
|
||||
// Draw buttons.
|
||||
buttons_ui(ui);
|
||||
});
|
||||
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(3.0);
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(3.0);
|
||||
|
||||
// Setup transaction amount.
|
||||
let mut amount_text = if tx.data.tx_type == TxLogEntryType::TxSent ||
|
||||
tx.data.tx_type == TxLogEntryType::TxSentCancelled {
|
||||
"-"
|
||||
} else if tx.data.tx_type == TxLogEntryType::TxReceived ||
|
||||
tx.data.tx_type == TxLogEntryType::TxReceivedCancelled {
|
||||
"+"
|
||||
} else {
|
||||
""
|
||||
}.to_string();
|
||||
amount_text = format!("{}{} {}",
|
||||
amount_text,
|
||||
amount_to_hr_string(tx.amount, true),
|
||||
GRIN);
|
||||
// Setup transaction amount.
|
||||
let mut amount_text = if tx.data.tx_type == TxLogEntryType::TxSent ||
|
||||
tx.data.tx_type == TxLogEntryType::TxSentCancelled {
|
||||
"-"
|
||||
} else if tx.data.tx_type == TxLogEntryType::TxReceived ||
|
||||
tx.data.tx_type == TxLogEntryType::TxReceivedCancelled {
|
||||
"+"
|
||||
} else {
|
||||
""
|
||||
}.to_string();
|
||||
amount_text = format!("{}{} {}",
|
||||
amount_text,
|
||||
amount_to_hr_string(tx.amount, true),
|
||||
GRIN);
|
||||
|
||||
// Setup amount color.
|
||||
let amount_color = match tx.data.tx_type {
|
||||
TxLogEntryType::ConfirmedCoinbase => Colors::white_or_black(true),
|
||||
TxLogEntryType::TxReceived => Colors::white_or_black(true),
|
||||
TxLogEntryType::TxSent => Colors::white_or_black(true),
|
||||
TxLogEntryType::TxReceivedCancelled => Colors::text(false),
|
||||
TxLogEntryType::TxSentCancelled => Colors::text(false),
|
||||
TxLogEntryType::TxReverted => Colors::text(false)
|
||||
};
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
ui.add_space(1.0);
|
||||
View::ellipsize_text(ui, amount_text, 18.0, amount_color);
|
||||
});
|
||||
ui.add_space(-2.0);
|
||||
// Setup amount color.
|
||||
let amount_color = match tx.data.tx_type {
|
||||
TxLogEntryType::ConfirmedCoinbase => Colors::white_or_black(true),
|
||||
TxLogEntryType::TxReceived => Colors::white_or_black(true),
|
||||
TxLogEntryType::TxSent => Colors::white_or_black(true),
|
||||
TxLogEntryType::TxReceivedCancelled => Colors::text(false),
|
||||
TxLogEntryType::TxSentCancelled => Colors::text(false),
|
||||
TxLogEntryType::TxReverted => Colors::text(false)
|
||||
};
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
ui.add_space(1.0);
|
||||
View::ellipsize_text(ui, amount_text, 18.0, amount_color);
|
||||
});
|
||||
ui.add_space(-2.0);
|
||||
|
||||
// Setup transaction status text.
|
||||
let height = data.info.last_confirmed_height;
|
||||
let status_text = if !tx.data.confirmed {
|
||||
let is_canceled = tx.data.tx_type == TxLogEntryType::TxSentCancelled
|
||||
|| tx.data.tx_type == TxLogEntryType::TxReceivedCancelled;
|
||||
if is_canceled {
|
||||
format!("{} {}", X_CIRCLE, t!("wallets.tx_canceled"))
|
||||
} else if let Some(a) = &tx.action {
|
||||
let error = if tx.action_error.is_none() {
|
||||
"".to_string()
|
||||
// Setup transaction status text.
|
||||
let height = data.info.last_confirmed_height;
|
||||
let status_text = if !tx.data.confirmed {
|
||||
let is_canceled = tx.data.tx_type == TxLogEntryType::TxSentCancelled
|
||||
|| tx.data.tx_type == TxLogEntryType::TxReceivedCancelled;
|
||||
if is_canceled {
|
||||
format!("{} {}", X_CIRCLE, t!("wallets.tx_canceled"))
|
||||
} else if let Some(action) = &tx.action {
|
||||
let error = if tx.action_error.is_none() {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!("{}: ", t!("error"))
|
||||
};
|
||||
let status = match action {
|
||||
WalletTxAction::Finalizing => t!("wallets.tx_finalizing"),
|
||||
WalletTxAction::Posting => t!("wallets.tx_posting"),
|
||||
WalletTxAction::SendingTor => t!("transport.tor_sending"),
|
||||
_ => t!("wallets.tx_cancelling")
|
||||
};
|
||||
let icon = if error.is_empty() {
|
||||
DOTS_THREE_CIRCLE
|
||||
} else {
|
||||
WARNING
|
||||
};
|
||||
format!("{} {}{}", icon, error, status)
|
||||
} else {
|
||||
format!("{}: ", t!("error"))
|
||||
};
|
||||
let status = match a {
|
||||
WalletTransactionAction::Cancelling => t!("wallets.tx_cancelling"),
|
||||
WalletTransactionAction::Finalizing => t!("wallets.tx_finalizing"),
|
||||
WalletTransactionAction::Posting => t!("wallets.tx_posting"),
|
||||
WalletTransactionAction::SendingTor => t!("transport.tor_sending")
|
||||
};
|
||||
let icon = if error.is_empty() {
|
||||
DOTS_THREE_CIRCLE
|
||||
} else {
|
||||
WARNING
|
||||
};
|
||||
format!("{} {}{}", icon, error, status)
|
||||
match tx.data.tx_type {
|
||||
TxLogEntryType::TxReceived => {
|
||||
let text = match tx.finalized() {
|
||||
true => t!("wallets.await_fin_amount"),
|
||||
false => t!("wallets.tx_receiving")
|
||||
};
|
||||
format!("{} {}", DOTS_THREE_CIRCLE, text)
|
||||
},
|
||||
TxLogEntryType::TxSent => {
|
||||
let text = match tx.finalized() {
|
||||
true => t!("wallets.await_fin_amount"),
|
||||
false => t!("wallets.tx_sending")
|
||||
};
|
||||
format!("{} {}", DOTS_THREE_CIRCLE, text)
|
||||
},
|
||||
TxLogEntryType::ConfirmedCoinbase => {
|
||||
let tx_h = tx.height.unwrap_or(1) - 1;
|
||||
if tx_h != 0 {
|
||||
let left_conf = height - tx_h;
|
||||
if height >= tx_h && left_conf < COINBASE_MATURITY {
|
||||
let conf_info = format!("{}/{}",
|
||||
left_conf,
|
||||
COINBASE_MATURITY);
|
||||
format!("{} {} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"),
|
||||
conf_info
|
||||
)
|
||||
} else {
|
||||
format!("{} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"))
|
||||
}
|
||||
} else {
|
||||
format!("{} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"))
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
format!("{} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match tx.data.tx_type {
|
||||
TxLogEntryType::TxReceived => {
|
||||
let text = match tx.finalized() {
|
||||
true => t!("wallets.await_fin_amount"),
|
||||
false => t!("wallets.tx_receiving")
|
||||
};
|
||||
format!("{} {}", DOTS_THREE_CIRCLE, text)
|
||||
},
|
||||
TxLogEntryType::TxSent => {
|
||||
let text = match tx.finalized() {
|
||||
true => t!("wallets.await_fin_amount"),
|
||||
false => t!("wallets.tx_sending")
|
||||
};
|
||||
format!("{} {}", DOTS_THREE_CIRCLE, text)
|
||||
},
|
||||
TxLogEntryType::ConfirmedCoinbase => {
|
||||
let tx_h = tx.height.unwrap_or(1) - 1;
|
||||
if tx_h != 0 {
|
||||
@@ -374,111 +442,91 @@ impl WalletTransactionsContent {
|
||||
} else {
|
||||
format!("{} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"))
|
||||
t!("wallets.tx_confirmed"))
|
||||
}
|
||||
} else {
|
||||
format!("{} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"))
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
format!("{} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match tx.data.tx_type {
|
||||
TxLogEntryType::ConfirmedCoinbase => {
|
||||
let tx_h = tx.height.unwrap_or(1) - 1;
|
||||
if tx_h != 0 {
|
||||
let left_conf = height - tx_h;
|
||||
if height >= tx_h && left_conf < COINBASE_MATURITY {
|
||||
let conf_info = format!("{}/{}",
|
||||
left_conf,
|
||||
COINBASE_MATURITY);
|
||||
format!("{} {} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"),
|
||||
conf_info
|
||||
)
|
||||
} else {
|
||||
format!("{} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirmed"))
|
||||
}
|
||||
} else {
|
||||
format!("{} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirmed"))
|
||||
}
|
||||
|
||||
},
|
||||
TxLogEntryType::TxSent | TxLogEntryType::TxReceived => {
|
||||
let min_conf = data.info.minimum_confirmations;
|
||||
if tx.height.is_none() || (tx.height.unwrap() != 0 &&
|
||||
height - tx.height.unwrap() >= min_conf - 1) {
|
||||
let (i, t) = if tx.data.tx_type == TxLogEntryType::TxSent {
|
||||
(ARROW_CIRCLE_UP, t!("wallets.tx_sent"))
|
||||
},
|
||||
TxLogEntryType::TxSent | TxLogEntryType::TxReceived => {
|
||||
let min_conf = data.info.minimum_confirmations;
|
||||
if tx.height.is_none() || (tx.height.unwrap() != 0 &&
|
||||
height - tx.height.unwrap() >= min_conf - 1) {
|
||||
let (i, t) = if tx.data.tx_type == TxLogEntryType::TxSent {
|
||||
(ARROW_CIRCLE_UP, t!("wallets.tx_sent"))
|
||||
} else {
|
||||
(ARROW_CIRCLE_DOWN, t!("wallets.tx_received"))
|
||||
};
|
||||
format!("{} {}", i, t)
|
||||
} else {
|
||||
(ARROW_CIRCLE_DOWN, t!("wallets.tx_received"))
|
||||
};
|
||||
format!("{} {}", i, t)
|
||||
} else {
|
||||
let tx_height = tx.height.unwrap() - 1;
|
||||
let left_conf = height - tx_height;
|
||||
let conf_info = if tx_height != 0 && height >= tx_height &&
|
||||
left_conf < min_conf {
|
||||
format!("{}/{}", left_conf, min_conf)
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
format!("{} {} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"),
|
||||
conf_info
|
||||
)
|
||||
}
|
||||
let tx_height = tx.height.unwrap() - 1;
|
||||
let left_conf = height - tx_height;
|
||||
let conf_info = if tx_height != 0 && height >= tx_height &&
|
||||
left_conf < min_conf {
|
||||
format!("{}/{}", left_conf, min_conf)
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
format!("{} {} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"),
|
||||
conf_info
|
||||
)
|
||||
}
|
||||
},
|
||||
_ => format!("{} {}", X_CIRCLE, t!("wallets.canceled"))
|
||||
}
|
||||
};
|
||||
|
||||
// Setup status text color.
|
||||
let status_color = match tx.data.tx_type {
|
||||
TxLogEntryType::ConfirmedCoinbase => Colors::text(false),
|
||||
TxLogEntryType::TxReceived => if tx.data.confirmed {
|
||||
Colors::green()
|
||||
} else {
|
||||
Colors::text(false)
|
||||
},
|
||||
_ => format!("{} {}", X_CIRCLE, t!("wallets.canceled"))
|
||||
}
|
||||
};
|
||||
TxLogEntryType::TxSent => if tx.data.confirmed {
|
||||
Colors::red()
|
||||
} else {
|
||||
Colors::text(false)
|
||||
},
|
||||
TxLogEntryType::TxReceivedCancelled => Colors::inactive_text(),
|
||||
TxLogEntryType::TxSentCancelled => Colors::inactive_text(),
|
||||
TxLogEntryType::TxReverted => Colors::inactive_text(),
|
||||
};
|
||||
View::ellipsize_text(ui, status_text, 15.0, status_color);
|
||||
|
||||
// Setup status text color.
|
||||
let status_color = match tx.data.tx_type {
|
||||
TxLogEntryType::ConfirmedCoinbase => Colors::text(false),
|
||||
TxLogEntryType::TxReceived => if tx.data.confirmed {
|
||||
Colors::green()
|
||||
} else {
|
||||
Colors::text(false)
|
||||
},
|
||||
TxLogEntryType::TxSent => if tx.data.confirmed {
|
||||
Colors::red()
|
||||
} else {
|
||||
Colors::text(false)
|
||||
},
|
||||
TxLogEntryType::TxReceivedCancelled => Colors::inactive_text(),
|
||||
TxLogEntryType::TxSentCancelled => Colors::inactive_text(),
|
||||
TxLogEntryType::TxReverted => Colors::inactive_text(),
|
||||
};
|
||||
View::ellipsize_text(ui, status_text, 15.0, status_color);
|
||||
|
||||
// Setup transaction time.
|
||||
let tx_time = View::format_time(tx.data.creation_ts.timestamp());
|
||||
let tx_time_text = format!("{} {}", CALENDAR_CHECK, tx_time);
|
||||
ui.label(RichText::new(tx_time_text).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(3.0);
|
||||
// Setup transaction time.
|
||||
let tx_time = View::format_time(tx.data.creation_ts.timestamp());
|
||||
let tx_time_text = format!("{} {}", CALENDAR_CHECK, tx_time);
|
||||
ui.label(RichText::new(tx_time_text).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(4.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
let (clickable, on_click) = on_click;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if clickable && res.hovered() {
|
||||
res.on_hover_cursor(CursorIcon::PointingHand);
|
||||
bg_shape.fill = Colors::TRANSPARENT;
|
||||
}
|
||||
ui.painter().set(bg_idx, bg_shape);
|
||||
// Handle clicks on layout.
|
||||
if clicked && clickable {
|
||||
on_click();
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw button to repeat transaction action on error or repost.
|
||||
pub fn tx_repeat_button_ui(ui: &mut egui::Ui,
|
||||
rounding: CornerRadius,
|
||||
tx: &WalletTransaction,
|
||||
tx: &WalletTx,
|
||||
wallet: &Wallet,
|
||||
repost: bool) {
|
||||
let (icon, color) = (ARROWS_CLOCKWISE, Some(Colors::green()));
|
||||
@@ -487,21 +535,21 @@ impl WalletTransactionsContent {
|
||||
wallet.task(WalletTask::Post(tx.data.id));
|
||||
} else if let Some(action) = tx.action.as_ref() {
|
||||
match action {
|
||||
WalletTransactionAction::Finalizing => {
|
||||
WalletTxAction::Finalizing => {
|
||||
wallet.task(WalletTask::Finalize(tx.data.id));
|
||||
}
|
||||
WalletTransactionAction::Posting => {
|
||||
WalletTxAction::Posting => {
|
||||
wallet.task(WalletTask::Post(tx.data.id));
|
||||
}
|
||||
_ => {
|
||||
if let Some(a) = &tx.receiver {
|
||||
wallet.task(WalletTask::SendTor(tx.data.id, a.clone()));
|
||||
wallet.task(WalletTask::SendTor(tx.data.clone(), a.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Some(a) = &tx.receiver {
|
||||
wallet.task(WalletTask::SendTor(tx.data.id, a.clone()));
|
||||
wallet.task(WalletTask::SendTor(tx.data.clone(), a.clone()));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -522,8 +570,8 @@ impl WalletTransactionsContent {
|
||||
let data = wallet.get_data().unwrap();
|
||||
let data_txs = data.txs.unwrap();
|
||||
let txs = data_txs.into_iter()
|
||||
.filter(|tx| tx.data.id == self.confirm_cancel_tx_id.unwrap())
|
||||
.collect::<Vec<WalletTransaction>>();
|
||||
.filter(|tx| tx.data.id == self.confirm_cancel_tx_id.unwrap_or_default())
|
||||
.collect::<Vec<WalletTx>>();
|
||||
if txs.is_empty() {
|
||||
Modal::close();
|
||||
return;
|
||||
@@ -562,7 +610,7 @@ impl WalletTransactionsContent {
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, "OK".to_string(), Colors::white_or_black(false), || {
|
||||
wallet.task(WalletTask::Cancel(tx.data.clone()));
|
||||
wallet.task(WalletTask::Cancel(tx.data.id));
|
||||
self.confirm_cancel_tx_id = None;
|
||||
Modal::close();
|
||||
});
|
||||
@@ -571,6 +619,62 @@ impl WalletTransactionsContent {
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
|
||||
/// Show transaction deletion confirmation [`Modal`].
|
||||
fn show_delete_confirmation_modal(&mut self, id: u32) {
|
||||
self.confirm_delete_tx_id = Some(id);
|
||||
// Show transaction deletion confirmation modal.
|
||||
Modal::new(DELETE_TX_CONFIRMATION_MODAL)
|
||||
.position(ModalPosition::Center)
|
||||
.title(t!("confirmation"))
|
||||
.show();
|
||||
}
|
||||
|
||||
/// Confirmation [`Modal`] to delete transaction.
|
||||
fn delete_confirmation_modal(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
let data = wallet.get_data().unwrap();
|
||||
let data_txs = data.txs.unwrap();
|
||||
let txs = data_txs.into_iter()
|
||||
.filter(|tx| tx.data.id == self.confirm_delete_tx_id.unwrap_or_default())
|
||||
.collect::<Vec<WalletTx>>();
|
||||
if txs.is_empty() {
|
||||
Modal::close();
|
||||
return;
|
||||
}
|
||||
let tx = txs.get(0).unwrap();
|
||||
|
||||
// Show confirmation text.
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.tx_delete_confirmation"))
|
||||
.size(17.0)
|
||||
.color(Colors::text(false)));
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
|
||||
// Show modal buttons.
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
self.confirm_delete_tx_id = None;
|
||||
Modal::close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, "OK".to_string(), Colors::white_or_black(false), || {
|
||||
wallet.task(WalletTask::Delete(tx.data.id));
|
||||
self.confirm_delete_tx_id = None;
|
||||
Modal::close();
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw awaiting balance item content.
|
||||
|
||||
@@ -19,15 +19,15 @@ use grin_util::ToHex;
|
||||
use grin_wallet_libwallet::TxLogEntryType;
|
||||
use std::fs;
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::icons::{CIRCLE_HALF, COPY, CUBE, FILE_ARCHIVE, FILE_TEXT, HASH_STRAIGHT, PROHIBIT, QR_CODE, SEAL_CHECK};
|
||||
use crate::gui::icons::{CIRCLE_HALF, COPY, CUBE, FILE_ARCHIVE, FILE_TEXT, FILE_X, HASH_STRAIGHT, PROHIBIT, QR_CODE, SEAL_CHECK};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::wallets::wallet::proof::PaymentProofContent;
|
||||
use crate::gui::views::wallets::wallet::txs::WalletTransactionsContent;
|
||||
use crate::gui::views::{Modal, QrCodeContent, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::types::{WalletTask, WalletTransaction};
|
||||
use crate::wallet::types::{WalletTask, WalletTx};
|
||||
use crate::wallet::Wallet;
|
||||
use crate::AppConfig;
|
||||
|
||||
/// Transaction information [`Modal`] content.
|
||||
pub struct WalletTransactionContent {
|
||||
@@ -44,7 +44,7 @@ pub struct WalletTransactionContent {
|
||||
}
|
||||
|
||||
impl WalletTransactionContent {
|
||||
/// Create new content instance with [`Wallet`] from provided [`WalletTransaction`].
|
||||
/// Create new content instance with [`Wallet`] from provided [`WalletTx`].
|
||||
pub fn new(tx_id: u32) -> Self {
|
||||
Self {
|
||||
tx_id,
|
||||
@@ -59,7 +59,8 @@ impl WalletTransactionContent {
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
wallet: &Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
on_delete: impl FnOnce(u32)) {
|
||||
// Check values and setup transaction data.
|
||||
let wallet_data = wallet.get_data();
|
||||
if wallet_data.is_none() {
|
||||
@@ -70,7 +71,7 @@ impl WalletTransactionContent {
|
||||
let data_txs = data.txs.clone().unwrap();
|
||||
let txs = data_txs.into_iter()
|
||||
.filter(|tx| tx.data.id == self.tx_id)
|
||||
.collect::<Vec<WalletTransaction>>();
|
||||
.collect::<Vec<WalletTx>>();
|
||||
if txs.is_empty() {
|
||||
Modal::close();
|
||||
return;
|
||||
@@ -110,7 +111,7 @@ impl WalletTransactionContent {
|
||||
} else {
|
||||
modal.set_background_color(Colors::fill());
|
||||
// Show transaction information.
|
||||
self.info_ui(ui, tx, wallet, cb);
|
||||
self.info_ui(ui, tx, wallet, cb, on_delete);
|
||||
|
||||
// Show transaction sharing content or payment proof.
|
||||
if self.proof_content.is_none() && tx.can_cancel() && !tx.finalized() {
|
||||
@@ -155,7 +156,7 @@ impl WalletTransactionContent {
|
||||
fn share_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
tx: &WalletTransaction,
|
||||
tx: &WalletTx,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
if self.message.is_none() {
|
||||
let slatepack_path = wallet.get_config().get_tx_slate_path(tx);
|
||||
@@ -258,22 +259,27 @@ impl WalletTransactionContent {
|
||||
/// Draw transaction information content.
|
||||
fn info_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
tx: &WalletTransaction,
|
||||
tx: &WalletTx,
|
||||
wallet: &Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
on_delete: impl FnOnce(u32)) {
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Transaction item background setup.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(WalletTransactionsContent::TX_ITEM_HEIGHT);
|
||||
|
||||
// Draw tx item background.
|
||||
let p = ui.painter();
|
||||
let r = View::item_rounding(0, 2, false);
|
||||
p.rect(rect, r, Colors::TRANSPARENT, View::item_stroke(), StrokeKind::Outside);
|
||||
|
||||
let rounding = View::item_rounding(0, 2, false);
|
||||
let bg = Colors::TRANSPARENT;
|
||||
// Show transaction amount status and time.
|
||||
let data = wallet.get_data().unwrap();
|
||||
WalletTransactionsContent::tx_item_ui(ui, tx, rect, &data, |ui| {
|
||||
let on_click = (false, || {});
|
||||
WalletTransactionsContent::tx_item_ui(ui, tx, rect, bg, rounding, &data, on_click, |ui| {
|
||||
// Show button to delete transaction from database.
|
||||
if tx.data.confirmed || tx.cancelled() {
|
||||
let r = View::item_rounding(0, 2, true);
|
||||
View::item_button(ui, r, FILE_X, Some(Colors::inactive_text()), || {
|
||||
on_delete(tx.data.id);
|
||||
});
|
||||
}
|
||||
// Show block height or buttons.
|
||||
if let Some(h) = tx.height {
|
||||
if h != 0 {
|
||||
@@ -288,27 +294,24 @@ impl WalletTransactionContent {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if wallet.synced_from_node() && !tx.cancelled() && !tx.cancelling() && !tx.posting() {
|
||||
let rebroadcast = tx.broadcasting_timed_out(&wallet);
|
||||
|
||||
let repeat = tx.broadcasting_timed_out(&wallet);
|
||||
// Draw button to cancel transaction.
|
||||
if tx.can_cancel() || rebroadcast {
|
||||
if tx.can_cancel() || repeat {
|
||||
let r = View::item_rounding(0, 2, true);
|
||||
View::item_button(ui, r, PROHIBIT, Some(Colors::red()), || {
|
||||
wallet.task(WalletTask::Cancel(tx.data.clone()));
|
||||
wallet.task(WalletTask::Cancel(tx.data.id));
|
||||
Modal::close();
|
||||
});
|
||||
}
|
||||
|
||||
// Draw button to repeat transaction action.
|
||||
if tx.can_repeat_action() || rebroadcast {
|
||||
if tx.can_repeat_action() || repeat {
|
||||
let r = if tx.can_finalize() || tx.can_cancel() {
|
||||
CornerRadius::default()
|
||||
} else {
|
||||
View::item_rounding(0, 2, true)
|
||||
};
|
||||
WalletTransactionsContent::tx_repeat_button_ui(ui, r, tx, wallet, rebroadcast);
|
||||
WalletTransactionsContent::tx_repeat_button_ui(ui, r, tx, wallet, repeat);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@ pub trait WalletContentContainer {
|
||||
|
||||
/// Get wallet status text.
|
||||
pub fn wallet_status_text(wallet: &Wallet) -> String {
|
||||
if wallet.sync_error() {
|
||||
if wallet.sync_error() && wallet.is_open() {
|
||||
format!("{} {}", WARNING_CIRCLE, t!("error"))
|
||||
} else if wallet.is_closing() {
|
||||
format!("{} {}", SPINNER, t!("wallets.closing"))
|
||||
|
||||
+2
-2
@@ -404,8 +404,8 @@ impl Tor {
|
||||
let mut w_client = TOR_STATE.client_config.write();
|
||||
*w_client = None;
|
||||
// Clear state.
|
||||
fs::remove_dir_all(TorConfig::state_path()).unwrap_or_default();}
|
||||
|
||||
fs::remove_dir_all(TorConfig::state_path()).unwrap_or_default();
|
||||
}
|
||||
port_key
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::{AppConfig, Settings};
|
||||
use crate::wallet::ConnectionsConfig;
|
||||
use crate::wallet::types::{ConnectionMethod, WalletTransaction};
|
||||
use crate::wallet::types::{ConnectionMethod, WalletTx};
|
||||
|
||||
/// Wallet configuration.
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
@@ -228,7 +228,7 @@ impl WalletConfig {
|
||||
}
|
||||
|
||||
/// Get Slatepack file path for transaction.
|
||||
pub fn get_tx_slate_path(&self, tx: &WalletTransaction) -> PathBuf {
|
||||
pub fn get_tx_slate_path(&self, tx: &WalletTx) -> PathBuf {
|
||||
let mut path = PathBuf::from(self.get_base_data_path());
|
||||
path.push(SLATEPACKS_DIR_NAME);
|
||||
if !path.exists() {
|
||||
|
||||
+39
-47
@@ -150,7 +150,7 @@ pub struct WalletData {
|
||||
pub info: WalletInfo,
|
||||
|
||||
/// Transactions data.
|
||||
pub txs: Option<Vec<WalletTransaction>>,
|
||||
pub txs: Option<Vec<WalletTx>>,
|
||||
/// Number of txs to show on select from database.
|
||||
pub txs_limit: u32,
|
||||
}
|
||||
@@ -160,53 +160,34 @@ impl WalletData {
|
||||
pub const TXS_LIMIT: u32 = 30;
|
||||
|
||||
/// Update transaction action status.
|
||||
pub fn on_tx_action(&mut self, id: String, action: Option<WalletTransactionAction>) {
|
||||
pub fn on_tx_action(&mut self, id: u32, action: Option<WalletTxAction>) {
|
||||
if self.txs.is_none() {
|
||||
return;
|
||||
}
|
||||
for tx in self.txs.as_mut().unwrap() {
|
||||
if let Some(slate_id) = tx.data.tx_slate_id {
|
||||
if slate_id.to_string() == id {
|
||||
tx.action = action;
|
||||
tx.action_error = None;
|
||||
break;
|
||||
}
|
||||
if id == tx.data.id {
|
||||
tx.action = action;
|
||||
tx.action_error = None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update transaction action error status.
|
||||
pub fn on_tx_error(&mut self, id: String, err: Option<Error>) {
|
||||
pub fn on_tx_error(&mut self, id: u32, err: Option<Error>) {
|
||||
if self.txs.is_none() {
|
||||
return;
|
||||
}
|
||||
for tx in self.txs.as_mut().unwrap() {
|
||||
if let Some(slate_id) = tx.data.tx_slate_id {
|
||||
if slate_id.to_string() == id {
|
||||
tx.action_error = err;
|
||||
break;
|
||||
}
|
||||
if id == tx.data.id {
|
||||
tx.action_error = err;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get transaction by slate identifier.
|
||||
pub fn tx_by_slate_id(&self, id: String) -> Option<WalletTransaction> {
|
||||
if self.txs.is_none() {
|
||||
return None;
|
||||
}
|
||||
for tx in self.txs.as_ref().unwrap() {
|
||||
if let Some(slate_id) = tx.data.tx_slate_id {
|
||||
if slate_id.to_string() == id {
|
||||
return Some(tx.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get transaction by identifier.
|
||||
pub fn tx_by_id(&self, id: u32) -> Option<WalletTransaction> {
|
||||
pub fn tx_by_id(&self, id: u32) -> Option<WalletTx> {
|
||||
if self.txs.is_none() {
|
||||
return None;
|
||||
}
|
||||
@@ -221,13 +202,13 @@ impl WalletData {
|
||||
|
||||
/// Wallet transaction action.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum WalletTransactionAction {
|
||||
Cancelling, Finalizing, Posting, SendingTor
|
||||
pub enum WalletTxAction {
|
||||
Cancelling, Finalizing, Posting, SendingTor, Deleting
|
||||
}
|
||||
|
||||
/// Wallet transaction data.
|
||||
#[derive(Clone)]
|
||||
pub struct WalletTransaction {
|
||||
pub struct WalletTx {
|
||||
/// Information from database.
|
||||
pub data: TxLogEntry,
|
||||
/// State of transaction Slate.
|
||||
@@ -247,19 +228,19 @@ pub struct WalletTransaction {
|
||||
pub broadcasting_height: Option<u64>,
|
||||
|
||||
/// Action on transaction.
|
||||
pub action: Option<WalletTransactionAction>,
|
||||
pub action: Option<WalletTxAction>,
|
||||
/// Action result error.
|
||||
pub action_error: Option<Error>
|
||||
}
|
||||
|
||||
impl WalletTransaction {
|
||||
impl WalletTx {
|
||||
/// Create new wallet transaction.
|
||||
pub fn new(tx: TxLogEntry,
|
||||
proof: Option<PaymentProof>,
|
||||
wallet: &Wallet,
|
||||
height: Option<u64>,
|
||||
broadcasting_height: Option<u64>,
|
||||
action: Option<WalletTransactionAction>,
|
||||
action: Option<WalletTxAction>,
|
||||
action_error: Option<Error>) -> Self {
|
||||
let amount = if tx.amount_debited > tx.amount_credited {
|
||||
tx.amount_debited - tx.amount_credited
|
||||
@@ -352,7 +333,7 @@ impl WalletTransaction {
|
||||
/// Check if transaction is sending over Tor.
|
||||
pub fn sending_tor(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTransactionAction::SendingTor;
|
||||
return a == &WalletTxAction::SendingTor;
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -360,7 +341,7 @@ impl WalletTransaction {
|
||||
/// Check if transaction is cancelling.
|
||||
pub fn cancelling(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTransactionAction::Cancelling;
|
||||
return a == &WalletTxAction::Cancelling;
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -368,7 +349,7 @@ impl WalletTransaction {
|
||||
/// Check if transaction is posting.
|
||||
pub fn posting(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTransactionAction::Posting;
|
||||
return a == &WalletTxAction::Posting;
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -390,7 +371,7 @@ impl WalletTransaction {
|
||||
/// Check if transaction is finalizing.
|
||||
pub fn finalizing(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTransactionAction::Finalizing;
|
||||
return a == &WalletTxAction::Finalizing;
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -398,7 +379,7 @@ impl WalletTransaction {
|
||||
/// Check if possible to repeat transaction action.
|
||||
pub fn can_repeat_action(&self) -> bool {
|
||||
if let Some(a) = &self.action {
|
||||
self.action_error.is_some() && a != &WalletTransactionAction::Cancelling
|
||||
self.action_error.is_some() && a != &WalletTxAction::Cancelling
|
||||
} else {
|
||||
// Can resend over Tor.
|
||||
!self.data.confirmed && !self.sending_tor() && !self.broadcasting() &&
|
||||
@@ -426,6 +407,14 @@ impl WalletTransaction {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if transaction is deleting.
|
||||
pub fn deleting(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTxAction::Deleting;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Task for the wallet.
|
||||
@@ -446,19 +435,22 @@ pub enum WalletTask {
|
||||
/// * receiver
|
||||
Send(u64, Option<SlatepackAddress>),
|
||||
/// Send request over Tor.
|
||||
/// * local tx id
|
||||
/// * tx
|
||||
/// * receiver
|
||||
SendTor(u32, SlatepackAddress),
|
||||
SendTor(TxLogEntry, SlatepackAddress),
|
||||
/// Invoice creation.
|
||||
/// * amount
|
||||
Receive(u64),
|
||||
/// Transaction finalization.
|
||||
/// * local tx id
|
||||
/// * tx id
|
||||
Finalize(u32),
|
||||
/// Post transaction to blockchain.
|
||||
/// * local tx id
|
||||
/// * tx id
|
||||
Post(u32),
|
||||
/// Cancel transaction.
|
||||
/// * tx
|
||||
Cancel(TxLogEntry),
|
||||
/// * tx id
|
||||
Cancel(u32),
|
||||
/// Delete transaction.
|
||||
/// * tx id
|
||||
Delete(u32)
|
||||
}
|
||||
+215
-177
@@ -16,7 +16,7 @@ use crate::node::{Node, NodeConfig};
|
||||
use crate::tor::Tor;
|
||||
use crate::wallet::seed::WalletSeed;
|
||||
use crate::wallet::store::TxHeightStore;
|
||||
use crate::wallet::types::{ConnectionMethod, PhraseMode, WalletAccount, WalletData, WalletInstance, WalletTask, WalletTransaction, WalletTransactionAction};
|
||||
use crate::wallet::types::{ConnectionMethod, PhraseMode, WalletAccount, WalletData, WalletInstance, WalletTask, WalletTx, WalletTxAction};
|
||||
use crate::wallet::{ConnectionsConfig, Mnemonic, WalletConfig};
|
||||
use crate::AppConfig;
|
||||
|
||||
@@ -48,7 +48,7 @@ use std::sync::mpsc::Sender;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::thread::Thread;
|
||||
use std::time::Duration;
|
||||
use std::{fs, thread};
|
||||
use std::{fs, path, thread};
|
||||
use chrono::Utc;
|
||||
use log::error;
|
||||
use num_bigint::BigInt;
|
||||
@@ -131,7 +131,7 @@ pub struct Wallet {
|
||||
/// Tasks sender.
|
||||
tasks_sender: Arc<RwLock<Option<Sender<WalletTask>>>>,
|
||||
/// Task result with optional transaction identifier.
|
||||
task_result: Arc<RwLock<Option<(Option<String>, WalletTask)>>>,
|
||||
task_result: Arc<RwLock<Option<(Option<u32>, WalletTask)>>>,
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
@@ -379,13 +379,8 @@ impl Wallet {
|
||||
}
|
||||
}
|
||||
|
||||
// Set Slatepack address and secret key.
|
||||
if let Ok((key, addr)) = self.get_secret_key_addr() {
|
||||
let mut w_key = self.secret_key.write();
|
||||
*w_key = Some(key);
|
||||
let mut w_address = self.slatepack_address.write();
|
||||
*w_address = Some(addr.to_string());
|
||||
}
|
||||
// Update Slatepack address and secret key.
|
||||
self.update_secret_key_addr()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -403,7 +398,7 @@ impl Wallet {
|
||||
}
|
||||
|
||||
/// Retrieve wallet [`SecretKey`] and Slatepack address for transport.
|
||||
fn get_secret_key_addr(&self) -> Result<(SecretKey, SlatepackAddress), Error> {
|
||||
fn update_secret_key_addr(&self) -> Result<(), Error> {
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
let instance = r_inst.clone().unwrap();
|
||||
let mut w_lock = instance.lock();
|
||||
@@ -414,7 +409,11 @@ impl Wallet {
|
||||
let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0)
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
let addr = SlatepackAddress::try_from(&sec_key)?;
|
||||
Ok((sec_key, addr))
|
||||
let mut w_key = self.secret_key.write();
|
||||
*w_key = Some(sec_key);
|
||||
let mut w_address = self.slatepack_address.write();
|
||||
*w_address = Some(addr.to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get unique opened wallet identifier, including current account.
|
||||
@@ -628,12 +627,11 @@ impl Wallet {
|
||||
}
|
||||
|
||||
/// Select transaction by slate id.
|
||||
fn retrieve_tx_by_id(&self, id: Uuid) -> Option<TxLogEntry> {
|
||||
fn retrieve_tx_by_id(&self, id: Option<u32>, slate_id: Option<Uuid>) -> Option<TxLogEntry> {
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
let inst = r_inst.clone().unwrap();
|
||||
let mask = self.keychain_mask();
|
||||
let tx_id = Some(id);
|
||||
if let Ok((_, txs)) = retrieve_txs(inst, mask.as_ref(), &None, false, None, tx_id, None) {
|
||||
if let Ok((_, txs)) = retrieve_txs(inst, mask.as_ref(), &None, false, id, slate_id, None) {
|
||||
if !txs.is_empty() {
|
||||
return Some(txs.get(0).unwrap().clone())
|
||||
}
|
||||
@@ -740,13 +738,8 @@ impl Wallet {
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Setup secret key and Slatepack address.
|
||||
if let Ok((key, addr)) = self.get_secret_key_addr() {
|
||||
let mut w_key = self.secret_key.write();
|
||||
*w_key = Some(key);
|
||||
let mut w_address = self.slatepack_address.write();
|
||||
*w_address = Some(addr.to_string());
|
||||
}
|
||||
// Update Slatepack address and secret key.
|
||||
self.update_secret_key_addr()?;
|
||||
|
||||
// Save account label into config.
|
||||
let mut w_config = self.config.write();
|
||||
@@ -893,7 +886,7 @@ impl Wallet {
|
||||
/// Check if Slatepack file exists.
|
||||
pub fn slatepack_exists(&self, slate: &Slate) -> bool {
|
||||
let slatepack_path = self.get_config().get_slate_path(slate);
|
||||
fs::exists(slatepack_path).unwrap()
|
||||
fs::exists(slatepack_path).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Calculate transaction fee for provided amount.
|
||||
@@ -969,12 +962,12 @@ impl Wallet {
|
||||
}
|
||||
|
||||
/// Send slate to Tor address.
|
||||
async fn send_tor(&self, slate: &Slate, addr: &SlatepackAddress) -> Result<Slate, Error> {
|
||||
self.on_tx_action(slate.id.to_string(), Some(WalletTransactionAction::SendingTor));
|
||||
async fn send_tor(&self, id: u32, s: &Slate, addr: &SlatepackAddress) -> Result<Slate, Error> {
|
||||
self.on_tx_action(id, Some(WalletTxAction::SendingTor));
|
||||
|
||||
let tor_addr = OnionV3Address::try_from(addr).unwrap().to_http_str();
|
||||
let url = format!("{}/v2/foreign", tor_addr);
|
||||
let slate_send = VersionedSlate::into_version(slate.clone(), SlateVersion::V4)?;
|
||||
let slate_send = VersionedSlate::into_version(s.clone(), SlateVersion::V4)?;
|
||||
let body = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "receive_tx",
|
||||
@@ -1074,8 +1067,8 @@ impl Wallet {
|
||||
}
|
||||
|
||||
/// Finalize transaction from provided message as sender or invoice issuer.
|
||||
fn finalize(&self, slate: &Slate) -> Result<Slate, Error> {
|
||||
self.on_tx_action(slate.id.to_string(), Some(WalletTransactionAction::Finalizing));
|
||||
fn finalize(&self, slate: &Slate, id: u32) -> Result<Slate, Error> {
|
||||
self.on_tx_action(id, Some(WalletTxAction::Finalizing));
|
||||
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
let instance = r_inst.clone().unwrap();
|
||||
@@ -1090,14 +1083,16 @@ impl Wallet {
|
||||
let _ = self.create_slatepack_message(&slate, None)?;
|
||||
|
||||
// Clear tx action.
|
||||
self.on_tx_action(slate.id.to_string(), None);
|
||||
self.on_tx_action(id, None);
|
||||
|
||||
Ok(slate)
|
||||
}
|
||||
|
||||
/// Post transaction to blockchain.
|
||||
fn post(&self, slate: &Slate) -> Result<(), Error> {
|
||||
self.on_tx_action(slate.id.to_string(), Some(WalletTransactionAction::Posting));
|
||||
fn post(&self, slate: &Slate, id: Option<u32>) -> Result<(), Error> {
|
||||
if let Some(id) = id {
|
||||
self.on_tx_action(id, Some(WalletTxAction::Posting));
|
||||
}
|
||||
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
let instance = r_inst.clone().unwrap();
|
||||
@@ -1108,19 +1103,19 @@ impl Wallet {
|
||||
})?;
|
||||
|
||||
// Clear tx action.
|
||||
self.on_tx_action(slate.id.to_string(), None);
|
||||
|
||||
if let Some(id) = id {
|
||||
self.on_tx_action(id, None);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cancel transaction.
|
||||
fn cancel(&self, tx: &TxLogEntry) -> Result<(), Error> {
|
||||
let id = tx.tx_slate_id.unwrap().to_string();
|
||||
self.on_tx_action(id.clone(), Some(WalletTransactionAction::Cancelling));
|
||||
fn cancel(&self, id: u32) -> Result<(), Error> {
|
||||
self.on_tx_action(id, Some(WalletTxAction::Cancelling));
|
||||
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
let instance = r_inst.clone().unwrap();
|
||||
cancel_tx(instance, self.keychain_mask().as_ref(), &None, Some(tx.id), None)?;
|
||||
cancel_tx(instance, self.keychain_mask().as_ref(), &None, Some(id), None)?;
|
||||
|
||||
// Clear tx action.
|
||||
self.on_tx_action(id, None);
|
||||
@@ -1129,25 +1124,30 @@ impl Wallet {
|
||||
}
|
||||
|
||||
/// Update transaction action status.
|
||||
fn on_tx_action(&self, id: String, action: Option<WalletTransactionAction>) {
|
||||
fn on_tx_action(&self, id: u32, action: Option<WalletTxAction>) {
|
||||
let mut w_data = self.data.write();
|
||||
w_data.as_mut().unwrap().on_tx_action(id, action);
|
||||
}
|
||||
|
||||
/// Update transaction action error status.
|
||||
fn on_tx_error(&self, id: String, err: Option<Error>) {
|
||||
fn on_tx_error(&self, id: u32, err: Option<Error>) {
|
||||
let mut w_data = self.data.write();
|
||||
w_data.as_mut().unwrap().on_tx_error(id, err);
|
||||
}
|
||||
|
||||
/// Save task result to consume later.
|
||||
fn on_task_result(&self, id: Option<String>, task: &WalletTask) {
|
||||
fn on_task_result(&self, tx: Option<TxLogEntry>, task: &WalletTask) {
|
||||
let mut w_res = self.task_result.write();
|
||||
let id = if let Some(t) = tx {
|
||||
Some(t.id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
*w_res = Some((id, task.clone()));
|
||||
}
|
||||
|
||||
/// Consume result of successful task.
|
||||
pub fn consume_task_result(&self) -> Option<(Option<String>, WalletTask)> {
|
||||
pub fn consume_task_result(&self) -> Option<(Option<u32>, WalletTask)> {
|
||||
let res = {
|
||||
let r_res = self.task_result.read();
|
||||
r_res.clone()
|
||||
@@ -1159,7 +1159,7 @@ impl Wallet {
|
||||
}
|
||||
|
||||
/// Get possible transaction confirmation height.
|
||||
fn tx_height(&self, tx: &WalletTransaction) -> Result<Option<u64>, Error> {
|
||||
fn tx_height(&self, tx: &WalletTx) -> Result<Option<u64>, Error> {
|
||||
let mut tx_height = None;
|
||||
if tx.data.confirmed && tx.data.kernel_excess.is_some() {
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
@@ -1185,17 +1185,45 @@ impl Wallet {
|
||||
Ok(tx_height)
|
||||
}
|
||||
|
||||
/// Get transaction Slate from database.
|
||||
fn get_tx(&self, tx_id: u32) -> Option<Slate> {
|
||||
/// Get stored transaction Slate.
|
||||
fn get_tx_slate(&self, tx_id: Option<u32>, slate_id: Option<&Uuid>) -> Option<Slate> {
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
let instance = r_inst.clone().unwrap();
|
||||
let api = Owner::new(instance, None);
|
||||
if let Ok(s) = api.get_stored_tx(self.keychain_mask().as_ref(), Some(tx_id), None) {
|
||||
if let Ok(s) = api.get_stored_tx(self.keychain_mask().as_ref(), tx_id, slate_id) {
|
||||
return s;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Delete transaction from database.
|
||||
fn delete_tx(&self, id: u32) -> Result<(), Error> {
|
||||
self.on_tx_action(id, Some(WalletTxAction::Deleting));
|
||||
|
||||
let slate = self.get_tx_slate(Some(id), None);
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
let instance = r_inst.clone().unwrap();
|
||||
let keychain_mask = self.keychain_mask();
|
||||
let mut wallet_lock = instance.lock();
|
||||
let lc = wallet_lock.lc_provider()?;
|
||||
let w = lc.wallet_inst()?;
|
||||
let parent_key = w.parent_key_id();
|
||||
let mut batch = w.batch(keychain_mask.as_ref())?;
|
||||
batch.delete_tx_log_entry(id, &parent_key)?;
|
||||
batch.commit()?;
|
||||
|
||||
// Delete transaction files.
|
||||
if let Some(s) = slate {
|
||||
let slatepack_path = self.get_config().get_slate_path(&s);
|
||||
fs::remove_file(&slatepack_path).unwrap_or_default();
|
||||
let path = path::Path::new(&self.get_config().get_data_path())
|
||||
.join("saved_txs")
|
||||
.join(format!("{}.grintx", s.id));
|
||||
fs::remove_file(&path).unwrap_or_default();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Change wallet password.
|
||||
pub fn change_password(&self, old: String, new: String) -> Result<(), Error> {
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
@@ -1577,36 +1605,33 @@ fn start_sync(wallet: Wallet) -> Thread {
|
||||
|
||||
/// Handle wallet task.
|
||||
async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
let send_tor = async |s: &Slate, r: &SlatepackAddress| {
|
||||
let id = s.id.to_string();
|
||||
match w.send_tor(&s, r).await {
|
||||
let send_tor = async |tx: TxLogEntry, s: &Slate, r: &SlatepackAddress| {
|
||||
match w.send_tor(tx.id, &s, r).await {
|
||||
Ok(s) => {
|
||||
match w.finalize(&s) {
|
||||
match w.finalize(&s, tx.id) {
|
||||
Ok(s) => {
|
||||
match w.post(&s) {
|
||||
match w.post(&s, Some(tx.id)) {
|
||||
Ok(_) => {
|
||||
sync_wallet_data(&w, false);
|
||||
w.on_task_result(Some(id), &t);
|
||||
w.on_task_result(Some(tx), &t);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("send tor post error: {:?}", e);
|
||||
w.on_tx_error(id, Some(e));
|
||||
w.on_tx_error(tx.id, Some(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("send tor finalize error: {:?}", e);
|
||||
if let Some(tx) = w.retrieve_tx_by_id(s.id) {
|
||||
let _ = w.cancel(&tx);
|
||||
sync_wallet_data(&w, false);
|
||||
}
|
||||
let _ = w.cancel(tx.id);
|
||||
sync_wallet_data(&w, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("send tor error: {:?}", e);
|
||||
w.on_tx_error(id.clone(), Some(e));
|
||||
w.on_task_result(Some(id), &t);
|
||||
w.on_tx_error(tx.id, Some(e));
|
||||
w.on_task_result(Some(tx), &t);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1616,74 +1641,71 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
return;
|
||||
}
|
||||
let w = w.clone();
|
||||
let load = w.message_opening.clone();
|
||||
let msg = m.clone();
|
||||
thread::spawn(move || {
|
||||
load.store(true, Ordering::Relaxed);
|
||||
if let Ok((s, dest)) = w.parse_slatepack(&msg) {
|
||||
let id = s.id.to_string();
|
||||
// Check if message already exists.
|
||||
let exists = {
|
||||
let mut exists = w.slatepack_exists(&s);
|
||||
if !exists && (s.state == SlateState::Invoice2 ||
|
||||
s.state == SlateState::Standard2) {
|
||||
let mut slate = s.clone();
|
||||
slate.state = if s.state == SlateState::Standard2 {
|
||||
SlateState::Standard3
|
||||
} else {
|
||||
SlateState::Invoice3
|
||||
};
|
||||
exists = w.slatepack_exists(&slate);
|
||||
}
|
||||
exists
|
||||
};
|
||||
if exists {
|
||||
w.on_task_result(Some(id), &t);
|
||||
load.store(false, Ordering::Relaxed);
|
||||
return;
|
||||
w.message_opening.store(true, Ordering::Relaxed);
|
||||
if let Ok((s, dest)) = w.parse_slatepack(&msg) {
|
||||
let tx = w.retrieve_tx_by_id(None, Some(s.id));
|
||||
// Check if message already exists.
|
||||
let exists = {
|
||||
let mut exists = w.slatepack_exists(&s);
|
||||
if !exists && (s.state == SlateState::Invoice2 ||
|
||||
s.state == SlateState::Standard2) {
|
||||
let mut slate = s.clone();
|
||||
slate.state = if s.state == SlateState::Standard2 {
|
||||
SlateState::Standard3
|
||||
} else {
|
||||
SlateState::Invoice3
|
||||
};
|
||||
exists = w.slatepack_exists(&slate);
|
||||
}
|
||||
// Create response or finalize.
|
||||
match s.state {
|
||||
SlateState::Standard1 | SlateState::Invoice1 => {
|
||||
if s.state != SlateState::Standard1 {
|
||||
if let Ok(_) = w.pay(&s) {
|
||||
sync_wallet_data(&w, false);
|
||||
w.on_task_result(Some(id), &t);
|
||||
}
|
||||
} else {
|
||||
if let Ok(_) = w.receive(&s, dest) {
|
||||
sync_wallet_data(&w, false);
|
||||
w.on_task_result(Some(id), &t);
|
||||
}
|
||||
exists
|
||||
};
|
||||
if exists {
|
||||
w.on_task_result(tx, &t);
|
||||
w.message_opening.store(false, Ordering::Relaxed);
|
||||
return;
|
||||
}
|
||||
// Create response or finalize.
|
||||
match s.state {
|
||||
SlateState::Standard1 | SlateState::Invoice1 => {
|
||||
if s.state != SlateState::Standard1 {
|
||||
if let Ok(_) = w.pay(&s) {
|
||||
sync_wallet_data(&w, false);
|
||||
w.on_task_result(tx, &t);
|
||||
}
|
||||
} else {
|
||||
if let Ok(_) = w.receive(&s, dest) {
|
||||
sync_wallet_data(&w, false);
|
||||
w.on_task_result(tx, &t);
|
||||
}
|
||||
}
|
||||
SlateState::Standard2 | SlateState::Invoice2 => {
|
||||
match w.finalize(&s) {
|
||||
}
|
||||
SlateState::Standard2 | SlateState::Invoice2 => {
|
||||
if let Some(tx) = tx {
|
||||
match w.finalize(&s, tx.id) {
|
||||
Ok(s) => {
|
||||
match w.post(&s) {
|
||||
match w.post(&s, Some(tx.id)) {
|
||||
Ok(_) => {
|
||||
sync_wallet_data(&w, false);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("message tx post error: {:?}", e);
|
||||
w.on_tx_error(id, Some(e));
|
||||
w.on_tx_error(tx.id, Some(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(tx) = w.retrieve_tx_by_id(s.id) {
|
||||
let _ = w.cancel(&tx);
|
||||
}
|
||||
let _ = w.cancel(tx.id);
|
||||
error!("message tx finalize error: {:?}", e);
|
||||
w.on_tx_error(id, Some(e));
|
||||
w.on_tx_error(tx.id, Some(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
load.store(false, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
w.message_opening.store(false, Ordering::Relaxed);
|
||||
}
|
||||
WalletTask::CalculateFee(a, _) => {
|
||||
// Wait if there are no more fee tasks or handle next input value.
|
||||
@@ -1710,95 +1732,101 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
w.send_creating.store(true, Ordering::Relaxed);
|
||||
if let Ok(s) = w.send(*a, r.clone()) {
|
||||
sync_wallet_data(&w, false);
|
||||
if let Some(addr) = r {
|
||||
w.send_creating.store(false, Ordering::Relaxed);
|
||||
send_tor(&s, addr).await;
|
||||
return;
|
||||
} else {
|
||||
w.on_task_result(Some(s.id.to_string()), &t);
|
||||
let tx = w.retrieve_tx_by_id(None, Some(s.id));
|
||||
if let Some(tx) = tx {
|
||||
if let Some(addr) = r {
|
||||
w.send_creating.store(false, Ordering::Relaxed);
|
||||
send_tor(tx, &s, addr).await;
|
||||
return;
|
||||
} else {
|
||||
w.on_task_result(Some(tx), &t);
|
||||
}
|
||||
}
|
||||
}
|
||||
w.send_creating.store(false, Ordering::Relaxed);
|
||||
}
|
||||
WalletTask::SendTor(id, r) => {
|
||||
if let Some(s) = w.get_tx(*id) {
|
||||
send_tor(&s, r).await;
|
||||
WalletTask::SendTor(tx, r) => {
|
||||
if let Some(s) = w.get_tx_slate(Some(tx.id), None) {
|
||||
send_tor(tx.clone(), &s, r).await;
|
||||
}
|
||||
}
|
||||
WalletTask::Receive(a) => {
|
||||
w.invoice_creating.store(true, Ordering::Relaxed);
|
||||
if let Ok(s) = w.issue_invoice(*a) {
|
||||
sync_wallet_data(&w, false);
|
||||
w.on_task_result(Some(s.id.to_string()), &t);
|
||||
let tx = w.retrieve_tx_by_id(None, Some(s.id));
|
||||
if let Some(tx) = tx {
|
||||
w.on_task_result(Some(tx), &t);
|
||||
}
|
||||
}
|
||||
w.invoice_creating.store(false, Ordering::Relaxed);
|
||||
},
|
||||
WalletTask::Finalize(id) => {
|
||||
let slate = &w.get_tx(*id).unwrap();
|
||||
w.on_tx_error(slate.id.to_string(), None);
|
||||
match w.finalize(slate) {
|
||||
Ok(s) => {
|
||||
match w.post(&s) {
|
||||
Ok(_) => {
|
||||
sync_wallet_data(&w, false);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("tx finalize post error: {:?}", e);
|
||||
w.on_tx_error(slate.id.to_string(), Some(e));
|
||||
if let Some(s) = w.get_tx_slate(Some(*id), None) {
|
||||
w.on_tx_error(*id, None);
|
||||
match w.finalize(&s, *id) {
|
||||
Ok(s) => {
|
||||
match w.post(&s, Some(*id)) {
|
||||
Ok(_) => {
|
||||
sync_wallet_data(&w, false);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("tx finalize post error: {:?}", e);
|
||||
w.on_tx_error(*id, Some(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(tx) = w.retrieve_tx_by_id(slate.id) {
|
||||
let _ = w.cancel(&tx);
|
||||
Err(e) => {
|
||||
let _ = w.cancel(*id);
|
||||
error!("tx finalize error: {:?}", e);
|
||||
w.on_tx_error(*id, Some(e));
|
||||
}
|
||||
error!("tx finalize error: {:?}", e);
|
||||
w.on_tx_error(slate.id.to_string(), Some(e));
|
||||
}
|
||||
} else {
|
||||
w.on_tx_error(*id, Some(Error::GenericError("tx slate not found".to_string())));
|
||||
}
|
||||
}
|
||||
WalletTask::Post(id) => {
|
||||
let slate = &w.get_tx(*id).unwrap();
|
||||
w.on_tx_error(slate.id.to_string(), None);
|
||||
|
||||
// Cleanup broadcasting tx height.
|
||||
let tx_height_store = TxHeightStore::new(w.get_config().get_extra_db_path());
|
||||
tx_height_store.delete_broadcasting_height(&slate.id.to_string());
|
||||
|
||||
let has_data = {
|
||||
let r_data = w.data.read();
|
||||
r_data.is_some()
|
||||
};
|
||||
if has_data {
|
||||
let mut w_data = w.data.write();
|
||||
for tx in w_data.as_mut().unwrap().txs.as_mut().unwrap() {
|
||||
if tx.data.id == *id {
|
||||
tx.broadcasting_height = None;
|
||||
break;
|
||||
if let Some(s) = w.get_tx_slate(Some(*id), None) {
|
||||
w.on_tx_error(*id, None);
|
||||
// Cleanup broadcasting tx height.
|
||||
let tx_height_store = TxHeightStore::new(w.get_config().get_extra_db_path());
|
||||
tx_height_store.delete_broadcasting_height(&id.to_string());
|
||||
let has_data = {
|
||||
let r_data = w.data.read();
|
||||
r_data.is_some()
|
||||
};
|
||||
if has_data {
|
||||
let mut w_data = w.data.write();
|
||||
for tx in w_data.as_mut().unwrap().txs.as_mut().unwrap() {
|
||||
if tx.data.id == *id {
|
||||
tx.broadcasting_height = None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match w.post(slate) {
|
||||
Ok(_) => {
|
||||
sync_wallet_data(&w, false);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("tx post error: {:?}", e);
|
||||
w.on_tx_error(slate.id.to_string(), Some(e));
|
||||
// Post transaction.
|
||||
match w.post(&s, Some(*id)) {
|
||||
Ok(_) => {
|
||||
sync_wallet_data(&w, false);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("tx post error: {:?}", e);
|
||||
w.on_tx_error(*id, Some(e));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
w.on_tx_error(*id, Some(Error::GenericError("tx slate not found".to_string())));
|
||||
}
|
||||
|
||||
}
|
||||
WalletTask::Cancel(tx) => {
|
||||
match w.cancel(tx) {
|
||||
WalletTask::Cancel(id) => {
|
||||
match w.cancel(*id) {
|
||||
Ok(_) => {
|
||||
sync_wallet_data(&w, false);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("tx cancel error: {:?}", e);
|
||||
let id = tx.tx_slate_id.unwrap().to_string();
|
||||
w.on_tx_error(id, Some(e));
|
||||
w.on_tx_error(*id, Some(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1808,6 +1836,15 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
w.proof_verifying.store(false, Ordering::Relaxed);
|
||||
w.on_task_result(None, &WalletTask::VerifyProof(p.clone(), Some(res)));
|
||||
}
|
||||
WalletTask::Delete(id) => {
|
||||
match w.delete_tx(*id) {
|
||||
Ok(_) => sync_wallet_data(&w, false),
|
||||
Err(e) => {
|
||||
error!("tx delete error: {:?}", e);
|
||||
w.on_tx_error(*id, Some(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1947,11 +1984,11 @@ fn update_txs(wallet: &Wallet, mut txs_limit: u32) -> Result<(), Error> {
|
||||
let tx_height_store = TxHeightStore::new(wallet.get_config().get_extra_db_path());
|
||||
let data = wallet.get_data().unwrap();
|
||||
let data_txs = data.txs.unwrap_or(vec![]);
|
||||
let mut new_txs: Vec<WalletTransaction> = vec![];
|
||||
let mut new_txs: Vec<WalletTx> = vec![];
|
||||
for tx in &filter_txs {
|
||||
let mut height: Option<u64> = None;
|
||||
let mut broadcasting_height: Option<u64> = None;
|
||||
let mut action: Option<WalletTransactionAction> = None;
|
||||
let mut action: Option<WalletTxAction> = None;
|
||||
let mut action_error: Option<Error> = None;
|
||||
let mut proof: Option<PaymentProof> = None;
|
||||
for t in &data_txs {
|
||||
@@ -1964,13 +2001,13 @@ fn update_txs(wallet: &Wallet, mut txs_limit: u32) -> Result<(), Error> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut new = WalletTransaction::new(tx.clone(),
|
||||
proof.clone(),
|
||||
wallet,
|
||||
height,
|
||||
broadcasting_height,
|
||||
action,
|
||||
action_error);
|
||||
let mut new = WalletTx::new(tx.clone(),
|
||||
proof.clone(),
|
||||
wallet,
|
||||
height,
|
||||
broadcasting_height,
|
||||
action,
|
||||
action_error);
|
||||
// Update Slate state for unconfirmed.
|
||||
let unconfirmed = !tx.confirmed && (tx.tx_type == TxLogEntryType::TxSent ||
|
||||
tx.tx_type == TxLogEntryType::TxReceived);
|
||||
@@ -2014,8 +2051,9 @@ fn update_txs(wallet: &Wallet, mut txs_limit: u32) -> Result<(), Error> {
|
||||
new.broadcasting_height = broadcasting_height;
|
||||
}
|
||||
}
|
||||
|
||||
new_txs.push(new);
|
||||
if !new.deleting() {
|
||||
new_txs.push(new);
|
||||
}
|
||||
}
|
||||
// Update wallet txs.
|
||||
let mut w_data = wallet.data.write();
|
||||
|
||||
+1
-1
Submodule wallet updated: a6e0cdae0c...da17e16541
Reference in New Issue
Block a user