From 019bdf9ce6fbb1d04b3d5b9dc8a1ce18df14e878 Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Sun, 2 Mar 2025 12:08:15 +0000 Subject: [PATCH] Add refreshing indicator to ActivitiesView --- client/src/activities_view.vala | 126 ++++++++++++-------------------- client/src/activity_card.vala | 86 ++++++++++++++++++++++ client/src/meson.build | 3 +- client/styles.css | 9 +++ 4 files changed, 143 insertions(+), 81 deletions(-) create mode 100644 client/src/activity_card.vala diff --git a/client/src/activities_view.vala b/client/src/activities_view.vala index 3853518..20bae2e 100644 --- a/client/src/activities_view.vala +++ b/client/src/activities_view.vala @@ -2,15 +2,18 @@ namespace StudySystemClient { public class ActivitiesView : Gtk.Box { private Client client; private Gtk.FlowBox card_container; + private RefreshingIndicator refreshing_indicator; public ActivitiesView(Client client) { - margin_top = margin_bottom = margin_start = margin_end = 0; - this.client = client; + Object(orientation: Gtk.Orientation.VERTICAL, + hexpand: true, + vexpand: true, + margin_top: 0, + margin_bottom: 0, + margin_start: 0, + margin_end: 0); - var scrolled_window = new Gtk.ScrolledWindow(); - scrolled_window.hscrollbar_policy = Gtk.PolicyType.NEVER; - scrolled_window.vexpand = true; - scrolled_window.add_css_class("card-container"); + this.client = client; card_container = new Gtk.FlowBox(); card_container.homogeneous = true; @@ -18,9 +21,20 @@ namespace StudySystemClient { card_container.max_children_per_line = 1; card_container.selection_mode = Gtk.SelectionMode.NONE; card_container.valign = Gtk.Align.START; + + var scrolled_window = new Gtk.ScrolledWindow(); + scrolled_window.hscrollbar_policy = Gtk.PolicyType.NEVER; + scrolled_window.hexpand = true; + scrolled_window.vexpand = true; + scrolled_window.add_css_class("card-container"); scrolled_window.set_child(card_container); - this.append(scrolled_window); + var overlay = new Gtk.Overlay(); + overlay.hexpand = overlay.vexpand = true; + overlay.set_child(scrolled_window); + this.append(overlay); + + refreshing_indicator = new RefreshingIndicator(overlay); refresh.begin((obj, res) => { refresh.end(res); @@ -28,6 +42,7 @@ namespace StudySystemClient { } private async void refresh() { + refreshing_indicator.show(); try { var activities = yield client.list_activities(); card_container.remove_all(); @@ -40,6 +55,7 @@ namespace StudySystemClient { stderr.printf("Error refreshing activities: %s\n", e.message); } + refreshing_indicator.hide(); } private async void log_session(string subject, ActivityType type, @@ -53,88 +69,38 @@ namespace StudySystemClient { } } - private class ActivityCard : Gtk.Frame { - public signal void session_logged(string subject, ActivityType type, - int minutes); + private class RefreshingIndicator { + private Gtk.Overlay overlay; + private Gtk.Frame frame; - public ActivityCard(Activity activity) { - add_css_class("card"); + public RefreshingIndicator(Gtk.Overlay overlay) { + this.overlay = overlay; - var content = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 12); - - var text = new Gtk.Box(Gtk.Orientation.VERTICAL, 6); - text.hexpand = true; - - var subject = new Gtk.Label(activity.subject); - subject.halign = Gtk.Align.START; - subject.add_css_class("activity-subject"); - text.append(subject); - - var type = new Gtk.Label(activity.type.to_string()); - type.halign = Gtk.Align.START; - text.append(type); - - content.append(text); - - var button - = new Gtk.Button.from_icon_name("appointment-new-symbolic"); - button.vexpand = false; - button.valign = Gtk.Align.CENTER; - button.set_tooltip_text("Log session"); - button.add_css_class("log-session-button"); - content.append(button); - - set_child(content); - - var log_session_popover = new LogSessionPopover(); - log_session_popover.set_parent(button); - button.clicked.connect(() => log_session_popover.popup()); - log_session_popover.session_logged.connect((minutes) => { - session_logged(activity.subject, activity.type, minutes); - }); - } - } - - private class LogSessionPopover : Gtk.Popover { - public signal void session_logged(int minutes); - - private const int DEFAULT_LENGTH = 30; - - private Gtk.SpinButton input; - - public LogSessionPopover() { - var content = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); - - var label = new Gtk.Label("Minutes"); + var label = new Gtk.Label("Refreshing"); label.halign = Gtk.Align.START; + + var spinner = new Gtk.Spinner(); + spinner.start(); + + var content = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); + content.margin_top = content.margin_bottom + = content.margin_start = content.margin_end = 12; content.append(label); + content.append(spinner); - var adjustment - = new Gtk.Adjustment(DEFAULT_LENGTH, 10, 480, 10, 10, 0); - input = new Gtk.SpinButton(adjustment, 1, 0); - input.numeric = true; - content.append(input); - - var button = new Gtk.Button.from_icon_name("emblem-ok-symbolic"); - button.halign = Gtk.Align.END; - button.set_tooltip_text("Submit"); - button.add_css_class("suggested-action"); - button.clicked.connect(submit); - content.append(button); - - set_child(content); - - closed.connect(reset); + frame = new Gtk.Frame(null); + frame.halign = Gtk.Align.CENTER; + frame.valign = Gtk.Align.START; + frame.add_css_class("osd"); + frame.set_child(content); } - private void submit() { - session_logged((int)input.value); - reset(); - popdown(); + public void show() { + overlay.add_overlay(frame); } - private void reset() { - input.value = DEFAULT_LENGTH; + public void hide() { + overlay.remove_overlay(frame); } } } diff --git a/client/src/activity_card.vala b/client/src/activity_card.vala new file mode 100644 index 0000000..2123f4f --- /dev/null +++ b/client/src/activity_card.vala @@ -0,0 +1,86 @@ +namespace StudySystemClient { + public class ActivityCard : Gtk.Frame { + public signal void session_logged(string subject, ActivityType type, + int minutes); + + public ActivityCard(Activity activity) { + add_css_class("card"); + + var content = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 12); + + var text = new Gtk.Box(Gtk.Orientation.VERTICAL, 6); + text.hexpand = true; + + var subject = new Gtk.Label(activity.subject); + subject.halign = Gtk.Align.START; + subject.add_css_class("activity-subject"); + text.append(subject); + + var type = new Gtk.Label(activity.type.to_string()); + type.halign = Gtk.Align.START; + text.append(type); + + content.append(text); + + var button + = new Gtk.Button.from_icon_name("appointment-new-symbolic"); + button.vexpand = false; + button.valign = Gtk.Align.CENTER; + button.set_tooltip_text("Log session"); + button.add_css_class("log-session-button"); + content.append(button); + + set_child(content); + + var log_session_popover = new LogSessionPopover(); + log_session_popover.set_parent(button); + button.clicked.connect(() => log_session_popover.popup()); + log_session_popover.session_logged.connect((minutes) => { + session_logged(activity.subject, activity.type, minutes); + }); + } + } + + private class LogSessionPopover : Gtk.Popover { + public signal void session_logged(int minutes); + + private const int DEFAULT_LENGTH = 30; + + private Gtk.SpinButton input; + + public LogSessionPopover() { + var content = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); + + var label = new Gtk.Label("Minutes"); + label.halign = Gtk.Align.START; + content.append(label); + + var adjustment + = new Gtk.Adjustment(DEFAULT_LENGTH, 10, 480, 10, 10, 0); + input = new Gtk.SpinButton(adjustment, 1, 0); + input.numeric = true; + content.append(input); + + var button = new Gtk.Button.from_icon_name("emblem-ok-symbolic"); + button.halign = Gtk.Align.END; + button.set_tooltip_text("Submit"); + button.add_css_class("suggested-action"); + button.clicked.connect(submit); + content.append(button); + + set_child(content); + + closed.connect(reset); + } + + private void submit() { + session_logged((int)input.value); + reset(); + popdown(); + } + + private void reset() { + input.value = DEFAULT_LENGTH; + } + } +} diff --git a/client/src/meson.build b/client/src/meson.build index 6463c12..274ce49 100644 --- a/client/src/meson.build +++ b/client/src/meson.build @@ -10,8 +10,9 @@ configure_file( lib = library( 'study-system-client', sources: files( - 'activity.vala', 'activities_view.vala', + 'activity.vala', + 'activity_card.vala', 'client.vala', 'connection.vala', 'der.vala', diff --git a/client/styles.css b/client/styles.css index 56c55ca..852f142 100644 --- a/client/styles.css +++ b/client/styles.css @@ -23,3 +23,12 @@ margin-top: -2px; margin-left: 3px; } + +/* + * Couldn't find a built-in way to get the common OSD overlay style of + * rounding the bottom corners but not the top ones, so doing this + * myself with this CSS rule. + */ +overlay > frame.osd { + border-radius: 0 0 8px 8px; +}