feat: in-app update notice — checks GitHub releases on startup
Adds a one-shot update check that runs on app boot and pops up a small
"Update Available" window when the GitHub releases API reports a newer
tag than this build's `CARGO_PKG_VERSION`.
- `src/update_check.rs` — async wrapper around a blocking `ureq` GET to
`api.github.com/repos/HakanSeven12/H7CAD/releases/latest`, returns
`Some(version)` when a newer release exists, `None` otherwise (silent
on network / parse failure so a missing connection never blocks
launch).
- `H7CAD::boot` chains a `Task::perform` next to the main-window open
so the check runs in parallel with first-paint.
- New window plumbed like the other floating panels (page setup, about,
etc.): `update_notice_window`/`update_notice_version` state, title
entry, `OsWindowClosed` cleanup, and a `ui::update_notice::view_window`
modal that shows installed vs. latest version plus "Later" /
"Open Release Page" buttons. Open routes through `open::that` to the
releases URL and dismisses the modal.
Dependency: adds `ureq = { version = "2", default-features = false,
features = ["tls"] }` — the smallest blocking HTTP client + TLS combo
that doesn't drag in an async runtime.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
de37ee04bf
commit
86f5ba7b62
10 changed files with 388 additions and 2 deletions
101
Cargo.lock
generated
101
Cargo.lock
generated
|
|
@ -19,6 +19,7 @@ dependencies = [
|
||||||
"truck-meshalgo",
|
"truck-meshalgo",
|
||||||
"truck-modeling",
|
"truck-modeling",
|
||||||
"truck-polymesh",
|
"truck-polymesh",
|
||||||
|
"ureq",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -4527,6 +4528,20 @@ dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.17.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"libc",
|
||||||
|
"untrusted",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "robust"
|
name = "robust"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
|
@ -4607,6 +4622,41 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.23.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"rustls-webpki",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pki-types"
|
||||||
|
version = "1.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-webpki"
|
||||||
|
version = "0.103.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
|
|
@ -5126,6 +5176,12 @@ dependencies = [
|
||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "svg_fmt"
|
name = "svg_fmt"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
|
@ -5724,6 +5780,27 @@ version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ureq"
|
||||||
|
version = "2.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"url",
|
||||||
|
"webpki-roots 0.26.11",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.8"
|
version = "2.5.8"
|
||||||
|
|
@ -6108,6 +6185,24 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "0.26.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
|
||||||
|
dependencies = [
|
||||||
|
"webpki-roots 1.0.7",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "weezl"
|
name = "weezl"
|
||||||
version = "0.1.12"
|
version = "0.1.12"
|
||||||
|
|
@ -7033,6 +7128,12 @@ dependencies = [
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize"
|
||||||
|
version = "1.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerotrie"
|
name = "zerotrie"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ printpdf = "0.9.1"
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||||
rayon = "1"
|
rayon = "1"
|
||||||
|
ureq = { version = "2", default-features = false, features = ["tls"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
windows-sys = { version = "0.59", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
|
windows-sys = { version = "0.59", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,12 @@ pub(super) struct H7CAD {
|
||||||
dimstyle_window: Option<window::Id>,
|
dimstyle_window: Option<window::Id>,
|
||||||
shortcuts_window: Option<window::Id>,
|
shortcuts_window: Option<window::Id>,
|
||||||
about_window: Option<window::Id>,
|
about_window: Option<window::Id>,
|
||||||
|
/// New-release notification window — opened on startup when the
|
||||||
|
/// GitHub releases API reports a newer version than this build.
|
||||||
|
update_notice_window: Option<window::Id>,
|
||||||
|
/// Tag of the latest available release (without the leading "v"),
|
||||||
|
/// e.g. `"0.3.0"`. `None` when up-to-date or check hasn't returned.
|
||||||
|
update_notice_version: Option<String>,
|
||||||
/// In-memory clipboard: cloned entities waiting to be pasted.
|
/// In-memory clipboard: cloned entities waiting to be pasted.
|
||||||
clipboard: Vec<acadrust::EntityType>,
|
clipboard: Vec<acadrust::EntityType>,
|
||||||
/// Centroid of the clipboard entities (XZ plane, Y-up).
|
/// Centroid of the clipboard entities (XZ plane, Y-up).
|
||||||
|
|
@ -529,6 +535,14 @@ pub enum Message {
|
||||||
OsWindowClosed(window::Id),
|
OsWindowClosed(window::Id),
|
||||||
/// No-op — used as a fallback when a TabEvent has no host mapping.
|
/// No-op — used as a fallback when a TabEvent has no host mapping.
|
||||||
Noop,
|
Noop,
|
||||||
|
/// GitHub releases API returned a result. `Some(version)` means a
|
||||||
|
/// newer release exists; we open the update-notice window.
|
||||||
|
UpdateCheckResult(Option<String>),
|
||||||
|
/// User dismissed the update-notice window.
|
||||||
|
UpdateNoticeClose,
|
||||||
|
/// User clicked the "Open release page" button — opens the GitHub
|
||||||
|
/// release URL in the OS default browser and closes the notice.
|
||||||
|
UpdateNoticeOpenRelease,
|
||||||
// ── Page Setup ────────────────────────────────────────────────────────
|
// ── Page Setup ────────────────────────────────────────────────────────
|
||||||
/// Open the Page Setup panel for the current layout.
|
/// Open the Page Setup panel for the current layout.
|
||||||
PageSetupOpen,
|
PageSetupOpen,
|
||||||
|
|
@ -713,6 +727,8 @@ impl H7CAD {
|
||||||
dimstyle_window: None,
|
dimstyle_window: None,
|
||||||
shortcuts_window: None,
|
shortcuts_window: None,
|
||||||
about_window: None,
|
about_window: None,
|
||||||
|
update_notice_window: None,
|
||||||
|
update_notice_version: None,
|
||||||
clipboard: Vec::new(),
|
clipboard: Vec::new(),
|
||||||
clipboard_centroid: glam::Vec3::ZERO,
|
clipboard_centroid: glam::Vec3::ZERO,
|
||||||
layout_context_menu: None,
|
layout_context_menu: None,
|
||||||
|
|
@ -810,8 +826,12 @@ impl H7CAD {
|
||||||
});
|
});
|
||||||
let mut s = state;
|
let mut s = state;
|
||||||
s.main_window = Some(id);
|
s.main_window = Some(id);
|
||||||
let task = open_task.map(|_| Message::Noop);
|
let open_main = open_task.map(|_| Message::Noop);
|
||||||
(s, task)
|
let check_update = Task::perform(
|
||||||
|
crate::update_check::check_for_update(),
|
||||||
|
Message::UpdateCheckResult,
|
||||||
|
);
|
||||||
|
(s, Task::batch([open_main, check_update]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -851,6 +871,9 @@ pub fn run() -> iced::Result {
|
||||||
if Some(window_id) == state.about_window {
|
if Some(window_id) == state.about_window {
|
||||||
return "About H7CAD".into();
|
return "About H7CAD".into();
|
||||||
}
|
}
|
||||||
|
if Some(window_id) == state.update_notice_window {
|
||||||
|
return "Update Available".into();
|
||||||
|
}
|
||||||
if Some(window_id) == state.unsaved_dialog_window {
|
if Some(window_id) == state.unsaved_dialog_window {
|
||||||
return "Unsaved Changes".into();
|
return "Unsaved Changes".into();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -974,6 +974,9 @@ impl H7CAD {
|
||||||
if self.about_window == Some(id) {
|
if self.about_window == Some(id) {
|
||||||
self.about_window = None;
|
self.about_window = None;
|
||||||
}
|
}
|
||||||
|
if self.update_notice_window == Some(id) {
|
||||||
|
self.update_notice_window = None;
|
||||||
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3536,6 +3539,37 @@ impl H7CAD {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::UpdateCheckResult(latest) => {
|
||||||
|
let Some(version) = latest else {
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
self.update_notice_version = Some(version);
|
||||||
|
if let Some(id) = self.update_notice_window {
|
||||||
|
return window::gain_focus(id);
|
||||||
|
}
|
||||||
|
let (id, task) = window::open(window::Settings {
|
||||||
|
size: iced::Size::new(480.0, 240.0),
|
||||||
|
resizable: false,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
self.update_notice_window = Some(id);
|
||||||
|
task.map(|_| Message::Noop)
|
||||||
|
}
|
||||||
|
Message::UpdateNoticeClose => {
|
||||||
|
if let Some(id) = self.update_notice_window.take() {
|
||||||
|
window::close(id)
|
||||||
|
} else {
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::UpdateNoticeOpenRelease => {
|
||||||
|
let _ = open::that(crate::update_check::RELEASES_PAGE);
|
||||||
|
if let Some(id) = self.update_notice_window.take() {
|
||||||
|
window::close(id)
|
||||||
|
} else {
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::PageSetupWidthEdit(s) => {
|
Message::PageSetupWidthEdit(s) => {
|
||||||
self.page_setup_w = s;
|
self.page_setup_w = s;
|
||||||
Task::none()
|
Task::none()
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,13 @@ impl H7CAD {
|
||||||
if Some(window_id) == self.about_window {
|
if Some(window_id) == self.about_window {
|
||||||
return crate::ui::about::view_window();
|
return crate::ui::about::view_window();
|
||||||
}
|
}
|
||||||
|
if Some(window_id) == self.update_notice_window {
|
||||||
|
let latest = self
|
||||||
|
.update_notice_version
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("?");
|
||||||
|
return crate::ui::update_notice::view_window(latest);
|
||||||
|
}
|
||||||
if Some(window_id) == self.unsaved_dialog_window {
|
if Some(window_id) == self.unsaved_dialog_window {
|
||||||
let tab_name = match &self.pending_close {
|
let tab_name = match &self.pending_close {
|
||||||
Some(super::PendingClose::Tab(idx)) => self
|
Some(super::PendingClose::Tab(idx)) => self
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,4 @@ pub mod patterns;
|
||||||
pub mod scene;
|
pub mod scene;
|
||||||
pub mod snap;
|
pub mod snap;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
pub mod update_check;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ mod patterns;
|
||||||
mod scene;
|
mod scene;
|
||||||
mod snap;
|
mod snap;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
mod update_check;
|
||||||
|
|
||||||
fn main() -> iced::Result {
|
fn main() -> iced::Result {
|
||||||
app::run()
|
app::run()
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ pub const ROW_H: f32 = 26.0;
|
||||||
|
|
||||||
pub mod about;
|
pub mod about;
|
||||||
pub mod app_menu;
|
pub mod app_menu;
|
||||||
|
pub mod update_notice;
|
||||||
pub mod command_line;
|
pub mod command_line;
|
||||||
pub mod dimstyle;
|
pub mod dimstyle;
|
||||||
pub mod layers;
|
pub mod layers;
|
||||||
|
|
|
||||||
167
src/ui/update_notice.rs
Normal file
167
src/ui/update_notice.rs
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
use crate::app::Message;
|
||||||
|
use iced::widget::{button, column, container, row, text, Space};
|
||||||
|
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||||
|
|
||||||
|
const BG: Color = Color {
|
||||||
|
r: 0.15,
|
||||||
|
g: 0.15,
|
||||||
|
b: 0.15,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const PANEL: Color = Color {
|
||||||
|
r: 0.12,
|
||||||
|
g: 0.12,
|
||||||
|
b: 0.12,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const BORDER: Color = Color {
|
||||||
|
r: 0.30,
|
||||||
|
g: 0.30,
|
||||||
|
b: 0.30,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const DIM: Color = Color {
|
||||||
|
r: 0.55,
|
||||||
|
g: 0.55,
|
||||||
|
b: 0.55,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const ACCENT: Color = Color {
|
||||||
|
r: 0.25,
|
||||||
|
g: 0.50,
|
||||||
|
b: 0.85,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const WHITE: Color = Color {
|
||||||
|
r: 0.92,
|
||||||
|
g: 0.92,
|
||||||
|
b: 0.92,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn info_row<'a>(label: &'static str, value: String) -> Element<'a, Message> {
|
||||||
|
row![
|
||||||
|
text(label).size(11).color(DIM).width(100),
|
||||||
|
text(value).size(11).color(WHITE),
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center)
|
||||||
|
.padding([3, 0])
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_window<'a>(latest: &'a str) -> Element<'a, Message> {
|
||||||
|
let header = container(
|
||||||
|
column![
|
||||||
|
text("New Release Available").size(20).color(ACCENT),
|
||||||
|
text("A newer H7CAD version is published on GitHub.")
|
||||||
|
.size(11)
|
||||||
|
.color(DIM),
|
||||||
|
]
|
||||||
|
.spacing(4)
|
||||||
|
.align_x(iced::Center),
|
||||||
|
)
|
||||||
|
.width(Fill)
|
||||||
|
.padding(iced::Padding {
|
||||||
|
top: 16.0,
|
||||||
|
right: 0.0,
|
||||||
|
bottom: 14.0,
|
||||||
|
left: 0.0,
|
||||||
|
})
|
||||||
|
.align_x(iced::Center);
|
||||||
|
|
||||||
|
let info_block = container(
|
||||||
|
column![
|
||||||
|
info_row("Installed", format!("v{}", env!("CARGO_PKG_VERSION"))),
|
||||||
|
info_row("Latest", format!("v{}", latest)),
|
||||||
|
]
|
||||||
|
.spacing(2)
|
||||||
|
.padding([12, 16]),
|
||||||
|
)
|
||||||
|
.width(Fill)
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(PANEL)),
|
||||||
|
border: Border {
|
||||||
|
color: BORDER,
|
||||||
|
width: 1.0,
|
||||||
|
radius: 4.0.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let later_btn = button(text("Later").size(11))
|
||||||
|
.on_press(Message::UpdateNoticeClose)
|
||||||
|
.style(|_: &Theme, st| button::Style {
|
||||||
|
background: Some(Background::Color(match st {
|
||||||
|
button::Status::Hovered | button::Status::Pressed => Color {
|
||||||
|
r: 0.22,
|
||||||
|
g: 0.22,
|
||||||
|
b: 0.22,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
_ => Color {
|
||||||
|
r: 0.18,
|
||||||
|
g: 0.18,
|
||||||
|
b: 0.18,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
text_color: WHITE,
|
||||||
|
border: Border {
|
||||||
|
color: BORDER,
|
||||||
|
width: 1.0,
|
||||||
|
radius: 4.0.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.padding([6, 16]);
|
||||||
|
|
||||||
|
let open_btn = button(text("Open Release Page").size(11))
|
||||||
|
.on_press(Message::UpdateNoticeOpenRelease)
|
||||||
|
.style(|_: &Theme, st| button::Style {
|
||||||
|
background: Some(Background::Color(match st {
|
||||||
|
button::Status::Hovered | button::Status::Pressed => Color {
|
||||||
|
r: 0.20,
|
||||||
|
g: 0.42,
|
||||||
|
b: 0.72,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
_ => ACCENT,
|
||||||
|
})),
|
||||||
|
text_color: WHITE,
|
||||||
|
border: Border {
|
||||||
|
radius: 4.0.into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.padding([6, 16]);
|
||||||
|
|
||||||
|
let footer = row![Space::new().width(Fill), later_btn, open_btn]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center)
|
||||||
|
.padding(iced::Padding {
|
||||||
|
top: 14.0,
|
||||||
|
right: 0.0,
|
||||||
|
bottom: 0.0,
|
||||||
|
left: 0.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
container(
|
||||||
|
column![header, info_block, footer]
|
||||||
|
.spacing(0)
|
||||||
|
.padding(iced::Padding {
|
||||||
|
top: 0.0,
|
||||||
|
right: 20.0,
|
||||||
|
bottom: 20.0,
|
||||||
|
left: 20.0,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(BG)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
50
src/update_check.rs
Normal file
50
src/update_check.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
// One-shot update check.
|
||||||
|
//
|
||||||
|
// `check_for_update()` runs on a background thread (joined inside an
|
||||||
|
// async wrapper, mirroring how the rest of `crate::io` wraps blocking
|
||||||
|
// work for iced's `Task::perform`). It hits the GitHub releases API and
|
||||||
|
// returns `Some(latest_version)` when a newer release is available, or
|
||||||
|
// `None` when up to date / on network failure / on parse error.
|
||||||
|
|
||||||
|
const RELEASES_API: &str =
|
||||||
|
"https://api.github.com/repos/HakanSeven12/H7CAD/releases/latest";
|
||||||
|
pub const RELEASES_PAGE: &str =
|
||||||
|
"https://github.com/HakanSeven12/H7CAD/releases/latest";
|
||||||
|
|
||||||
|
pub async fn check_for_update() -> Option<String> {
|
||||||
|
std::thread::spawn(fetch_latest_if_outdated)
|
||||||
|
.join()
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_latest_if_outdated() -> Option<String> {
|
||||||
|
let body = ureq::AgentBuilder::new()
|
||||||
|
.timeout(std::time::Duration::from_secs(5))
|
||||||
|
.build()
|
||||||
|
.get(RELEASES_API)
|
||||||
|
.set("User-Agent", concat!("h7cad/", env!("CARGO_PKG_VERSION")))
|
||||||
|
.set("Accept", "application/vnd.github+json")
|
||||||
|
.call()
|
||||||
|
.ok()?
|
||||||
|
.into_string()
|
||||||
|
.ok()?;
|
||||||
|
let latest = extract_tag(&body)?
|
||||||
|
.trim_start_matches('v')
|
||||||
|
.to_string();
|
||||||
|
if latest != env!("CARGO_PKG_VERSION") {
|
||||||
|
Some(latest)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_tag(body: &str) -> Option<String> {
|
||||||
|
// Minimal substring extraction so we don't pull serde_json just for
|
||||||
|
// one field. The GitHub releases endpoint always returns
|
||||||
|
// `"tag_name":"v…"` near the top of the JSON object.
|
||||||
|
const KEY: &str = "\"tag_name\":\"";
|
||||||
|
let start = body.find(KEY)? + KEY.len();
|
||||||
|
let end = body[start..].find('"')? + start;
|
||||||
|
Some(body[start..end].to_string())
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue