feat(gui): polish pass over wording, icons and responsiveness

The Sync page gives each folder one card with its state, a Synced /
Online only switch and a menu for the rest; switching either way asks
first and the download prompt shows the free space on the disk.

Enabling, disabling and restarting the Proton Drive service ran
systemctl on GTK's main thread and froze the window. They now run in
the background, and every Retry button goes through one shared restart
helper.

Ten icons are bundled under resources/icons for glyphs no stock theme
has or that read ambiguously (sync, share, people, link, menu, map and
so on). A new test fails when the app or search prompt asks for a
symbolic icon that is neither bundled nor on a list checked against
Adwaita 50, libadwaita and GTK, or when a bundled icon is missing from
the resource manifest. The tray's sync icon, which Adwaita 50 no longer
ships, is now view-refresh.

Wording follows one glossary, recorded in docs/TRANSLATING.md: "Proton
Drive service" instead of "mount service" or "daemon", "Online only"
instead of "On-demand", "Proton Drive folder" instead of "mountpoint".
Dialog headings and buttons use title case and page titles match the
sidebar. Dates go through one formatter (app/dates.rs) and sizes
through i18n::human_bytes, and the cache label is translated.

The status tick stops while the window is hidden or minimized and runs
every fifth tick while it is unfocused, refreshing as soon as it gains
focus. "Sync complete" is announced only for large batches and only
when the window isn't focused. Smaller fixes: Empty Trash hides when
there is nothing to empty, copy buttons confirm with a check mark, the
sidebar status spins until the first report, and Photos moves Import
and Find Duplicates into a menu beside a labelled Select toggle.

All catalogs are updated and complete.
This commit is contained in:
nvrl committed 2026-09-28 13:26:59 +02:00
1 parent e872cad058
commit bd4f797d1f
48 files changed
+12684 -10991

No files matched your search

+28 -47
View File
@@ -46,12 +46,13 @@ pub(crate) struct SharedWidgets {
/// links I've bookmarked (open / remove). All three live outside the mount tree,
/// so the page addresses them by uid/id/token and always re-lists from the daemon.
pub(crate) fn build_shared_page() -> (gtk4::Widget, SharedWidgets) {
// A label, not a bookmark glyph: nothing about the icon says what a Drive
// bookmark is.
let add_bookmark = gtk4::Button::builder()
.icon_name("bookmark-new-symbolic")
.tooltip_text(gettext(
"Add Bookmark — save a public link to open it from here",
))
.label(gettext("Add Bookmark…"))
.tooltip_text(gettext("Save a public link to open it from here"))
.build();
add_bookmark.add_css_class("flat");
let refresh = refresh_button();
// Only sections with something in them are shown, so an account with one
@@ -87,7 +88,7 @@ pub(crate) fn build_shared_page() -> (gtk4::Widget, SharedWidgets) {
retry.add_css_class("suggested-action");
retry.set_visible(false);
let status = adw::StatusPage::builder()
.icon_name("emblem-shared-symbolic")
.icon_name("pdfs-people-symbolic")
.vexpand(true)
.child(&retry)
.build();
@@ -111,7 +112,7 @@ pub(crate) fn build_shared_page() -> (gtk4::Widget, SharedWidgets) {
.tooltip_text(gettext("Back"))
.visible(false)
.build();
let (frame, header, title) = page_frame(&gettext("Shared with Me"), &inner);
let (frame, header, title) = page_frame(&gettext("Shared with me"), &inner);
header.pack_start(&back);
header.pack_end(&refresh);
header.pack_end(&add_bookmark);
@@ -141,10 +142,7 @@ pub(crate) fn wire_shared(ui: &Rc<Ui>, retry: &gtk4::Button, add_bookmark: &gtk4
load_shared(&ui_back);
});
let ui_retry = ui.clone();
retry.connect_clicked(move |_| {
service::restart();
load_shared(&ui_retry);
});
retry.connect_clicked(move |_| restart_service_then(&ui_retry, load_shared));
let ui_add = ui.clone();
add_bookmark.connect_clicked(move |_| prompt_add_bookmark(&ui_add));
}
@@ -177,7 +175,7 @@ pub(crate) fn load_shared(ui: &Rc<Ui>) {
ui.shared.inflight.set(true);
shared_status(
ui,
"emblem-shared-symbolic",
"pdfs-people-symbolic",
&gettext("Loading…"),
&gettext("Reading your shared items."),
false,
@@ -429,7 +427,7 @@ pub(crate) fn shared_row_subtitle(entry: &DirEntry) -> String {
if !entry.shared_by.is_empty() {
parts.push(entry.shared_by.clone());
if entry.shared_at > 0 {
parts.push(format_modified(entry.shared_at));
parts.push(dates::short_date(entry.shared_at));
}
}
if !entry.is_dir {
@@ -507,7 +505,7 @@ fn open_shared_file(ui: &Rc<Ui>, uid: &str, name: &str) {
_ => toast_error(
&ui,
&gettext("Couldn't open file"),
&gettext("The mount service didn't respond."),
&gettext("The Proton Drive service didn't respond."),
),
}
});
@@ -516,29 +514,7 @@ fn open_shared_file(ui: &Rc<Ui>, uid: &str, name: &str) {
/// The daemon didn't answer the Shared page. Same still-starting vs. down split
/// as the other pages.
pub(crate) fn shared_unreachable(ui: &Rc<Ui>) {
if service::is_failed() || !service::is_active() {
shared_status(
ui,
"network-offline-symbolic",
&gettext("Not connected"),
&gettext("The Proton Drive mount service isn't running."),
true,
);
return;
}
shared_status(
ui,
"folder-remote-symbolic",
&gettext("Connecting…"),
&gettext("Waiting for the Proton Drive mount service to come up."),
false,
);
let ui = ui.clone();
glib::timeout_add_local_once(CONNECT_RETRY_INTERVAL, move || {
if ui.stack.visible_child_name().as_deref() == Some("shared") {
load_shared(&ui);
}
});
service_unreachable(ui, "shared", shared_status, load_shared);
}
/// Rebuild the three Shared sections. Rows added last time are removed first —
@@ -559,7 +535,7 @@ pub(crate) fn repaint_shared(
if shared.is_empty() && invitations.is_empty() && bookmarks.is_empty() {
shared_status(
ui,
"emblem-shared-symbolic",
"pdfs-people-symbolic",
&gettext("Nothing Shared with You"),
&gettext(
"Files and folders other people share with you appear here. Public links you bookmark are kept here too.",
@@ -593,7 +569,7 @@ pub(crate) fn repaint_shared(
rows.push((ui.shared.with_me_group.clone(), row.upcast()));
}
// Invitations: inviter + item, Accept / Reject.
// Invitations: inviter + item, Accept / Decline.
for inv in invitations {
let item = inv.name.clone().unwrap_or_else(|| gettext("a shared item"));
let row = adw::ActionRow::builder()
@@ -606,12 +582,13 @@ pub(crate) fn repaint_shared(
} else {
"text-x-generic-symbolic"
}));
// Labelled: a close glyph here reads as "dismiss this row".
let reject = gtk4::Button::builder()
.icon_name("window-close-symbolic")
.tooltip_text(gettext("Reject"))
.label(gettext("Decline"))
.valign(gtk4::Align::Center)
.build();
reject.add_css_class("flat");
reject.add_css_class("pill");
let accept = gtk4::Button::builder()
.label(gettext("Accept"))
.valign(gtk4::Align::Center)
@@ -631,13 +608,13 @@ pub(crate) fn repaint_shared(
let id = id_rej.clone();
confirm_destructive(
btn,
&gettext("Reject Invitation?"),
&gettext("Decline Invitation?"),
// Translators: {name} is the shared item's name, or "this item".
&gettext_f(
"You won't have access to {name} unless it is shared again.",
&[("name", &name_rej)],
),
&gettext("Reject"),
&gettext("Decline"),
move || respond_invitation(&ui, &id, false),
);
});
@@ -662,7 +639,7 @@ pub(crate) fn repaint_shared(
.build();
remove.add_css_class("flat");
let open = gtk4::Button::builder()
.icon_name("external-link-symbolic")
.icon_name("adw-external-link-symbolic")
.tooltip_text(gettext("Open in browser"))
.valign(gtk4::Align::Center)
.build();
@@ -707,7 +684,7 @@ pub(crate) fn respond_invitation(ui: &Rc<Ui>, id: &str, accept: bool) {
pub(crate) fn prompt_leave_shared(ui: &Rc<Ui>, uid: &str, name: &str) {
let win = ui_window(ui);
let dialog = adw::AlertDialog::builder()
.heading(gettext("Leave shared item"))
.heading(gettext("Leave Shared Item?"))
// Translators: {name} is the shared item's name.
.body(gettext_f(
"Leave “{name}”? You'll lose access until you're invited again.",
@@ -738,7 +715,7 @@ pub(crate) fn prompt_leave_shared(ui: &Rc<Ui>, uid: &str, name: &str) {
pub(crate) fn prompt_remove_bookmark(ui: &Rc<Ui>, token: &str, name: &str) {
let win = ui_window(ui);
let dialog = adw::AlertDialog::builder()
.heading(gettext("Remove bookmark"))
.heading(gettext("Remove Bookmark?"))
// Translators: {name} is the bookmark's name.
.body(gettext_f(
"Remove the bookmark for “{name}”?",
@@ -771,7 +748,7 @@ pub(crate) fn prompt_remove_bookmark(ui: &Rc<Ui>, token: &str, name: &str) {
pub(crate) fn prompt_add_bookmark(ui: &Rc<Ui>) {
let win = ui_window(ui);
let dialog = adw::AlertDialog::builder()
.heading(gettext("Add bookmark"))
.heading(gettext("Add Bookmark"))
.body(gettext("Paste a Proton Drive public link to save it here."))
.build();
let group = adw::PreferencesGroup::new();
@@ -835,7 +812,11 @@ pub(crate) fn run_shared_mutation(ui: &Rc<Ui>, req: Request, done: &str, failed:
Ok(Ok(Response::Error { message, kind })) => {
toast_failure(&ui, &failed, &message, kind)
}
_ => toast_error(&ui, &failed, &gettext("The mount service didn't respond.")),
_ => toast_error(
&ui,
&failed,
&gettext("The Proton Drive service didn't respond."),
),
}
});
}