fix(cli): blink after selecting device

This commit is contained in:
Tien Do Nam
2026-07-31 17:36:37 +02:00
parent 25a0b8a85e
commit 7cd9bb8ebd
5 changed files with 66 additions and 8 deletions
+10 -4
View File
@@ -80,11 +80,17 @@ impl App {
return !self.preselected.is_empty();
}
DeviceListOutcome::Send { fingerprint } => {
self.close_device_list();
if !self.preselected.is_empty() {
self.start_send(&fingerprint, self.preselected.clone());
} else if let Some(device) = self.discovery.device_by_fingerprint(&fingerprint) {
if self.preselected.is_empty()
&& let Some(device) = self.discovery.device_by_fingerprint(&fingerprint)
{
// The list stays open until the picker takes over its
// alternate screen, so the main screen never shows.
self.open_picker(device);
} else {
self.close_device_list();
if !self.preselected.is_empty() {
self.start_send(&fingerprint, self.preselected.clone());
}
}
}
DeviceListOutcome::Pair { fingerprint } => self.pair(&fingerprint),
+25 -4
View File
@@ -46,18 +46,39 @@ impl App {
pub(super) fn open_picker(&mut self, device: StatefulDevice) {
if self.send.is_some() {
self.close_device_list();
self.ui.log(Category::Send, "A send is already in progress");
return;
}
match Picker::open(
device.device.fingerprint.clone(),
device.device.alias.clone(),
) {
// When the device list is open, the picker takes over its alternate
// screen — leaving and re-entering it would flash the main screen.
let handoff = match self.device_list.take() {
Some(list) => {
list.close_keeping_screen();
true
}
None => false,
};
let opened = match handoff {
true => Picker::open_on_alternate_screen(
device.device.fingerprint.clone(),
device.device.alias.clone(),
),
false => Picker::open(
device.device.fingerprint.clone(),
device.device.alias.clone(),
),
};
match opened {
Ok(picker) => {
self.ui.suspend();
self.picker = Some(picker);
}
Err(err) => {
if handoff {
crate::util::leave_alternate_screen();
self.ui.resume();
}
self.ui.log(
Category::Send,
&format!(
+7
View File
@@ -86,6 +86,13 @@ impl DeviceList {
util::leave_alternate_screen();
}
/// Drops the list but stays on the (cleared) alternate screen, handing it
/// over to the next modal (see [crate::picker::Picker::open_on_alternate_screen]).
/// That modal's close leaves the screen.
pub fn close_keeping_screen(self) {
util::clear_alternate_screen();
}
/// Replaces the rows (discovery goes on while the list is open), keeping
/// the highlight on the same device where possible.
pub fn set_rows(&mut self, rows: Vec<Row>) {
+13
View File
@@ -70,6 +70,19 @@ impl Picker {
pub fn open(fingerprint: String, alias: String) -> anyhow::Result<Self> {
let explorer = FileExplorer::new()?;
util::enter_alternate_screen()?;
Self::show(fingerprint, alias, explorer)
}
/// Shows the picker on the blank alternate screen a previous modal left
/// behind (see [crate::device_list::DeviceList::close_keeping_screen]);
/// [Picker::close] leaves it as usual. When this fails, the caller must
/// leave the alternate screen itself.
pub fn open_on_alternate_screen(fingerprint: String, alias: String) -> anyhow::Result<Self> {
let explorer = FileExplorer::new()?;
Self::show(fingerprint, alias, explorer)
}
fn show(fingerprint: String, alias: String, explorer: FileExplorer) -> anyhow::Result<Self> {
let terminal = Terminal::new(CrosstermBackend::new(std::io::stdout()))?;
let mut picker = Self {
fingerprint,
+11
View File
@@ -32,6 +32,17 @@ pub fn leave_alternate_screen() {
);
}
/// Clears the alternate screen without leaving it, for handing it over from
/// one modal to the next — leaving and re-entering would flash the main
/// screen in between.
pub fn clear_alternate_screen() {
let _ = execute!(
std::io::stdout(),
Clear(ClearType::All),
cursor::MoveTo(0, 0)
);
}
pub fn format_bytes(bytes: u64) -> String {
const UNITS: [&str; 5] = ["B", "KB", "MB", "GB", "TB"];
let mut value = bytes as f64;