use gpui::{
point, px, size, App, AppContext as _, Bounds, SharedString, TitlebarOptions, WindowBounds,
WindowHandle, WindowOptions,
};
use super::{
is_installing, Release, UpdateCheck, UpdateNotice, UpdateNoticeEvent, UpdateOutcome,
UpdatePrompt, UpdatePromptEvent, Updater,
};
const WIDTH: f32 = 460.0;
const PROMPT_HEIGHT: f32 = 300.0;
const NOTICE_HEIGHT: f32 = 180.0;
struct Started;
impl gpui::Global for Started {}
#[derive(Default)]
struct Notified(String);
impl gpui::Global for Notified {}
pub fn start(updater: Updater, cx: &mut App) {
if cx.try_global::<Started>().is_some() {
return;
}
cx.set_global(Started);
let every = updater.poll();
let executor = cx.background_executor().clone();
cx.spawn(async move |cx| loop {
let config = updater.config().clone();
let found = executor.spawn(async move { config.check() }).await;
let _ = cx.update(|cx| apply(&updater, found, false, cx));
executor.timer(every).await;
})
.detach();
}
pub fn check_now(updater: Updater, cx: &mut App) {
let executor = cx.background_executor().clone();
cx.spawn(async move |cx| {
let config = updater.config().clone();
let found = executor.spawn(async move { config.check() }).await;
let _ = cx.update(|cx| apply(&updater, found, true, cx));
})
.detach();
}
fn apply(updater: &Updater, found: Result<UpdateCheck, String>, manual: bool, cx: &mut App) {
match found {
Ok(UpdateCheck::Ready(_)) if is_installing(cx) => {}
Ok(UpdateCheck::Ready(release)) => {
let seen = cx
.try_global::<Notified>()
.is_some_and(|n| n.0 == release.version);
if manual || !seen {
cx.set_global(Notified(release.version.clone()));
open(updater.clone(), release, cx);
}
}
Ok(UpdateCheck::Pending(version)) => {
if manual {
open_notice(updater.clone(), UpdateOutcome::Pending(version), cx);
}
}
Ok(UpdateCheck::UpToDate) => {
if manual {
open_notice(updater.clone(), UpdateOutcome::UpToDate, cx);
}
}
Err(e) => {
if manual {
open_notice(updater.clone(), UpdateOutcome::Failed(e), cx);
}
}
}
}
pub fn open(
updater: Updater,
release: Release,
cx: &mut App,
) -> Option<WindowHandle<UpdatePrompt>> {
let title = updater.title().clone();
let prompt = cx.new(|cx| UpdatePrompt::new(updater, release, cx).window_root(true));
let handle = window(WIDTH, PROMPT_HEIGHT, title, prompt.clone(), cx)?;
cx.subscribe(&prompt, move |_prompt, event: &UpdatePromptEvent, cx| {
if matches!(event, UpdatePromptEvent::Dismissed) {
let _ = handle.update(cx, |_view, window, _cx| window.remove_window());
}
})
.detach();
Some(handle)
}
pub fn open_notice(
updater: Updater,
outcome: UpdateOutcome,
cx: &mut App,
) -> Option<WindowHandle<UpdateNotice>> {
let title = updater.title().clone();
let notice = cx.new(|cx| UpdateNotice::new(updater, outcome, cx).window_root(true));
let handle = window(WIDTH, NOTICE_HEIGHT, title, notice.clone(), cx)?;
cx.subscribe(¬ice, move |_notice, event: &UpdateNoticeEvent, cx| {
let UpdateNoticeEvent::Dismissed = event;
let _ = handle.update(cx, |_view, window, _cx| window.remove_window());
})
.detach();
Some(handle)
}
fn window<V: gpui::Render>(
width: f32,
height: f32,
title: SharedString,
view: gpui::Entity<V>,
cx: &mut App,
) -> Option<WindowHandle<V>> {
let bounds = Bounds::centered(None, size(px(width), px(height)), cx);
let handle = cx
.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
is_resizable: false,
titlebar: Some(TitlebarOptions {
title: Some(title.clone()),
appears_transparent: true,
traffic_light_position: Some(point(px(12.0), px(12.0))),
}),
..Default::default()
},
|window, _cx| {
window.set_window_title(&title);
view
},
)
.ok()?;
handle
.update(cx, |_view, window, _cx| window.activate_window())
.ok();
Some(handle)
}