diff --git a/src/setup.rs b/src/setup.rs index 35e37ae..49c4c1c 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -3,7 +3,7 @@ use anyhow::Result; use iced::widget::{ button, column, container, pick_list, progress_bar, row, scrollable, text, text_input, Column, }; -use iced::{Element, Length, Subscription, Task, Theme}; +use iced::{Color, Element, Length, Subscription, Task, Theme}; use std::ffi::OsString; use std::io::{BufRead, BufReader, Read, Write}; use std::path::{Path, PathBuf}; @@ -99,8 +99,8 @@ impl State { fn new_install(config: Config, launcher: Launcher) -> Self { let status = format!( - "Paste an installer URL or a local .exe path. It will install into {}.", - launcher.prefix_dir.display() + "Paste an installer URL or a local .exe path for {}.", + launcher.display ); let template_options = config::presets() .iter() @@ -171,30 +171,21 @@ fn update(state: &mut State, message: Message) -> Task { preset.prefix_dir = PathBuf::from(&prefix); // If we know the official installer URL, pre-fill source and - // kick off the download automatically so the user doesn't have - // to find or paste anything. + // let the user confirm before downloading. if let Some(url) = preset.installer_url.clone() { - state.source = url.clone(); - state.stage = Stage::Busy; + state.source = url; + state.stage = Stage::Idle; state.status = format!( - "Found official installer — downloading to {}…", - preset.prefix_dir.display() + "Ready to download the official {} installer. Press Download → to begin.", + preset.display ); - if let Ok(mut p) = state.download.lock() { - *p = DownloadProgress::default(); - } - let name = preset.name.clone(); - let progress = state.download.clone(); state.launcher = Some(preset); - return Task::perform( - async_blocking(move || download_blocking(&url, &name, progress)), - Message::PrepareDone, - ); + return Task::none(); } state.status = format!( - "Paste an installer URL or a local .exe path. It will install into {}.", - preset.prefix_dir.display() + "Paste an installer URL or a local .exe path for {}.", + preset.display ); state.launcher = Some(preset); state.stage = Stage::Idle; @@ -228,7 +219,12 @@ fn update(state: &mut State, message: Message) -> Task { if path.is_file() { state.installer = Some(path.clone()); state.stage = Stage::Ready; - state.status = format!("Ready: {}", path.display()); + let display = state + .launcher + .as_ref() + .map(|l| l.display.as_str()) + .unwrap_or("installer"); + state.status = format!("Local installer ready for {}. Press Install → to continue.", display); return Task::none(); } if !src.starts_with("http://") && !src.starts_with("https://") { @@ -236,7 +232,12 @@ fn update(state: &mut State, message: Message) -> Task { return Task::none(); } state.stage = Stage::Busy; - state.status = format!("Downloading {src} …"); + let display_name = state + .launcher + .as_ref() + .map(|l| l.display.clone()) + .unwrap_or_else(|| "installer".to_string()); + state.status = format!("Downloading {} installer…", display_name); if let Ok(mut p) = state.download.lock() { *p = DownloadProgress::default(); } @@ -256,7 +257,12 @@ fn update(state: &mut State, message: Message) -> Task { state.downloaded_temp = Some(path.clone()); state.installer = Some(path.clone()); state.stage = Stage::Ready; - state.status = format!("Downloaded to {}", path.display()); + let display_name = state + .launcher + .as_ref() + .map(|l| l.display.as_str()) + .unwrap_or("installer"); + state.status = format!("Download complete. Press Install → to run the {} installer.", display_name); Task::none() } Message::PrepareDone(Err(e)) => { @@ -309,11 +315,9 @@ fn update(state: &mut State, message: Message) -> Task { "✓ Installer finished (exit {code}). {} is ready.", launcher.display, ), - Ok(code) => format!( - "umu-run exited {code} but the expected exe is not at {}.\n\ - Check the installer's destination path, or edit exe_path in config.", - exe.display(), - ), + Ok(_) => "Installation finished but the launcher wasn't found where expected. \ + Check the log below for details." + .to_string(), Err(e) => format!("Install failed: {e}"), }; Task::none() @@ -430,7 +434,7 @@ fn view_install(state: &State) -> Element<'_, Message> { let source_label = if launcher.installer_url.is_some() && state.source == launcher.installer_url.as_deref().unwrap_or("") { - "Official installer URL (auto-filled — or paste your own):" + "✓ Official installer detected — or paste a custom URL:" } else { "Installer URL or local .exe path:" }; @@ -439,18 +443,37 @@ fn view_install(state: &State) -> Element<'_, Message> { .on_input(Message::SourceChanged) .padding(8); - let prepare_enabled = matches!(state.stage, Stage::Idle | Stage::Ready | Stage::Finished); - let install_enabled = matches!(state.stage, Stage::Ready); let finished = matches!(state.stage, Stage::Finished); - let install_success = finished - && launcher - .full_exe_path() - .exists(); + let install_success = finished && launcher.full_exe_path().exists(); - let prepare_btn = button(text("Download / Prepare")) - .on_press_maybe(prepare_enabled.then_some(Message::PreparePressed)); - let install_btn = button(text("Run installer")) - .on_press_maybe(install_enabled.then_some(Message::InstallPressed)); + // Single context-aware action button (changes 3 & 4) + let action_btn: Option> = match state.stage { + Stage::Idle => { + let enabled = !state.source.trim().is_empty(); + Some( + button(text("Download →")) + .on_press_maybe(enabled.then_some(Message::PreparePressed)) + .into(), + ) + } + Stage::Busy => Some( + button(text("Downloading…")) + .on_press_maybe(None::) + .into(), + ), + Stage::Ready => Some( + button(text("Install →")) + .on_press(Message::InstallPressed) + .into(), + ), + Stage::Installing => Some( + button(text("Installing…")) + .on_press_maybe(None::) + .into(), + ), + Stage::Finished => None, + Stage::Picking => None, + }; let status = text(state.status.clone()); @@ -496,7 +519,7 @@ fn view_install(state: &State) -> Element<'_, Message> { let close_btn = button(text("Close").size(13)) .on_press(Message::Close) .style(button::secondary); - let launch_btn = button(text("Launch now").size(13)) + let launch_btn = button(text("Open launcher").size(13)) .on_press_maybe(install_success.then_some(Message::LaunchNow)) .style(button::primary); row![close_btn, launch_btn].spacing(10).into() @@ -504,21 +527,37 @@ fn view_install(state: &State) -> Element<'_, Message> { text("").into() }; - let body = column![ + // Success banner (change 7) + let success_banner: Element = if finished && install_success { + text(format!("✓ {} installed successfully!", launcher.display)) + .size(14) + .color(Color::from_rgb(0.4, 0.9, 0.4)) + .into() + } else { + text("").into() + }; + + let mut body = column![ header, prefix, expected, text(source_label).size(12), input, - row![prepare_btn, install_btn].spacing(12), - progress_row, - status, - finished_row, - log_pane, ] .spacing(12) .padding(20); + if let Some(btn) = action_btn { + body = body.push(btn); + } + + let body = body + .push(progress_row) + .push(status) + .push(success_banner) + .push(finished_row) + .push(log_pane); + container(body).into() }