This commit is contained in:
2026-08-03 08:52:37 +07:00
parent a1e1133bdc
commit c3b5bbc1b2
5 changed files with 82 additions and 8 deletions

1
Cargo.lock generated
View File

@@ -9117,6 +9117,7 @@ name = "workspace"
version = "1.0.0-beta5" version = "1.0.0-beta5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"auto_update",
"browser-signer-proxy", "browser-signer-proxy",
"chat", "chat",
"chat_ui", "chat_ui",

View File

@@ -1,8 +1,8 @@
#![cfg(not(target_arch = "wasm32"))] #![cfg(not(target_arch = "wasm32"))]
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Window}; use gpui::{App, AppContext, Context, Entity, Global, SharedString, Subscription, Window};
use gpui_updater::{EngineConfig, GitHubSource, UpdateStatus, Updater, Version}; use gpui_updater::{EngineConfig, GitHubSource, UpdateStatus, Updater, Version};
use instant::Duration; use instant::{Duration, Instant};
const COOP_UPDATE_EXPLANATION: &str = "COOP_UPDATE_EXPLANATION"; const COOP_UPDATE_EXPLANATION: &str = "COOP_UPDATE_EXPLANATION";
@@ -52,6 +52,8 @@ pub struct AutoUpdater {
pub version: Version, pub version: Version,
/// Keeps the observer subscription alive. /// Keeps the observer subscription alive.
_subscription: Subscription, _subscription: Subscription,
/// When the last error was recorded, so we can reset to idle after 5s.
error_time: Option<Instant>,
} }
impl AutoUpdater { impl AutoUpdater {
@@ -83,11 +85,24 @@ impl AutoUpdater {
// When an update becomes available, automatically download and install it. // When an update becomes available, automatically download and install it.
let subscription = cx.observe(&updater, |this: &mut AutoUpdater, _updater, cx| { let subscription = cx.observe(&updater, |this: &mut AutoUpdater, _updater, cx| {
let status = this.updater.read(cx).status().clone(); let status = this.updater.read(cx).status().clone();
if matches!(status, UpdateStatus::Available(_)) { if matches!(status, UpdateStatus::Available(_)) {
this.updater.update(cx, |updater, cx| { this.updater.update(cx, |updater, cx| {
updater.download_and_install(cx); updater.download_and_install(cx);
}); });
} }
if matches!(status, UpdateStatus::Errored(_)) {
this.error_time = Some(Instant::now());
cx.spawn(async move |this, cx| {
cx.background_executor().timer(Duration::from_secs(5)).await;
this.update(cx, |_this, cx| cx.notify()).ok();
})
.detach();
} else {
this.error_time = None;
}
cx.notify(); cx.notify();
}); });
@@ -111,6 +126,55 @@ impl AutoUpdater {
updater, updater,
version, version,
_subscription: subscription, _subscription: subscription,
error_time: None,
}
}
pub fn idle(&self, cx: &App) -> bool {
let status = self.updater.read(cx).status();
if status == &UpdateStatus::Idle {
return true;
}
if matches!(status, UpdateStatus::Errored(_))
&& self
.error_time
.is_some_and(|t| t.elapsed() >= Duration::from_secs(5))
{
return true;
}
false
}
pub fn status(&self, cx: &App) -> SharedString {
let status = self.updater.read(cx).status();
match status {
UpdateStatus::Idle => "Up to date".into(),
UpdateStatus::Checking => "Checking for updates…".into(),
UpdateStatus::UpToDate => "Up to date".into(),
UpdateStatus::Available(version) => format!("Version {version} available").into(),
UpdateStatus::Downloading { downloaded, total } => {
let total_mb = total.map(|t| t as f64 / 1_048_576.0);
let downloaded_mb = *downloaded as f64 / 1_048_576.0;
match total_mb {
Some(t) => format!("Downloading {downloaded_mb:.1} / {t:.1} MB").into(),
None => format!("Downloading {downloaded_mb:.1} MB").into(),
}
}
UpdateStatus::Installing => "Installing update…".into(),
UpdateStatus::Staged(version) => {
format!("Version {version} ready — restart to apply").into()
}
UpdateStatus::Errored(msg) => {
if self
.error_time
.is_some_and(|t| t.elapsed() >= Duration::from_secs(5))
{
"Up to date".into()
} else {
format!("Update failed: {msg}").into()
}
}
} }
} }
} }

View File

@@ -164,12 +164,8 @@
</head> </head>
<body> <body>
<div class="card"> <div class="card">
<div class="logo">
<div class="logo__mark">C</div>
<span class="logo__text">Coop</span>
</div>
<h1 class="heading">Web Signer</h1> <h1 class="heading">Web Signer</h1>
<p class="subtitle"> <p class="subtitle">
This page connects the app to your Nostr Web Signer extension so you can sign in and use Coop securely. This page connects the app to your Nostr Web Signer extension so you can sign in and use Coop securely.
</p> </p>

View File

@@ -14,6 +14,7 @@ chat = { path = "../chat" }
chat_ui = { path = "../chat_ui" } chat_ui = { path = "../chat_ui" }
settings = { path = "../settings" } settings = { path = "../settings" }
person = { path = "../person" } person = { path = "../person" }
auto_update = { path = "../auto_update" }
gpui.workspace = true gpui.workspace = true
nostr-sdk.workspace = true nostr-sdk.workspace = true

View File

@@ -2,6 +2,7 @@ use std::sync::Arc;
use ::settings::AppSettings; use ::settings::AppSettings;
use anyhow::Error; use anyhow::Error;
use auto_update::AutoUpdater;
use chat::{ChatEvent, ChatRegistry}; use chat::{ChatEvent, ChatRegistry};
use common::{CoopImageCache, download_dir}; use common::{CoopImageCache, download_dir};
use device::{DeviceEvent, DeviceRegistry}; use device::{DeviceEvent, DeviceRegistry};
@@ -379,7 +380,12 @@ impl Workspace {
self.import_encryption(window, cx); self.import_encryption(window, cx);
} }
Command::Update => { Command::Update => {
// let auto_updater = AutoUpdater::global(cx);
auto_updater.update(cx, |this, cx| {
this.updater.update(cx, |updater, cx| {
updater.check(cx);
});
});
} }
} }
} }
@@ -604,6 +610,7 @@ impl Workspace {
} }
fn titlebar_right(&mut self, cx: &mut Context<Self>) -> impl IntoElement { fn titlebar_right(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let updater = AutoUpdater::global(cx);
let chat = ChatRegistry::global(cx); let chat = ChatRegistry::global(cx);
let nip4e_enabled = AppSettings::get_nip4e(cx); let nip4e_enabled = AppSettings::get_nip4e(cx);
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
@@ -615,10 +622,15 @@ impl Workspace {
let persons = PersonRegistry::global(cx); let persons = PersonRegistry::global(cx);
let profile = persons.read(cx).get(&public_key, cx); let profile = persons.read(cx).get(&public_key, cx);
let announcement = profile.announcement(); let announcement = profile.announcement();
let updater_idle = updater.read(cx).idle(cx);
h_flex() h_flex()
.when(!cx.theme().platform.is_mac(), |this| this.pr_2()) .when(!cx.theme().platform.is_mac(), |this| this.pr_2())
.gap_2() .gap_2()
.when(!updater_idle, |this| {
let status = updater.read(cx).status(cx);
this.child(div().text_xs().italic().child(status))
})
.when(nip4e_enabled, |this| { .when(nip4e_enabled, |this| {
this.child( this.child(
Button::new("key") Button::new("key")