Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ test_db*
**/.DS_Store
explorer.log
.gitaipconfig
.claude/worktrees
18 changes: 18 additions & 0 deletions src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -706,12 +706,24 @@ impl AppContext {
/// Import an address into the correct Core wallet if it's not already known.
/// Uses `core_wallet_name` to target the right wallet on multi-wallet nodes.
/// No-op if the address is already watched/mine.
///
/// In SPV mode this is a no-op: there is no Dash Core node to import into.
/// HD-derived addresses are tracked by the SPV wallet manager watching the
/// BIP44 account derived from the same xprv — `Wallet::register_address`
/// records them in wallet state (`known_addresses`) but does not update the
/// SPV bloom filter directly. Incoming UTXOs for these addresses are
/// populated via the SPV reconciliation path (`reconcile_spv_wallets()`),
/// which is what downstream checks such as
/// `capture_qr_funding_utxo_if_available` observe.
pub fn ensure_address_imported(
&self,
address: &Address,
core_wallet_name: Option<&str>,
label: Option<&str>,
) -> Result<(), TaskError> {
if self.core_backend_mode() != CoreBackendMode::Rpc {
return Ok(());
}
Comment thread
lklimek marked this conversation as resolved.
Comment thread
lklimek marked this conversation as resolved.
Comment thread
lklimek marked this conversation as resolved.
Comment thread
lklimek marked this conversation as resolved.
let client = self.core_client_for_wallet(core_wallet_name)?;
let info = client
.get_address_info(address)
Expand All @@ -725,12 +737,18 @@ impl AppContext {
}

/// Import address into Core, ignoring errors. For best-effort registration.
///
/// No-ops in SPV mode — mirroring [`Self::ensure_address_imported`] — because there is no
/// RPC client to talk to and every call would fail silently, wasting resources.
pub fn try_import_address(
&self,
address: &Address,
core_wallet_name: Option<&str>,
label: Option<&str>,
) {
if self.core_backend_mode() != CoreBackendMode::Rpc {
return;
}
if let Ok(client) = self.core_client_for_wallet(core_wallet_name) {
let _ = client.import_address(address, label, Some(false));
}
Expand Down
21 changes: 19 additions & 2 deletions src/ui/components/password_input.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use egui::{Rect, Response, Sense, Stroke, Ui, pos2, vec2};

use crate::model::secret::Secret;
use crate::ui::theme::{DashColors, ResponseExt};
use crate::ui::theme::{DashColors, ResponseExt, Typography};

const PASSWORD_INPUT_HORIZONTAL_PADDING: f32 = 8.0;
const PASSWORD_INPUT_REVEAL_ICON_WIDTH: f32 = 28.0;
Comment thread
lklimek marked this conversation as resolved.

/// Response from [`PasswordInput::show`].
///
Expand Down Expand Up @@ -141,7 +144,7 @@ impl PasswordInput {
.password(!self.revealing)
.hint_text(&self.hint_text)
.margin(egui::Margin {
right: 28,
right: PASSWORD_INPUT_REVEAL_ICON_WIDTH as i8,
..egui::Margin::same(4)
});

Expand All @@ -155,6 +158,20 @@ impl PasswordInput {

if let Some(width) = self.desired_width {
text_edit = text_edit.desired_width(width);
} else if let Some(limit) = self.char_limit {
let font_id = if self.monospace {
egui::TextStyle::Monospace.resolve(ui.style())
} else {
egui::TextStyle::Body.resolve(ui.style())
};
// Wide glyph upper bound — identical width in monospace, safe upper bound in proportional.
let sample = "W".repeat(limit);
let measured_width = Typography::measure_text_width(ui, sample, font_id);
text_edit = text_edit.desired_width(
measured_width
+ PASSWORD_INPUT_HORIZONTAL_PADDING
+ PASSWORD_INPUT_REVEAL_ICON_WIDTH,
);
Comment thread
lklimek marked this conversation as resolved.
Comment thread
lklimek marked this conversation as resolved.
} else {
text_edit = text_edit.desired_width(ui.available_width());
}
Expand Down
3 changes: 3 additions & 0 deletions src/ui/identities/keys/add_key_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ impl AddKeyScreen {
app_context: app_context.clone(),
private_key_input: PasswordInput::new()
.with_hint_text("Private key (hex)")
.with_char_limit(64)
.with_monospace(),
Comment thread
lklimek marked this conversation as resolved.
key_type: KeyType::ECDSA_SECP256K1,
purpose: Purpose::AUTHENTICATION,
Expand Down Expand Up @@ -119,6 +120,7 @@ impl AddKeyScreen {
app_context: app_context.clone(),
private_key_input: PasswordInput::new()
.with_hint_text("Private key (hex)")
.with_char_limit(64)
.with_monospace(),
key_type: KeyType::ECDSA_SECP256K1,
purpose: Purpose::ENCRYPTION,
Expand Down Expand Up @@ -162,6 +164,7 @@ impl AddKeyScreen {
app_context: app_context.clone(),
private_key_input: PasswordInput::new()
.with_hint_text("Private key (hex)")
.with_char_limit(64)
.with_monospace(),
key_type: KeyType::ECDSA_SECP256K1,
purpose: Purpose::DECRYPTION,
Expand Down
12 changes: 11 additions & 1 deletion src/ui/theme.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use egui::{Button, Color32, CursorIcon, FontFamily, FontId, RichText, Stroke, Vec2, WidgetText};
use egui::{
Button, Color32, CursorIcon, FontFamily, FontId, RichText, Stroke, Ui, Vec2, WidgetText,
};

/// Theme mode enumeration
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
Expand Down Expand Up @@ -602,6 +604,14 @@ impl Typography {
pub fn button() -> FontId {
FontId::new(Self::SCALE_BASE, FontFamily::Proportional)
}

/// Measure the width of a representative sample using egui's active font metrics.
pub fn measure_text_width(ui: &Ui, sample: impl Into<String>, font_id: FontId) -> f32 {
ui.painter()
.layout_no_wrap(sample.into(), font_id, Color32::TRANSPARENT)
.size()
.x
}
}

/// Spacing constants for consistent layout
Expand Down
Loading