From 2c83ae27361188eb5a8adbbd735d908cbfe0394f Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sun, 6 Sep 2026 11:44:38 +0300 Subject: [PATCH] Show donation request once per app version --- locales/ar-SA/opencadstudio.ftl | 6 ++ locales/bg-BG/opencadstudio.ftl | 6 ++ locales/cs-CZ/opencadstudio.ftl | 6 ++ locales/de-DE/opencadstudio.ftl | 6 ++ locales/el-GR/opencadstudio.ftl | 6 ++ locales/en-US/opencadstudio.ftl | 6 ++ locales/es-ES/opencadstudio.ftl | 6 ++ locales/fi-FI/opencadstudio.ftl | 6 ++ locales/fr-FR/opencadstudio.ftl | 6 ++ locales/hi-IN/opencadstudio.ftl | 6 ++ locales/hu-HU/opencadstudio.ftl | 6 ++ locales/it-IT/opencadstudio.ftl | 6 ++ locales/ja-JP/opencadstudio.ftl | 6 ++ locales/ko-KR/opencadstudio.ftl | 6 ++ locales/nl-NL/opencadstudio.ftl | 6 ++ locales/pl-PL/opencadstudio.ftl | 6 ++ locales/pt-BR/opencadstudio.ftl | 6 ++ locales/ru-RU/opencadstudio.ftl | 6 ++ locales/tr-TR/opencadstudio.ftl | 6 ++ locales/zh-CN/opencadstudio.ftl | 6 ++ locales/zh-TW/opencadstudio.ftl | 6 ++ src/app/mod.rs | 24 +++---- src/app/settings.rs | 5 +- src/app/startup.rs | 108 ++++++++++++++++++++++++++++++++ src/app/update/file.rs | 2 + src/app/update/mod.rs | 17 +++-- src/app/view/modal.rs | 45 +++++++++++-- src/i18n.rs | 29 +++++++++ 28 files changed, 332 insertions(+), 24 deletions(-) create mode 100644 src/app/startup.rs diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 72fdf28a3..65dab25c2 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = تخزين المتصفح .keep-recent-files = الاحتفاظ بالملفات الأخيرة +donation = + .title = طلب تبرع + .heading = تبرّع لـ Open CAD Studio + .body = يحتاج Open CAD Studio إلى مساعدتك! تموّل التبرعات أعمال التطوير والبنية التحتية الضرورية لاستمرار Open CAD Studio. + .decline = لا، شكرًا + modal = .about = حول .keyboard-shortcuts = اختصارات لوحة المفاتيح diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 9abf6e410..6ef10f4be 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Хранилище на браузъра .keep-recent-files = Пази скорошните файлове +donation = + .title = Призив за дарение + .heading = Дарете за Open CAD Studio + .body = Open CAD Studio се нуждае от вашата помощ! Даренията финансират разработката и инфраструктурата, важни за продължаващото съществуване на Open CAD Studio. + .decline = Не, благодаря + modal = .about = Относно .keyboard-shortcuts = Клавишни комбинации diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 77fcab83e..743f160ed 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Úložiště prohlížeče .keep-recent-files = Uchovávat nedávné soubory +donation = + .title = Žádost o dar + .heading = Přispějte na Open CAD Studio + .body = Open CAD Studio potřebuje vaši pomoc! Dary financují vývoj a infrastrukturu důležité pro další existenci Open CAD Studio. + .decline = Ne, děkuji + modal = .about = O aplikaci .keyboard-shortcuts = Klávesové zkratky diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 617953887..f304c633c 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Browserspeicher .keep-recent-files = Zuletzt verwendete Dateien behalten +donation = + .title = Spendenaufruf + .heading = Für Open CAD Studio spenden + .body = Open CAD Studio braucht Ihre Hilfe! Spenden finanzieren die Entwicklung und Infrastruktur, die für den Fortbestand von Open CAD Studio wichtig sind. + .decline = Nein, danke + modal = .about = Über .keyboard-shortcuts = Tastenkombinationen diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 6e57b1115..089fd162a 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Αποθηκευτικός χώρος προγράμματος περιήγησης .keep-recent-files = Διατήρηση πρόσφατων αρχείων +donation = + .title = Έκκληση για δωρεά + .heading = Κάντε δωρεά στο Open CAD Studio + .body = Το Open CAD Studio χρειάζεται τη βοήθειά σας! Οι δωρεές χρηματοδοτούν την ανάπτυξη και τις υποδομές που είναι σημαντικές για τη συνέχιση της ύπαρξης του Open CAD Studio. + .decline = Όχι, ευχαριστώ + modal = .about = Σχετικά .keyboard-shortcuts = Συντομεύσεις πληκτρολογίου diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index f18f782ad..8ce5c7f1c 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Browser storage .keep-recent-files = Keep recent files +donation = + .title = Request for Donation + .heading = Donate to Open CAD Studio + .body = Open CAD Studio needs your help! Donations fund development and infrastructure important for Open CAD Studio's continued existence. + .decline = No Thanks + modal = .about = About .keyboard-shortcuts = Keyboard Shortcuts diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 5ca5c20b7..279caab37 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Almacenamiento del navegador .keep-recent-files = Conservar archivos recientes +donation = + .title = Solicitud de donación + .heading = Dona a Open CAD Studio + .body = ¡Open CAD Studio necesita tu ayuda! Las donaciones financian el desarrollo y la infraestructura que son importantes para que Open CAD Studio siga existiendo. + .decline = No, gracias + modal = .about = Acerca de .keyboard-shortcuts = Métodos abreviados de teclado diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index b4a1d3d85..bbbc4a55e 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Selaimen tallennustila .keep-recent-files = Säilytä viimeisimmät tiedostot +donation = + .title = Lahjoituspyyntö + .heading = Lahjoita Open CAD Studiolle + .body = Open CAD Studio tarvitsee apuasi! Lahjoituksilla rahoitetaan kehitystyötä ja infrastruktuuria, jotka ovat tärkeitä Open CAD Studion toiminnan jatkumiselle. + .decline = Ei kiitos + modal = .about = Tietoja .keyboard-shortcuts = Pikanäppäimet diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 03e720fe6..dc5e62be8 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Stockage du navigateur .keep-recent-files = Conserver les fichiers récents +donation = + .title = Appel aux dons + .heading = Faire un don à Open CAD Studio + .body = Open CAD Studio a besoin de votre aide ! Les dons financent le développement et l’infrastructure essentiels à la pérennité d’Open CAD Studio. + .decline = Non merci + modal = .about = À propos .keyboard-shortcuts = Raccourcis clavier diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 9db75625e..90f3115df 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = ब्राउज़र संग्रहण .keep-recent-files = हाल की फ़ाइलें रखें +donation = + .title = दान का अनुरोध + .heading = Open CAD Studio को दान दें + .body = Open CAD Studio को आपकी मदद चाहिए! दान से विकास और बुनियादी ढाँचे का खर्च पूरा होता है, जो Open CAD Studio के अस्तित्व को बनाए रखने के लिए महत्वपूर्ण हैं। + .decline = नहीं, धन्यवाद + modal = .about = परिचय .keyboard-shortcuts = कीबोर्ड शॉर्टकट diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 0e2379ca5..fad3220a9 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Böngésző tárhelye .keep-recent-files = Legutóbbi fájlok megőrzése +donation = + .title = Adománykérés + .heading = Támogassa az Open CAD Studiót + .body = Az Open CAD Studiónak szüksége van a segítségére! Az adományok az Open CAD Studio fennmaradásához fontos fejlesztést és infrastruktúrát finanszírozzák. + .decline = Nem, köszönöm + modal = .about = Névjegy .keyboard-shortcuts = Billentyűparancsok diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 851de9c31..23998cd8d 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Memoria del browser .keep-recent-files = Conserva i file recenti +donation = + .title = Richiesta di donazione + .heading = Dona a Open CAD Studio + .body = Open CAD Studio ha bisogno del tuo aiuto! Le donazioni finanziano lo sviluppo e l’infrastruttura importanti per la continuità di Open CAD Studio. + .decline = No, grazie + modal = .about = Informazioni .keyboard-shortcuts = Scorciatoie da tastiera diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 9845cef2f..691ad892e 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = ブラウザーストレージ .keep-recent-files = 最近使ったファイルを保持 +donation = + .title = 寄付のお願い + .heading = Open CAD Studio に寄付する + .body = Open CAD Studio は皆さまの支援を必要としています!寄付は、Open CAD Studio の存続に重要な開発とインフラの資金に充てられます。 + .decline = 今はしない + modal = .about = バージョン情報 .keyboard-shortcuts = キーボードショートカット diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 56198eba5..93e6fad31 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = 브라우저 저장소 .keep-recent-files = 최근 파일 유지 +donation = + .title = 기부 요청 + .heading = Open CAD Studio에 기부하기 + .body = Open CAD Studio는 여러분의 도움이 필요합니다! 기부금은 Open CAD Studio의 지속적인 운영에 중요한 개발과 인프라 비용을 지원합니다. + .decline = 괜찮습니다 + modal = .about = 정보 .keyboard-shortcuts = 키보드 단축키 diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 9d9c6c014..026b5aa0c 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Browseropslag .keep-recent-files = Recente bestanden bewaren +donation = + .title = Verzoek om een donatie + .heading = Doneer aan Open CAD Studio + .body = Open CAD Studio heeft uw hulp nodig! Donaties financieren de ontwikkeling en infrastructuur die belangrijk zijn voor het voortbestaan van Open CAD Studio. + .decline = Nee, bedankt + modal = .about = Over .keyboard-shortcuts = Sneltoetsen diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 240a75776..c554b7d78 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Pamięć przeglądarki .keep-recent-files = Zachowuj ostatnie pliki +donation = + .title = Prośba o darowiznę + .heading = Wesprzyj Open CAD Studio + .body = Open CAD Studio potrzebuje Twojej pomocy! Darowizny finansują rozwój i infrastrukturę niezbędne do dalszego istnienia Open CAD Studio. + .decline = Nie, dziękuję + modal = .about = Informacje .keyboard-shortcuts = Skróty klawiaturowe diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 58b985bdc..084db7cf9 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Armazenamento do navegador .keep-recent-files = Manter arquivos recentes +donation = + .title = Pedido de doação + .heading = Doe para o Open CAD Studio + .body = O Open CAD Studio precisa da sua ajuda! As doações financiam o desenvolvimento e a infraestrutura importantes para a continuidade do Open CAD Studio. + .decline = Não, obrigado + modal = .about = Sobre .keyboard-shortcuts = Atalhos de teclado diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 84eebc875..4281e6476 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Хранилище браузера .keep-recent-files = Хранить недавние файлы +donation = + .title = Просьба о пожертвовании + .heading = Поддержите Open CAD Studio + .body = Open CAD Studio нужна ваша помощь! Пожертвования финансируют разработку и инфраструктуру, важные для дальнейшего существования Open CAD Studio. + .decline = Нет, спасибо + modal = .about = О программе .keyboard-shortcuts = Настройки клавиатуры diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 2a7aa4118..fb11eee04 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = Tarayıcı depolama alanı .keep-recent-files = Son dosyaları sakla +donation = + .title = Bağış Talebi + .heading = Open CAD Studio'ya Bağış Yapın + .body = Open CAD Studio'nun yardımınıza ihtiyacı var! Bağışlar, Open CAD Studio'nun varlığını sürdürebilmesi için önemli olan geliştirme çalışmalarını ve altyapıyı finanse eder. + .decline = Hayır, Teşekkürler + modal = .about = Hakkında .keyboard-shortcuts = Klavye Kısayolları diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index a0f5ce848..e51bb282a 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = 浏览器存储 .keep-recent-files = 保留最近文件 +donation = + .title = 捐款请求 + .heading = 向 Open CAD Studio 捐款 + .body = Open CAD Studio 需要您的帮助!捐款将用于资助对 Open CAD Studio 持续发展至关重要的开发工作和基础设施。 + .decline = 不,谢谢 + modal = .about = 关于 .keyboard-shortcuts = 键盘快捷键 diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 248208283..dac33ce33 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -90,6 +90,12 @@ start = .browser-storage = 瀏覽器儲存空間 .keep-recent-files = 保留最近檔案 +donation = + .title = 捐款請求 + .heading = 捐款給 Open CAD Studio + .body = Open CAD Studio 需要您的協助!捐款將用於資助對 Open CAD Studio 持續發展至關重要的開發工作與基礎設施。 + .decline = 不用,謝謝 + modal = .about = 關於 .keyboard-shortcuts = 鍵盤快速鍵 diff --git a/src/app/mod.rs b/src/app/mod.rs index 0d430db89..8e75d1bcc 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -24,6 +24,7 @@ mod presspull_ops; mod recent; pub(crate) mod settings; mod shortcuts; +mod startup; mod style_ops; mod text_inline; mod tolerance_dialog; @@ -691,6 +692,7 @@ pub(super) struct OpenCADStudio { /// The open in-canvas modal dialog, if any (Plan B: shared overlay instead /// of OS windows). active_modal: Option, + pending_startup_modals: std::collections::VecDeque, /// Plot modal geometry preserved while the Plot Style editor is open as /// a child dialog. None means Plotstyle was opened directly (e.g. command). plotstyle_parent_plot_geometry: Option<(iced::Vector, iced::Vector)>, @@ -795,13 +797,9 @@ pub(super) struct OpenCADStudio { /// Tracked separately from the PDSIZE sign so a size of 0 (sign-less) still /// remembers which radio is active. point_size_relative: bool, - /// New-release notification window — opened on startup when the - /// GitHub releases API reports a newer version than this build. - /// First-launch "make Open CAD Studio the default for .dwg/.dxf?" prompt - /// window. Shown once, gated on `default_assoc_prompted`. - /// Whether the one-time default-association prompt has already been shown. - /// Persisted via [`settings::UserSettings`] so it survives restarts. + /// Whether the default-association prompt has been answered. default_assoc_prompted: bool, + donation_prompt_version: String, /// Read-only session (`--read-only`): editing is allowed but every save /// path is refused. Set once at boot from the CLI config. read_only: bool, @@ -1663,6 +1661,7 @@ pub enum ModalKind { Shortcuts, PluginManager, UpdateNotice, + DonationPrompt, Layers, LayerStateManager, LayerTranslator, @@ -2914,6 +2913,7 @@ pub enum Message { UpdateCheckResult(Option), /// User dismissed the update-notice window. UpdateNoticeClose, + DonationPromptDonate, /// First-launch default-association prompt: user accepted — register this /// app as the default handler for .dwg / .dxf. AssocPromptYes, @@ -3421,6 +3421,7 @@ impl OpenCADStudio { color_picker_tab: ColorPickerTab::Index, recent_colors: Vec::new(), active_modal: None, + pending_startup_modals: std::collections::VecDeque::new(), plotstyle_parent_plot_geometry: None, find_replace: FindReplaceState::default(), aec_drop_acknowledged: false, @@ -3461,6 +3462,7 @@ impl OpenCADStudio { point_size_buf: String::new(), point_size_relative: true, default_assoc_prompted: false, + donation_prompt_version: String::new(), read_only: false, update_notice_version: None, update_notice_body: None, @@ -3831,13 +3833,7 @@ impl OpenCADStudio { .map(|line| Task::done(Message::Command(line))), ) }; - // One-time prompt offering to make Open CAD Studio the default app for - // .dwg / .dxf. Shown only on the first launch that hasn't answered it - // yet; the flag is persisted so we never ask twice. - let assoc_prompt: Task = Task::none(); - if !s.default_assoc_prompted { - s.active_modal = Some(ModalKind::AssocPrompt); - } + s.queue_startup_prompts(); // Fetch the Patreon supporters list once at boot for the Start page. #[cfg(not(target_arch = "wasm32"))] let patrons_fetch = Task::perform( @@ -3900,7 +3896,6 @@ impl OpenCADStudio { focus_cmd, cli_open, script, - assoc_prompt, patrons_fetch, videos_fetch, discussions_fetch, @@ -3916,6 +3911,7 @@ impl OpenCADStudio { fn boot_web() -> (Self, Task) { #[cfg_attr(target_arch = "wasm32", allow(unused_mut))] let mut s = Self::new(); + s.queue_startup_prompts(); let focus = s.focus_cmd_input(); let primary_font = crate::scene::text::web_font::preload_language( &crate::i18n::active_language_tag(), diff --git a/src/app/settings.rs b/src/app/settings.rs index da1643a3e..a9ca0cb55 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -90,7 +90,7 @@ const SNAP_ORDER: &[SnapType] = &[ // persisted set. (#121) ]; -/// `$OSMODE` bit for each running object-snap mode (standard AutoCAD bitmask). +/// `$OSMODE` bit for each running object-snap mode. /// `None` for OCS-only snaps (Grid, ObjectPick) that have no standard bit. fn snap_bit(s: SnapType) -> Option { Some(match s { @@ -179,6 +179,8 @@ pub struct UserSettings { /// prompt has already been shown. Set once the user answers (either way), /// so we never nag again on subsequent launches. pub default_assoc_prompted: bool, + /// App version whose donation prompt has been displayed. + pub donation_prompt_version: String, /// Ids of plugins the user turned off in the Plugin Manager. Disabled /// plugins keep their manifest listed but drop their ribbon tab and command /// dispatch. @@ -313,6 +315,7 @@ impl Default for UserSettings { snap_angle_deg: 0.0, otrack: false, default_assoc_prompted: false, + donation_prompt_version: String::new(), disabled_plugins: Vec::new(), plugin_repos: Vec::new(), literal_spaces: false, diff --git a/src/app/startup.rs b/src/app/startup.rs new file mode 100644 index 000000000..9a15e2a98 --- /dev/null +++ b/src/app/startup.rs @@ -0,0 +1,108 @@ +use super::{ModalKind, OpenCADStudio}; + +impl OpenCADStudio { + pub(super) fn queue_startup_prompts(&mut self) { + #[cfg(not(target_arch = "wasm32"))] + if !self.default_assoc_prompted { + self.pending_startup_modals + .push_back(ModalKind::AssocPrompt); + } + if self.donation_prompt_version != env!("OCS_APP_VERSION") { + self.pending_startup_modals + .push_back(ModalKind::DonationPrompt); + } + self.show_next_startup_modal(); + } + + pub(super) fn show_next_startup_modal(&mut self) { + if self.active_modal.is_none() && self.opening.is_none() && self.pending_opens.is_empty() { + if let Some(&kind) = self.pending_startup_modals.front() { + self.active_modal = Some(kind); + self.reset_modal_geometry(); + } + } + } + + pub(super) fn mark_startup_modal_shown(&mut self) { + if self.active_modal.is_some() + && self.active_modal.as_ref() == self.pending_startup_modals.front() + { + self.pending_startup_modals.pop_front(); + } + if self.active_modal == Some(ModalKind::DonationPrompt) + && self.donation_prompt_version != env!("OCS_APP_VERSION") + { + self.donation_prompt_version = env!("OCS_APP_VERSION").to_string(); + self.save_config(); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::app::{config::AppConfig, Message}; + + #[test] + fn donation_prompt_persists_once_per_version() { + let mut app = OpenCADStudio::new_for_test(); + // Older settings have no donation version; headless construction never prompts. + app.apply_config(serde_json::from_str::("{}").unwrap()); + assert!(app.active_modal.is_none()); + assert!(app.pending_startup_modals.is_empty()); + app.default_assoc_prompted = true; + app.queue_startup_prompts(); + assert_eq!(app.active_modal, Some(ModalKind::DonationPrompt)); + assert!(app.donation_prompt_version.is_empty()); + + let _ = app.update(Message::ModalContentResized(iced::Size::new(540.0, 280.0))); + let saved = serde_json::to_string(&app.current_config()).unwrap(); + let _ = app.update(Message::CloseModal); + assert!(app.active_modal.is_none()); + + let mut restarted = OpenCADStudio::new_for_test(); + restarted.apply_config(serde_json::from_str(&saved).unwrap()); + restarted.queue_startup_prompts(); + assert_eq!(restarted.donation_prompt_version, env!("OCS_APP_VERSION")); + assert!(restarted.active_modal.is_none()); + + restarted.donation_prompt_version = "previous-release".to_string(); + restarted.queue_startup_prompts(); + assert_eq!(restarted.active_modal, Some(ModalKind::DonationPrompt)); + let _ = restarted.update(Message::CommandEscape); + assert!(restarted.active_modal.is_none()); + assert_eq!(restarted.donation_prompt_version, env!("OCS_APP_VERSION")); + let _ = restarted.update(Message::Noop); + assert!(restarted.active_modal.is_none()); + } + + #[test] + fn startup_dialogs_wait_their_turn_and_preserve_unseen_prompts() { + let mut app = OpenCADStudio::new_for_test(); + app.apply_config(AppConfig::default()); + app.queue_startup_prompts(); + assert_eq!(app.active_modal, Some(ModalKind::AssocPrompt)); + let _ = app.update(Message::UpdateCheckResult(Some( + crate::io::update_check::UpdateInfo { + version: "next-release".to_string(), + body: String::new(), + }, + ))); + assert_eq!(app.active_modal, Some(ModalKind::AssocPrompt)); + let _ = app.update(Message::AssocPromptNo); + assert_eq!(app.active_modal, Some(ModalKind::DonationPrompt)); + + // A file-recovery dialog may interrupt startup before the first frame. + app.active_modal = Some(ModalKind::Recovery); + let _ = app.update(Message::CloseModal); + assert_eq!(app.active_modal, Some(ModalKind::DonationPrompt)); + assert!(app.donation_prompt_version.is_empty()); + let _ = app.update(Message::ModalContentResized(iced::Size::new(540.0, 280.0))); + assert_eq!(app.donation_prompt_version, env!("OCS_APP_VERSION")); + let _ = app.update(Message::CloseModal); + assert_eq!(app.active_modal, Some(ModalKind::UpdateNotice)); + let _ = app.update(Message::UpdateNoticeClose); + assert!(app.active_modal.is_none()); + assert!(app.pending_startup_modals.is_empty()); + } +} diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 13d2c160f..b16da004c 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -370,6 +370,7 @@ impl OpenCADStudio { snap_angle_deg: self.snap_angle_deg, otrack: self.snapper.otrack_enabled, default_assoc_prompted: self.default_assoc_prompted, + donation_prompt_version: self.donation_prompt_version.clone(), disabled_plugins: { let mut v: Vec = self.disabled_plugins.iter().cloned().collect(); v.sort(); @@ -432,6 +433,7 @@ impl OpenCADStudio { // open / tab switch), not app-global, so they are not applied here. self.snapper.otrack_enabled = s.otrack; self.default_assoc_prompted = s.default_assoc_prompted; + self.donation_prompt_version = s.donation_prompt_version.clone(); self.disabled_plugins = s.disabled_plugins.iter().cloned().collect(); self.plugin_repos = s.plugin_repos.clone(); self.command_line.literal_spaces = s.literal_spaces; diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index fbe07234d..5b928610a 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -124,6 +124,7 @@ impl OpenCADStudio { /// old OS window did: a style editor discards its staged (un-applied) /// changes, and the ribbon tool that launched the dialog is de-highlighted. fn close_active_modal(&mut self) { + self.mark_startup_modal_shown(); use super::ModalKind::*; // Plot Style opened from PLOT behaves as a child modal. // Closing it restores the parent Plot dialog instead of returning @@ -282,6 +283,7 @@ impl OpenCADStudio { } } let task = self.update_inner(msg); + self.show_next_startup_modal(); self.sync_open_command_history(); // Close the document-level first-touch transaction started by // push_undo_snapshot at this message boundary. @@ -6429,6 +6431,7 @@ impl OpenCADStudio { return Task::none(); } let first_measurement = self.modal_content_size.replace(size).is_none(); + self.mark_startup_modal_shown(); if first_measurement { let initial_width = self.mtext_editor.as_ref().and_then(|editor| { editor.editing.is_none().then(|| { @@ -7125,9 +7128,15 @@ impl OpenCADStudio { }; self.update_notice_version = Some(info.version); self.update_notice_body = Some(info.body); - self.active_modal = Some(super::ModalKind::UpdateNotice); + self.pending_startup_modals + .push_back(super::ModalKind::UpdateNotice); Task::none() } + Message::DonationPromptDonate => { + self.close_active_modal(); + self.dispatch_view("DONATE", self.active_tab) + .unwrap_or_else(Task::none) + } Message::UpdateNoticeClose => { self.close_active_modal(); Task::none() @@ -7143,8 +7152,7 @@ impl OpenCADStudio { Message::AssocPromptYes => { self.file_assoc_enabled = true; self.mark_assoc_prompted(); - self.active_modal = None; - self.reset_modal_geometry(); + self.close_active_modal(); // set_default_app registers the handler first, then makes us the // default — boot no longer does this automatically. Task::perform( @@ -7155,8 +7163,7 @@ impl OpenCADStudio { Message::AssocPromptNo => { self.file_assoc_enabled = false; self.mark_assoc_prompted(); - self.active_modal = None; - self.reset_modal_geometry(); + self.close_active_modal(); Task::none() } Message::AssocResult(result) => { diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index 4a51688d2..799058559 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -17,6 +17,7 @@ impl OpenCADStudio { Some(K::FindReplace) => crate::tr!("modal", "find-replace"), Some(K::PluginManager) => crate::tr!("modal", "plugin-manager"), Some(K::UpdateNotice) => crate::tr!("modal", "update-available"), + Some(K::DonationPrompt) => crate::tr!("donation", "title"), Some(K::Layers) => crate::tr!("modal", "layer-manager"), Some(K::LayerStateManager) => crate::tr!("modal", "layer-state-manager"), Some(K::LayerTranslator) => crate::t!("Layer Translator").into_owned(), @@ -1300,6 +1301,9 @@ impl OpenCADStudio { super::super::ModalKind::AssocPrompt => { automatic_flow(ex, default_assoc_dialog_window) } + super::super::ModalKind::DonationPrompt => { + sized_flow(ex, 540, 360, donation_dialog_window) + } super::super::ModalKind::AecDropWarning => { let src_label = self .tabs @@ -1843,10 +1847,43 @@ fn layer_delete_warning_window( .into() } -/// First-launch prompt offering to register Open CAD Studio as the default -/// handler for .dwg / .dxf. "Yes" runs the platform association call; "Not now" -/// just dismisses. Either answer flips the persisted `default_assoc_prompted` -/// flag so the dialog never reappears. +fn donation_dialog_window(sizing: crate::ui::modal::ModalSizing) -> Element<'static, Message> { + container( + column![ + text(crate::tr!("donation", "heading")).size(18), + row![ + text(crate::tr!("donation", "body")) + .size(14) + .width(Fill), + crate::ui::icons::themed(crate::ui::icons::HEART, 52.0), + ] + .spacing(20) + .align_y(iced::Center), + row![ + Space::new().width(Fill), + dialog_button( + crate::tr!("start", "donate"), + Message::DonationPromptDonate, + button::primary, + ), + dialog_button( + crate::tr!("donation", "decline"), + Message::CloseModal, + button::secondary, + ), + ] + .spacing(8) + .align_y(iced::Center), + ] + .spacing(18) + .width(sizing.width), + ) + .style(dialog_body_style) + .padding([24, 28]) + .into() +} + +/// One-time default application prompt. fn default_assoc_dialog_window( sizing: crate::ui::modal::ModalSizing, ) -> Element<'static, Message> { diff --git a/src/i18n.rs b/src/i18n.rs index ba6d98e82..b72f78c4d 100644 --- a/src/i18n.rs +++ b/src/i18n.rs @@ -408,6 +408,35 @@ mod tests { use super::*; use std::collections::BTreeSet; + #[test] + fn donation_text_is_translated_in_every_locale() { + for language in Language::ALL + .into_iter() + .filter(|language| *language != Language::System) + { + let loader = FluentLanguageLoader::new("opencadstudio", "en-US".parse().unwrap()); + load_language(&loader, language).expect("Fluent resources must parse"); + let attributes: BTreeSet = loader.with_message_iter( + &language.requested()[0], + |messages| { + messages + .filter(|message| message.id.name == "donation") + .flat_map(|message| { + message.attributes.iter().map(|attr| attr.id.name.to_owned()) + }) + .collect() + }, + ); + assert_eq!( + attributes, + BTreeSet::from(["title", "heading", "body", "decline"].map(String::from)) + ); + for attribute in attributes { + assert!(!loader.get_attr("donation", &attribute).is_empty()); + } + } + } + #[test] fn greek_catalog_loads_and_covers_the_source_catalog() { let loader = FluentLanguageLoader::new("opencadstudio", "en-US".parse().unwrap());