Merge #1760: Improve timelock duration selection and display

749be2ec9eac16f2596a7bdf57f5ecbb82ab5465 refacto: sequence duration formatting (Thomas Ballivet)
7251c098adfbdbd2cad4e16af10a3ae0ac4da761 feat: add monthly granularity to timelock slider (Thomas Ballivet)

Pull request description:

  The first commit adds monthly steps to the timelock selection slider and adapts time estimation precision.
  The second is a tiny GUI improvement.

  Here few examples :

  ![Capture d’écran du 2025-06-29 19-22-16](https://github.com/user-attachments/assets/3d037390-f22e-4213-8210-c9daa3c882a8)
  ![Capture d’écran du 2025-06-29 19-22-12](https://github.com/user-attachments/assets/ceaff667-8066-4ef6-8ade-51a0828fbb44)
  ![Capture d’écran du 2025-06-29 19-22-01](https://github.com/user-attachments/assets/aa9fd978-08c9-4cd7-99cd-532b648f1add)
  ![Capture d’écran du 2025-06-29 19-21-56](https://github.com/user-attachments/assets/cffa46a2-d320-4b69-91e1-7afd14ffdb54)
  ![Capture d’écran du 2025-06-29 19-21-34](https://github.com/user-attachments/assets/1abe5639-04ab-4ac8-aa3c-5e581071dd7f)

ACKs for top commit:
  jp1ac4:
    tACK 749be2ec9eac16f2596a7bdf57f5ecbb82ab5465. Thanks!

Tree-SHA512: 21ddb173a84fe10323cb0841c346184acb5a7805799f2c2bbc0d538e194d42a017c15f203534b7eb651a00138a615557643818f37c842efb2324c168b796f49f
This commit is contained in:
edouardparis 2025-07-04 11:25:21 +02:00
commit c9cc6514cf
No known key found for this signature in database
GPG Key ID: E65F7A089C20DC8F
2 changed files with 67 additions and 38 deletions

View File

@ -3,7 +3,7 @@
pub mod template;
use iced::widget::{self, container, pick_list, scrollable, slider, Button, Space};
use iced::{Alignment, Length};
use iced::{alignment, Alignment, Length};
use liana::miniscript::bitcoin::Network;
use liana_ui::component::text::{self, h3, p1_bold, p2_regular, H3_SIZE};
@ -524,7 +524,7 @@ fn example_xpub(network: Network) -> String {
}
/// returns y,m,d,h,m
pub fn duration_from_sequence(sequence: u16) -> (u32, u32, u32, u32, u32) {
fn duration_from_sequence(sequence: u16) -> (u32, u32, u32, u32, u32) {
let mut n_minutes = sequence as u32 * 10;
let n_years = n_minutes / 525960;
n_minutes -= n_years * 525960;
@ -538,6 +538,44 @@ pub fn duration_from_sequence(sequence: u16) -> (u32, u32, u32, u32, u32) {
(n_years, n_months, n_days, n_hours, n_minutes)
}
/// Formats a Bitcoin sequence duration into readable units with smart truncation.
///
/// Converts block count to (value, unit) tuples and truncates precision based on duration:
/// - ≥ 1440 blocks (~10d): show up to days (e.g., "1m 10d")
/// - 144-1439 blocks (~1-10d): show up to hours (e.g., "2d 5h")
/// - < 144 blocks: show all units (e.g., "3h 45mn")
///
/// `short_format`: true = "y/m/d/h/mn", false = "year/month/day/hour/minute"
pub fn format_sequence_duration(sequence: u16, short_format: bool) -> Vec<(u32, &'static str)> {
let (n_years, n_months, n_days, n_hours, n_minutes) = duration_from_sequence(sequence);
let mut formatted_duration = if short_format {
vec![
(n_years, "y"),
(n_months, "m"),
(n_days, "d"),
(n_hours, "h"),
(n_minutes, "mn"),
]
} else {
vec![
(n_years, "year"),
(n_months, "month"),
(n_days, "day"),
(n_hours, "hour"),
(n_minutes, "minute"),
]
};
if sequence >= 1440 {
formatted_duration.truncate(3);
} else if sequence >= 144 {
formatted_duration.truncate(4);
}
formatted_duration
}
pub fn edit_sequence_modal<'a>(sequence: &form::Value<String>) -> Element<'a, Message> {
let mut col = Column::new()
.width(Length::Fill)
@ -555,28 +593,21 @@ pub fn edit_sequence_modal<'a>(sequence: &form::Value<String>) -> Element<'a, Me
),
)
})
.warning("Sequence must be superior to 0 and inferior to 65535"),
.warning("Value must be superior to 0 and inferior to 65535"),
)
.width(Length::Fixed(200.0)),
)
.spacing(10)
.push(text("blocks").bold()),
.push(text("blocks").bold())
.align_y(alignment::Vertical::Center),
);
if sequence.valid {
if let Ok(sequence) = u16::from_str(&sequence.value) {
let (n_years, n_months, n_days, n_hours, n_minutes) = duration_from_sequence(sequence);
col = col
.push(
[
(n_years, "year"),
(n_months, "month"),
(n_days, "day"),
(n_hours, "hour"),
(n_minutes, "minute"),
]
.iter()
.fold(Row::new().spacing(5), |row, (n, unit)| {
.push(format_sequence_duration(sequence, false).iter().fold(
Row::new().spacing(5).push(text("~ ").bold()),
|row, (n, unit)| {
row.push_maybe(if *n > 0 {
Some(
text(format!("{} {}{}", n, unit, if *n > 1 { "s" } else { "" }))
@ -585,18 +616,23 @@ pub fn edit_sequence_modal<'a>(sequence: &form::Value<String>) -> Element<'a, Me
} else {
None
})
}),
)
},
))
.push(
Container::new(
slider(1..=u16::MAX, sequence, |v| {
Message::DefineDescriptor(
message::DefineDescriptor::ThresholdSequenceModal(
message::ThresholdSequenceModal::SequenceEdited(v.to_string()),
message::ThresholdSequenceModal::SequenceEdited(
// Since slider starts at 1, intermediate values are off by 1 from intended values.
// Subtract 1 to align with expected sequence values, except for edge cases (1 and u16::MAX)
(if v > 1 && v != u16::MAX { v - 1 } else { v })
.to_string(),
),
),
)
})
.step(144_u16), // 144 blocks per day
.step(4383_u16), // 4383 blocks per month
)
.width(Length::Fixed(500.0)),
);

View File

@ -40,7 +40,7 @@ use crate::{
message::{self, DefineBitcoind, DefineNode, Message},
prompt,
step::{DownloadState, InstallState},
view::editor::duration_from_sequence,
view::editor::format_sequence_duration,
Error,
},
node::{
@ -1616,29 +1616,22 @@ pub fn defined_sequence<'a>(
sequence: PathSequence,
warning: Option<PathWarning>,
) -> Element<'a, message::DefinePath> {
let (n_years, n_months, n_days, n_hours, n_minutes) = duration_from_sequence(sequence.as_u16());
let duration_row = Row::new()
.padding(5)
.spacing(5)
.align_y(Alignment::Center)
.push(text(
[
(n_years, "y"),
(n_months, "m"),
(n_days, "d"),
(n_hours, "h"),
(n_minutes, "mn"),
]
.iter()
.filter_map(|(n, unit)| {
if *n > 0 {
Some(format!("{}{}", n, unit))
} else {
None
}
})
.collect::<Vec<String>>()
.join(" "),
format_sequence_duration(sequence.as_u16(), true)
.iter()
.filter_map(|(n, unit)| {
if *n > 0 {
Some(format!("{}{}", n, unit))
} else {
None
}
})
.collect::<Vec<String>>()
.join(" "),
));
Container::new(
Column::new()