From 7f06c58ea071e3711bb34c5e0864b0cae948ffbe Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Tue, 12 Nov 2024 17:16:23 +0700 Subject: [PATCH 1/4] revamp register dpns name screen --- .../identities/register_dpns_name_screen.rs | 265 ++++++++++++++---- 1 file changed, 216 insertions(+), 49 deletions(-) diff --git a/src/ui/identities/register_dpns_name_screen.rs b/src/ui/identities/register_dpns_name_screen.rs index 7df9af6d3..67d517866 100644 --- a/src/ui/identities/register_dpns_name_screen.rs +++ b/src/ui/identities/register_dpns_name_screen.rs @@ -3,7 +3,9 @@ use crate::backend_task::identity::{IdentityTask, RegisterDpnsNameInput}; use crate::backend_task::BackendTask; use crate::context::AppContext; use crate::model::qualified_identity::QualifiedIdentity; +use crate::model::wallet::Wallet; use crate::ui::components::top_panel::add_top_panel; +use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; use crate::ui::{MessageType, ScreenLike}; use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; use dash_sdk::dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; @@ -13,7 +15,9 @@ use dash_sdk::dpp::identity::{Purpose, SecurityLevel, TimestampMillis}; use dash_sdk::dpp::platform_value::string_encoding::Encoding; use dash_sdk::platform::IdentityPublicKey; use eframe::egui::Context; +use egui::{Color32, RichText}; use std::sync::Arc; +use std::sync::RwLock; use std::time::{SystemTime, UNIX_EPOCH}; pub enum RegisterDpnsNameStatus { @@ -29,6 +33,10 @@ pub struct RegisterDpnsNameScreen { name_input: String, register_dpns_name_status: RegisterDpnsNameStatus, pub app_context: Arc, + selected_wallet: Option>>, + wallet_password: String, + show_password: bool, + error_message: Option, } impl RegisterDpnsNameScreen { @@ -64,55 +72,88 @@ impl RegisterDpnsNameScreen { }) .collect(); let selected_qualified_identity = qualified_identities.first().cloned(); + let selected_wallet = if let Some(wallet) = app_context.wallets.read().unwrap().first() { + Some(wallet.clone()) + } else { + None + }; Self { qualified_identities, selected_qualified_identity, name_input: String::new(), register_dpns_name_status: RegisterDpnsNameStatus::NotStarted, app_context: app_context.clone(), + selected_wallet, + wallet_password: String::new(), + show_password: false, + error_message: None, } } fn render_identity_id_selection(&mut self, ui: &mut egui::Ui) { - ui.horizontal(|ui| { - ui.label("Identity ID:"); - - // Create a ComboBox for selecting a Qualified Identity - egui::ComboBox::from_label("") - .selected_text( - self.selected_qualified_identity + if self.qualified_identities.len() == 1 { + // Only one identity, display it directly + let qualified_identity = &self.qualified_identities[0]; + ui.horizontal(|ui| { + ui.label("Identity ID:"); + ui.label( + qualified_identity + .0 + .alias .as_ref() - .map(|qi| { - qi.0.alias - .as_ref() - .unwrap_or(&qi.0.identity.id().to_string(Encoding::Base58)) - .clone() - }) - .unwrap_or_else(|| "Select an identity".to_string()), - ) - .show_ui(ui, |ui| { - // Loop through the qualified identities and display each as selectable - for qualified_identity in &self.qualified_identities { - // Display each QualifiedIdentity as a selectable item - if ui - .selectable_value( - &mut self.selected_qualified_identity, - Some(qualified_identity.clone()), - qualified_identity.0.alias.as_ref().unwrap_or( - &qualified_identity - .0 - .identity - .id() - .to_string(Encoding::Base58), - ), - ) - .clicked() - { - self.selected_qualified_identity = Some(qualified_identity.clone()); + .unwrap_or( + &qualified_identity + .0 + .identity + .id() + .to_string(Encoding::Base58), + ) + .clone(), + ); + }); + self.selected_qualified_identity = Some(qualified_identity.clone()); + } else { + // Multiple identities, display ComboBox + ui.horizontal(|ui| { + ui.label("Identity ID:"); + + // Create a ComboBox for selecting a Qualified Identity + egui::ComboBox::from_label("") + .selected_text( + self.selected_qualified_identity + .as_ref() + .map(|qi| { + qi.0.alias + .as_ref() + .unwrap_or(&qi.0.identity.id().to_string(Encoding::Base58)) + .clone() + }) + .unwrap_or_else(|| "Select an identity".to_string()), + ) + .show_ui(ui, |ui| { + // Loop through the qualified identities and display each as selectable + for qualified_identity in &self.qualified_identities { + // Display each QualifiedIdentity as a selectable item + if ui + .selectable_value( + &mut self.selected_qualified_identity, + Some(qualified_identity.clone()), + qualified_identity.0.alias.as_ref().unwrap_or( + &qualified_identity + .0 + .identity + .id() + .to_string(Encoding::Base58), + ), + ) + .clicked() + { + self.selected_qualified_identity = Some(qualified_identity.clone()); + } } - } - }); - }); + }); + }); + } } fn register_dpns_name_clicked(&mut self) -> AppAction { @@ -160,22 +201,77 @@ impl ScreenLike for RegisterDpnsNameScreen { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Register DPNS Name"); - + ui.add_space(10.0); + + // Before proceeding, check if we need to render wallet unlock + let (needs_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); + if needs_unlock { + if !just_unlocked { + return; + } + } + + // If no identities loaded, give message if self.qualified_identities.is_empty() { - ui.label("No qualified identities available to register a DPNS name."); + ui.colored_label( + egui::Color32::DARK_RED, + "No qualified identities available to register a DPNS name.", + ); return; } - - // Select the identity to register the name for + + // 1. Select the identity to register the name for + ui.heading("1. Select Identity"); + ui.add_space(5.0); self.render_identity_id_selection(ui); - - // Input for the name + + ui.add_space(10.0); + ui.separator(); + ui.add_space(10.0); + + // 2. Input for the name + ui.heading("2. Enter the Name to Register:"); + ui.add_space(5.0); ui.horizontal(|ui| { ui.label("Name (without \".dash\"):"); ui.text_edit_singleline(&mut self.name_input); }); - - if ui.button("Register Name").clicked() { + ui.add_space(10.0); + + // Display if the name is contested and the estimated cost + let name = self.name_input.trim(); + if !name.is_empty() && name.len() >= 3 { + if is_contested_name(&name.to_lowercase()) { + ui.colored_label( + egui::Color32::DARK_RED, + "This is a contested name.", + ); + ui.colored_label( + egui::Color32::DARK_RED, + "Cost ≈ 0.2 Dash", + ); + } else { + ui.colored_label( + egui::Color32::DARK_GREEN, + "This is not a contested name.", + ); + ui.colored_label( + egui::Color32::DARK_GREEN, + "Cost ≈ 0.0006 Dash", + ); + } + } + + ui.add_space(5.0); + + // Register button + let button = egui::Button::new(RichText::new("Register Name").color(Color32::WHITE)) + .fill(Color32::from_rgb(0, 128, 255)) + .frame(true) + .rounding(3.0) + .min_size(egui::vec2(80.0, 30.0)); + + if ui.add(button).clicked() { // Set the status to waiting and capture the current time let now = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -184,7 +280,8 @@ impl ScreenLike for RegisterDpnsNameScreen { self.register_dpns_name_status = RegisterDpnsNameStatus::WaitingForResult(now); action = self.register_dpns_name_clicked(); } - + + // Handle registration status messages match &self.register_dpns_name_status { RegisterDpnsNameStatus::NotStarted => { // Do nothing @@ -195,7 +292,7 @@ impl ScreenLike for RegisterDpnsNameScreen { .expect("Time went backwards") .as_secs(); let elapsed_seconds = now - start_time; - + let display_time = if elapsed_seconds < 60 { format!( "{} second{}", @@ -213,7 +310,7 @@ impl ScreenLike for RegisterDpnsNameScreen { if seconds == 1 { "" } else { "s" } ) }; - + ui.label(format!( "Registering... Time taken so far: {}", display_time @@ -226,8 +323,78 @@ impl ScreenLike for RegisterDpnsNameScreen { action = AppAction::PopScreenAndRefresh; } } - }); + ui.add_space(10.0); + ui.separator(); + ui.add_space(10.0); + + // DPNS Name Constraints Explanation + ui.heading("DPNS Name Constraints:"); + ui.add_space(5.0); + ui.label(" • Minimum length: 3 characters"); + ui.label(" • Maximum length: 63 characters"); + ui.label(" • Allowed characters: letters (A-Z, case-insensitive), numbers (0-9), and hyphens (-)"); + ui.label(" • Cannot start or end with a hyphen (-)"); + ui.label(" • Names are case-sensitive"); + + ui.add_space(20.0); + + // Contested Names Explanation + ui.heading("Contested Names Info:"); + ui.add_space(5.0); + ui.label(" • To prevent name front-running, some names are contested and require a higher fee to register."); + ui.label(" • Masternodes vote whether or not to award contested names to contestants."); + ui.label(" • Contested names are those that are:"); + ui.label(" • Less than 20 characters long (i.e. “alice”, “quantumexplorer”)"); + ui.label(" • AND"); + ui.label(" • Contain no numbers or only contain the number(s) 0 and/or 1 (i.e. “bob”, “carol01”)"); + }); + action } } + +impl ScreenWithWalletUnlock for RegisterDpnsNameScreen { + fn selected_wallet_ref(&self) -> &Option>> { + &self.selected_wallet + } + + fn wallet_password_ref(&self) -> &String { + &self.wallet_password + } + + fn wallet_password_mut(&mut self) -> &mut String { + &mut self.wallet_password + } + + fn show_password(&self) -> bool { + self.show_password + } + + fn show_password_mut(&mut self) -> &mut bool { + &mut self.show_password + } + + fn set_error_message(&mut self, error_message: Option) { + self.error_message = error_message; + } + + fn error_message(&self) -> Option<&String> { + self.error_message.as_ref() + } +} + +pub fn is_contested_name(name: &str) -> bool { + let length = name.len(); + if length >= 20 { + return false; + } + for c in name.chars() { + if c.is_digit(10) { + if c != '0' && c != '1' { + return false; + } + } + } + true +} From 47bc95a4686666fbf825e1a3770157df5202bc85 Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Tue, 12 Nov 2024 17:22:42 +0700 Subject: [PATCH 2/4] ya --- src/ui/identities/register_dpns_name_screen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/identities/register_dpns_name_screen.rs b/src/ui/identities/register_dpns_name_screen.rs index 67d517866..584f01d54 100644 --- a/src/ui/identities/register_dpns_name_screen.rs +++ b/src/ui/identities/register_dpns_name_screen.rs @@ -262,7 +262,7 @@ impl ScreenLike for RegisterDpnsNameScreen { } } - ui.add_space(5.0); + ui.add_space(10.0); // Register button let button = egui::Button::new(RichText::new("Register Name").color(Color32::WHITE)) From 5e19466d72235342fa7e30cb6321e47d2d0be3f5 Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Tue, 12 Nov 2024 17:28:45 +0700 Subject: [PATCH 3/4] cargo fmt --- src/backend_task/core/mod.rs | 5 +++- src/backend_task/core/start_dash_qt.rs | 6 ++-- src/backend_task/identity/load_identity.rs | 2 +- src/ui/components/top_panel.rs | 22 +++++++------- .../identities/register_dpns_name_screen.rs | 30 +++++++++---------- src/ui/network_chooser_screen.rs | 3 +- 6 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/backend_task/core/mod.rs b/src/backend_task/core/mod.rs index 068335631..8dad43414 100644 --- a/src/backend_task/core/mod.rs +++ b/src/backend_task/core/mod.rs @@ -44,7 +44,10 @@ impl AppContext { }) .map_err(|e| e.to_string()), CoreTask::RefreshWalletInfo(wallet) => self.refresh_wallet_info(wallet), - CoreTask::StartDashQT(network) => self.start_dash_qt(network).map_err(|e| e.to_string()).map(|_| BackendTaskSuccessResult::None), + CoreTask::StartDashQT(network) => self + .start_dash_qt(network) + .map_err(|e| e.to_string()) + .map(|_| BackendTaskSuccessResult::None), } } } diff --git a/src/backend_task/core/start_dash_qt.rs b/src/backend_task/core/start_dash_qt.rs index 5c392d9eb..9b4a8a246 100644 --- a/src/backend_task/core/start_dash_qt.rs +++ b/src/backend_task/core/start_dash_qt.rs @@ -1,10 +1,10 @@ +use crate::context::AppContext; +use dash_sdk::dpp::dashcore::Network; use std::path::PathBuf; use std::process::{Command, Stdio}; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::{env, io}; -use dash_sdk::dpp::dashcore::Network; -use crate::context::AppContext; impl AppContext { /// Function to start Dash QT based on the selected network @@ -56,4 +56,4 @@ impl AppContext { Ok(()) } -} \ No newline at end of file +} diff --git a/src/backend_task/identity/load_identity.rs b/src/backend_task/identity/load_identity.rs index a191015cd..b94e5acca 100644 --- a/src/backend_task/identity/load_identity.rs +++ b/src/backend_task/identity/load_identity.rs @@ -7,6 +7,7 @@ use crate::model::qualified_identity::PrivateKeyTarget::{ self, PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, }; use crate::model::qualified_identity::{DPNSNameInfo, IdentityType, QualifiedIdentity}; +use crate::model::wallet::WalletSeedHash; use dash_sdk::dashcore_rpc::dashcore::key::Secp256k1; use dash_sdk::dashcore_rpc::dashcore::PrivateKey; use dash_sdk::dpp::dashcore::hashes::Hash; @@ -22,7 +23,6 @@ use dash_sdk::platform::{Document, DocumentQuery, Fetch, FetchMany, Identifier, use dash_sdk::Sdk; use egui::ahash::HashMap; use std::collections::{BTreeMap, HashSet}; -use crate::model::wallet::WalletSeedHash; impl AppContext { pub(super) async fn load_identity( diff --git a/src/ui/components/top_panel.rs b/src/ui/components/top_panel.rs index 49809d4b8..f9e8d26b6 100644 --- a/src/ui/components/top_panel.rs +++ b/src/ui/components/top_panel.rs @@ -1,11 +1,11 @@ use crate::app::{AppAction, DesiredAppAction}; +use crate::backend_task::core::CoreTask; +use crate::backend_task::BackendTask; use crate::components::core_zmq_listener::ZMQConnectionEvent; use crate::context::AppContext; use dash_sdk::dashcore_rpc::dashcore::Network; use egui::{Align, Color32, Context, Frame, Layout, Margin, RichText, Stroke, TopBottomPanel, Ui}; use std::sync::Arc; -use crate::backend_task::BackendTask; -use crate::backend_task::core::CoreTask; fn add_location_view(ui: &mut Ui, location: Vec<(&str, AppAction)>) -> AppAction { let mut action = AppAction::None; @@ -57,17 +57,18 @@ fn add_connection_indicator(ui: &mut Ui, app_context: &Arc) -> AppAc // Define circle properties let circle_size = 14.0; // Increase size slightly for visibility - let color = if connected { Color32::DARK_GREEN } else { Color32::DARK_RED }; + let color = if connected { + Color32::DARK_GREEN + } else { + Color32::DARK_RED + }; // Allocate space for the circle with some padding ui.horizontal(|ui| { ui.add_space(8.0); // Add padding before the circle for visibility - let (rect, response) = ui.allocate_exact_size( - egui::vec2(circle_size, circle_size), - egui::Sense::click(), - ); - + let (rect, response) = + ui.allocate_exact_size(egui::vec2(circle_size, circle_size), egui::Sense::click()); // Offset the circle's center by 5 pixels down let circle_center = rect.center() + egui::vec2(0.0, 5.0); @@ -80,7 +81,8 @@ fn add_connection_indicator(ui: &mut Ui, app_context: &Arc) -> AppAc ); // Draw the main circle (the "light") - ui.painter().circle_filled(circle_center, circle_size / 2.0, color); + ui.painter() + .circle_filled(circle_center, circle_size / 2.0, color); // Tooltip text let tooltip_text = if connected { @@ -125,7 +127,6 @@ pub fn add_top_panel( .exact_height(50.0) .show(ctx, |ui| { egui::menu::bar(ui, |ui| { - action |= add_connection_indicator(ui, app_context); // Left-aligned content with location view @@ -133,7 +134,6 @@ pub fn add_top_panel( // Right-aligned content with buttons ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - for (text, right_button_action) in right_buttons.into_iter().rev() { ui.add_space(8.0); diff --git a/src/ui/identities/register_dpns_name_screen.rs b/src/ui/identities/register_dpns_name_screen.rs index 584f01d54..2d1fc8718 100644 --- a/src/ui/identities/register_dpns_name_screen.rs +++ b/src/ui/identities/register_dpns_name_screen.rs @@ -202,7 +202,7 @@ impl ScreenLike for RegisterDpnsNameScreen { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Register DPNS Name"); ui.add_space(10.0); - + // Before proceeding, check if we need to render wallet unlock let (needs_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); if needs_unlock { @@ -210,7 +210,7 @@ impl ScreenLike for RegisterDpnsNameScreen { return; } } - + // If no identities loaded, give message if self.qualified_identities.is_empty() { ui.colored_label( @@ -219,16 +219,16 @@ impl ScreenLike for RegisterDpnsNameScreen { ); return; } - + // 1. Select the identity to register the name for ui.heading("1. Select Identity"); ui.add_space(5.0); self.render_identity_id_selection(ui); - + ui.add_space(10.0); ui.separator(); ui.add_space(10.0); - + // 2. Input for the name ui.heading("2. Enter the Name to Register:"); ui.add_space(5.0); @@ -236,8 +236,8 @@ impl ScreenLike for RegisterDpnsNameScreen { ui.label("Name (without \".dash\"):"); ui.text_edit_singleline(&mut self.name_input); }); - ui.add_space(10.0); - + ui.add_space(10.0); + // Display if the name is contested and the estimated cost let name = self.name_input.trim(); if !name.is_empty() && name.len() >= 3 { @@ -261,16 +261,16 @@ impl ScreenLike for RegisterDpnsNameScreen { ); } } - + ui.add_space(10.0); - + // Register button let button = egui::Button::new(RichText::new("Register Name").color(Color32::WHITE)) .fill(Color32::from_rgb(0, 128, 255)) .frame(true) .rounding(3.0) .min_size(egui::vec2(80.0, 30.0)); - + if ui.add(button).clicked() { // Set the status to waiting and capture the current time let now = SystemTime::now() @@ -280,7 +280,7 @@ impl ScreenLike for RegisterDpnsNameScreen { self.register_dpns_name_status = RegisterDpnsNameStatus::WaitingForResult(now); action = self.register_dpns_name_clicked(); } - + // Handle registration status messages match &self.register_dpns_name_status { RegisterDpnsNameStatus::NotStarted => { @@ -292,7 +292,7 @@ impl ScreenLike for RegisterDpnsNameScreen { .expect("Time went backwards") .as_secs(); let elapsed_seconds = now - start_time; - + let display_time = if elapsed_seconds < 60 { format!( "{} second{}", @@ -310,7 +310,7 @@ impl ScreenLike for RegisterDpnsNameScreen { if seconds == 1 { "" } else { "s" } ) }; - + ui.label(format!( "Registering... Time taken so far: {}", display_time @@ -336,7 +336,7 @@ impl ScreenLike for RegisterDpnsNameScreen { ui.label(" • Allowed characters: letters (A-Z, case-insensitive), numbers (0-9), and hyphens (-)"); ui.label(" • Cannot start or end with a hyphen (-)"); ui.label(" • Names are case-sensitive"); - + ui.add_space(20.0); // Contested Names Explanation @@ -349,7 +349,7 @@ impl ScreenLike for RegisterDpnsNameScreen { ui.label(" • AND"); ui.label(" • Contain no numbers or only contain the number(s) 0 and/or 1 (i.e. “bob”, “carol01”)"); }); - + action } } diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index 4eb2f49be..0e99c5c82 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -157,7 +157,8 @@ impl NetworkChooserScreen { // Add a button to start the network if ui.button("Start").clicked() { - app_action |= AppAction::BackendTask(BackendTask::CoreTask(CoreTask::StartDashQT(network))); + app_action |= + AppAction::BackendTask(BackendTask::CoreTask(CoreTask::StartDashQT(network))); // in 5 seconds self.recheck_time = Some( (SystemTime::now() From dc76db80949ce86d885eeab0efdfce4898d6d9a6 Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Tue, 12 Nov 2024 19:14:39 +0700 Subject: [PATCH 4/4] add message about contest duration --- src/ui/identities/register_dpns_name_screen.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/identities/register_dpns_name_screen.rs b/src/ui/identities/register_dpns_name_screen.rs index d2f331842..ed32b5ba2 100644 --- a/src/ui/identities/register_dpns_name_screen.rs +++ b/src/ui/identities/register_dpns_name_screen.rs @@ -362,6 +362,7 @@ impl ScreenLike for RegisterDpnsNameScreen { ui.add_space(5.0); ui.label(" • To prevent name front-running, some names are contested and require a higher fee to register."); ui.label(" • Masternodes vote whether or not to award contested names to contestants."); + ui.label(" • Contests last two weeks and new contenders can only join during the first week."); ui.label(" • Contested names are those that are:"); ui.label(" • Less than 20 characters long (i.e. “alice”, “quantumexplorer”)"); ui.label(" • AND");