mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-20 02:44:29 +08:00
[feat] add a one-time sponsorship prompt
This commit is contained in:
@@ -131,6 +131,8 @@ int ConfigCenter::Load() {
|
||||
portable_service_prompt_suppressed_ =
|
||||
ini_->GetBoolValue(section_, "portable_service_prompt_suppressed",
|
||||
portable_service_prompt_suppressed_);
|
||||
sponsorship_prompt_dismissed_ =
|
||||
ini_->GetBoolValue(section_, "sponsorship_prompt_dismissed", false);
|
||||
|
||||
const char* file_transfer_save_path_value =
|
||||
ini_->GetValue(section_, "file_transfer_save_path", nullptr);
|
||||
@@ -221,6 +223,8 @@ int ConfigCenter::Save() {
|
||||
save_remote_previews_.load());
|
||||
ini_->SetBoolValue(section_, "portable_service_prompt_suppressed",
|
||||
portable_service_prompt_suppressed_);
|
||||
ini_->SetBoolValue(section_, "sponsorship_prompt_dismissed",
|
||||
sponsorship_prompt_dismissed_);
|
||||
|
||||
ini_->SetValue(section_, "file_transfer_save_path",
|
||||
file_transfer_save_path_.c_str());
|
||||
@@ -395,6 +399,15 @@ int ConfigCenter::SetPortableServicePromptSuppressed(bool suppressed) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetSponsorshipPromptDismissed(bool dismissed) {
|
||||
if (StoreValues({{"sponsorship_prompt_dismissed",
|
||||
dismissed ? "true" : "false"}}) != 0) {
|
||||
return -1;
|
||||
}
|
||||
sponsorship_prompt_dismissed_ = dismissed;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// getters
|
||||
|
||||
ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() const { return language_; }
|
||||
@@ -463,6 +476,10 @@ bool ConfigCenter::IsPortableServicePromptSuppressed() const {
|
||||
return portable_service_prompt_suppressed_;
|
||||
}
|
||||
|
||||
bool ConfigCenter::IsSponsorshipPromptDismissed() const {
|
||||
return sponsorship_prompt_dismissed_;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetFileTransferSavePath(const std::string& path) {
|
||||
if (path.size() >= 512 || std::any_of(path.begin(), path.end(), [](unsigned char c) { return c < 32 || c == 127; })) return -1;
|
||||
std::lock_guard<std::mutex> lock(file_path_mutex_);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-05-29
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _CONFIG_CENTER_H_
|
||||
@@ -49,6 +49,7 @@ class ConfigCenter {
|
||||
int SetPrivacyScreen(bool enable_privacy_screen);
|
||||
int SetSaveRemotePreviews(bool enabled);
|
||||
int SetPortableServicePromptSuppressed(bool suppressed);
|
||||
int SetSponsorshipPromptDismissed(bool dismissed);
|
||||
int SetFileTransferSavePath(const std::string& path);
|
||||
|
||||
// read config
|
||||
@@ -73,6 +74,7 @@ class ConfigCenter {
|
||||
bool IsEnablePrivacyScreen() const;
|
||||
bool IsSaveRemotePreviews() const;
|
||||
bool IsPortableServicePromptSuppressed() const;
|
||||
bool IsSponsorshipPromptDismissed() const;
|
||||
std::string GetFileTransferSavePath() const;
|
||||
|
||||
int Load();
|
||||
@@ -102,6 +104,7 @@ class ConfigCenter {
|
||||
std::atomic<bool> enable_privacy_screen_{true};
|
||||
std::atomic<bool> save_remote_previews_{true};
|
||||
bool portable_service_prompt_suppressed_ = false;
|
||||
bool sponsorship_prompt_dismissed_ = false;
|
||||
std::string file_transfer_save_path_ = "";
|
||||
};
|
||||
} // namespace crossdesk
|
||||
|
||||
@@ -1018,6 +1018,7 @@ struct GuiApplication::SlintUi {
|
||||
bool connection_password_initialized = false;
|
||||
bool connection_dialog_gallery = false;
|
||||
bool portable_service_dialog_initialized = false;
|
||||
bool sponsorship_prompt_pending = false;
|
||||
std::string recent_model_signature;
|
||||
std::optional<std::string> recent_group_filter;
|
||||
std::vector<std::string> recent_filter_groups;
|
||||
@@ -1265,6 +1266,8 @@ void GuiApplication::InitializeUi() {
|
||||
slint::set_xdg_app_id("crossdesk");
|
||||
#endif
|
||||
ui_ = std::make_unique<SlintUi>();
|
||||
ui_->sponsorship_prompt_pending =
|
||||
!config_center_->IsSponsorshipPromptDismissed();
|
||||
ui_->video_presenter =
|
||||
std::make_unique<SlintVideoPresenter>(*video_renderer_);
|
||||
#if _WIN32
|
||||
@@ -1746,6 +1749,13 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
|
||||
main->on_open_kofi_sponsor([this] {
|
||||
OpenUrl("https://ko-fi.com/kunkundi");
|
||||
});
|
||||
main->on_dismiss_sponsorship([this, main] {
|
||||
main->set_sponsorship_open(false);
|
||||
ui_->sponsorship_prompt_pending = false;
|
||||
if (config_center_->SetSponsorshipPromptDismissed(true) != 0) {
|
||||
LOG_WARN("Could not save sponsorship prompt dismissal");
|
||||
}
|
||||
});
|
||||
main->on_check_for_updates([this] {
|
||||
update_checker_->RequestCheck();
|
||||
SyncMainWindow();
|
||||
@@ -2299,6 +2309,16 @@ void GuiApplication::Tick() {
|
||||
SyncGallery();
|
||||
SyncConnectionDialog();
|
||||
SyncPlatformDialogs();
|
||||
// Wait for startup notices and user interactions to finish. Once shown,
|
||||
// never reopen it during this run, even if saving the dismissal fails.
|
||||
if (ui_->sponsorship_prompt_pending &&
|
||||
ui_->main->window().is_visible() &&
|
||||
!ui_->main->window().is_minimized() &&
|
||||
ui_->main->get_can_show_sponsorship() &&
|
||||
!ui_->file_dialog_open && !HasActiveSession()) {
|
||||
ui_->sponsorship_prompt_pending = false;
|
||||
ui_->main->set_sponsorship_open(true);
|
||||
}
|
||||
SyncStreamWindow();
|
||||
SyncServerWindow();
|
||||
SyncFileTransferNotifications();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-05-29
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _LOCALIZATION_DATA_H_
|
||||
#define _LOCALIZATION_DATA_H_
|
||||
|
||||
@@ -289,6 +290,12 @@ struct TranslationRow {
|
||||
X(no_such_id, u8"无此ID", "No such ID", u8"ID не найден") \
|
||||
X(about, u8"关于", "About", u8"О программе") \
|
||||
X(sponsorship_title, u8"赞助支持", "Sponsorship", u8"Поддержка проекта") \
|
||||
X(sponsorship_message, u8"如果 CrossDesk 对你有帮助,欢迎赞助支持项目的持续开发、跨平台测试与日常维护。感谢你的支持!", \
|
||||
"If CrossDesk is useful to you, consider supporting its continued development, cross-platform testing, and maintenance. Thank you for your support!", \
|
||||
u8"Если CrossDesk вам полезен, поддержите его дальнейшую разработку, тестирование на разных платформах и сопровождение. Спасибо за вашу поддержку!") \
|
||||
X(sponsorship_dismiss_hint, u8"关闭后不再自动弹出,你仍可在「关于」中找到赞助入口。", \
|
||||
"This prompt will not appear again after you close it. Sponsorship links remain available in About.", \
|
||||
u8"После закрытия это окно больше не появится автоматически. Ссылки для поддержки останутся в разделе «О программе».") \
|
||||
X(sponsor_via_afdian, u8"爱发电", "AFDIAN", "AFDIAN") \
|
||||
X(notification, u8"通知", "Notification", u8"Уведомление") \
|
||||
X(new_version, u8"新版本", "New version", u8"Новая версия") \
|
||||
|
||||
@@ -53,6 +53,8 @@ export global UiStrings {
|
||||
in-out property <string> settings: "Settings";
|
||||
in-out property <string> about: "About";
|
||||
in-out property <string> sponsorship-title: "Sponsorship";
|
||||
in-out property <string> sponsorship-message;
|
||||
in-out property <string> sponsorship-dismiss-hint;
|
||||
in-out property <string> sponsor-via-afdian: "AFDIAN";
|
||||
in-out property <string> ok: "OK";
|
||||
in-out property <string> cancel: "Cancel";
|
||||
@@ -127,14 +129,15 @@ export global UiStrings {
|
||||
in-out property <string> release-date-label: "Release Date: ";
|
||||
}
|
||||
|
||||
// Keep sponsor labels, order, and sizing consistent across both dialogs.
|
||||
// Keep sponsor labels, order, and sizing consistent across dialogs.
|
||||
component SponsorshipSection inherits VerticalLayout {
|
||||
in property <bool> show-title: true;
|
||||
callback open-afdian;
|
||||
callback open-kofi;
|
||||
callback open-github;
|
||||
vertical-stretch: 0;
|
||||
spacing: 10px;
|
||||
Text {
|
||||
if root.show-title: Text {
|
||||
text: UiStrings.sponsorship-title;
|
||||
color: WorkbenchStyle.text; font-size: 12px; font-weight: 600;
|
||||
wrap: word-wrap;
|
||||
@@ -492,6 +495,7 @@ export component MainWindow inherits Window {
|
||||
callback open-afdian-sponsor;
|
||||
callback open-github-sponsor;
|
||||
callback open-kofi-sponsor;
|
||||
callback dismiss-sponsorship;
|
||||
callback check-for-updates;
|
||||
callback signal-reconnect-requested;
|
||||
callback connection-cancel;
|
||||
@@ -517,6 +521,20 @@ export component MainWindow inherits Window {
|
||||
if !root.self-host-settings-open { root.self-host-settings-error = ""; }
|
||||
}
|
||||
in-out property <bool> about-open: false;
|
||||
in-out property <bool> sponsorship-open: false;
|
||||
// Automatic prompts must not interrupt another dialog or menu.
|
||||
private property <bool> sponsorship-unobstructed: !root.gallery-mode
|
||||
&& !root.menu-open && !root.settings-open
|
||||
&& !root.self-host-settings-open && !root.about-open
|
||||
&& !(root.update-open && root.update-available) && !root.messages-open
|
||||
&& !root.reset-password-open && !root.alias-open && !root.delete-open
|
||||
&& !root.device-group-open && !root.connection-dialog-open
|
||||
&& !root.permission-dialog-open && !root.wayland-notice-open
|
||||
&& !root.portable-service-dialog-open
|
||||
&& !root.portable-service-suppressed-notice-open
|
||||
&& root.offline-warning == "";
|
||||
out property <bool> can-show-sponsorship: !root.sponsorship-open
|
||||
&& root.sponsorship-unobstructed;
|
||||
// Follow the first available update. Dismissing the dialog replaces this
|
||||
// binding, so background checks do not reopen it during this launch.
|
||||
in-out property <bool> update-open: root.update-available;
|
||||
@@ -574,6 +592,10 @@ export component MainWindow inherits Window {
|
||||
if root.about-open { focus-sink.focus(); }
|
||||
}
|
||||
|
||||
changed sponsorship-open => {
|
||||
if root.sponsorship-open { focus-sink.focus(); }
|
||||
}
|
||||
|
||||
reset-remote-id => {
|
||||
root.remote-id-input = "";
|
||||
remote-id-editor.focus();
|
||||
@@ -799,6 +821,10 @@ export component MainWindow inherits Window {
|
||||
if event.text == Key.Escape { root.about-open = false; }
|
||||
return accept;
|
||||
}
|
||||
if root.sponsorship-open && root.sponsorship-unobstructed {
|
||||
if event.text == Key.Escape { root.dismiss-sponsorship(); }
|
||||
return accept;
|
||||
}
|
||||
if root.messages-open {
|
||||
if event.text == Key.Escape { root.messages-open = false; }
|
||||
return accept;
|
||||
@@ -2253,6 +2279,52 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
}
|
||||
|
||||
if root.sponsorship-open && root.sponsorship-unobstructed: Rectangle {
|
||||
background: #15223850;
|
||||
z: 38;
|
||||
TouchArea { clicked => { focus-sink.focus(); } }
|
||||
SettingsDialog {
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
width: min(480px, parent.width - 40px);
|
||||
height: self.content-top + sponsorship-content.preferred-height + 24px;
|
||||
title: UiStrings.sponsorship-title;
|
||||
close-label: MessageStrings.close;
|
||||
close => { root.dismiss-sponsorship(); }
|
||||
sponsorship-content := VerticalLayout {
|
||||
x: 24px; y: parent.content-top;
|
||||
width: parent.width - 48px; height: self.preferred-height;
|
||||
padding: 0px; spacing: 18px;
|
||||
Text {
|
||||
text: UiStrings.sponsorship-message;
|
||||
color: WorkbenchStyle.text; font-size: 13px; wrap: word-wrap;
|
||||
}
|
||||
SponsorshipSection {
|
||||
show-title: false;
|
||||
open-afdian => { root.open-afdian-sponsor(); }
|
||||
open-kofi => { root.open-kofi-sponsor(); }
|
||||
open-github => { root.open-github-sponsor(); }
|
||||
}
|
||||
Text {
|
||||
text: UiStrings.sponsorship-dismiss-hint;
|
||||
color: WorkbenchStyle.muted; font-size: 12px; wrap: word-wrap;
|
||||
}
|
||||
Rectangle { height: 1px; background: WorkbenchStyle.separator; }
|
||||
HorizontalLayout {
|
||||
alignment: end;
|
||||
SettingsButton {
|
||||
width: max(80px, sponsorship-close-metrics.preferred-width + 24px);
|
||||
primary: true; text: MessageStrings.close;
|
||||
clicked => { root.dismiss-sponsorship(); }
|
||||
sponsorship-close-metrics := Text {
|
||||
visible: false; text: MessageStrings.close; font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.about-open: Rectangle {
|
||||
background: #15223850;
|
||||
z: 46;
|
||||
|
||||
@@ -75,6 +75,8 @@ inline int ApplyMainWindowStrings(
|
||||
strings.set_settings(Text(localization::settings[language]));
|
||||
strings.set_about(Text(localization::about[language]));
|
||||
strings.set_sponsorship_title(Text(localization::sponsorship_title[language]));
|
||||
strings.set_sponsorship_message(Text(localization::sponsorship_message[language]));
|
||||
strings.set_sponsorship_dismiss_hint(Text(localization::sponsorship_dismiss_hint[language]));
|
||||
strings.set_sponsor_via_afdian(Text(localization::sponsor_via_afdian[language]));
|
||||
strings.set_ok(Text(localization::ok[language]));
|
||||
strings.set_cancel(Text(localization::cancel[language]));
|
||||
|
||||
Reference in New Issue
Block a user