Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
254 changes: 210 additions & 44 deletions src/ui/identities/register_dpns_name_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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::{Identifier, 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 {
Expand All @@ -30,6 +34,10 @@ pub struct RegisterDpnsNameScreen {
name_input: String,
register_dpns_name_status: RegisterDpnsNameStatus,
pub app_context: Arc<AppContext>,
selected_wallet: Option<Arc<RwLock<Wallet>>>,
wallet_password: String,
show_password: bool,
error_message: Option<String>,
}

impl RegisterDpnsNameScreen {
Expand Down Expand Up @@ -80,6 +88,11 @@ 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
};
let show_identity_selector = qualified_identities.len() > 0;
Self {
show_identity_selector,
Expand All @@ -88,49 +101,77 @@ impl RegisterDpnsNameScreen {
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 {
Expand Down Expand Up @@ -178,24 +219,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;
}

if self.show_identity_selector {
// Select the identity to register the name for
self.render_identity_id_selection(ui);
}
// 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);

// Input for the name
// 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);
});
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",
);
}
}

if ui.button("Register Name").clicked() {
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()
.duration_since(UNIX_EPOCH)
Expand All @@ -205,6 +299,7 @@ impl ScreenLike for RegisterDpnsNameScreen {
action = self.register_dpns_name_clicked();
}

// Handle registration status messages
match &self.register_dpns_name_status {
RegisterDpnsNameStatus::NotStarted => {
// Do nothing
Expand Down Expand Up @@ -246,8 +341,79 @@ 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(" • 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");
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<Arc<RwLock<Wallet>>> {
&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<String>) {
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
}