Add refreshing indicator to ActivitiesView

This commit is contained in:
Camden Dixie O'Brien 2025-03-02 12:08:15 +00:00
parent 17629f1db7
commit 019bdf9ce6
4 changed files with 143 additions and 81 deletions

View File

@ -2,15 +2,18 @@ namespace StudySystemClient {
public class ActivitiesView : Gtk.Box { public class ActivitiesView : Gtk.Box {
private Client client; private Client client;
private Gtk.FlowBox card_container; private Gtk.FlowBox card_container;
private RefreshingIndicator refreshing_indicator;
public ActivitiesView(Client client) { public ActivitiesView(Client client) {
margin_top = margin_bottom = margin_start = margin_end = 0; Object(orientation: Gtk.Orientation.VERTICAL,
this.client = client; hexpand: true,
vexpand: true,
margin_top: 0,
margin_bottom: 0,
margin_start: 0,
margin_end: 0);
var scrolled_window = new Gtk.ScrolledWindow(); this.client = client;
scrolled_window.hscrollbar_policy = Gtk.PolicyType.NEVER;
scrolled_window.vexpand = true;
scrolled_window.add_css_class("card-container");
card_container = new Gtk.FlowBox(); card_container = new Gtk.FlowBox();
card_container.homogeneous = true; card_container.homogeneous = true;
@ -18,9 +21,20 @@ namespace StudySystemClient {
card_container.max_children_per_line = 1; card_container.max_children_per_line = 1;
card_container.selection_mode = Gtk.SelectionMode.NONE; card_container.selection_mode = Gtk.SelectionMode.NONE;
card_container.valign = Gtk.Align.START; 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); 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.begin((obj, res) => {
refresh.end(res); refresh.end(res);
@ -28,6 +42,7 @@ namespace StudySystemClient {
} }
private async void refresh() { private async void refresh() {
refreshing_indicator.show();
try { try {
var activities = yield client.list_activities(); var activities = yield client.list_activities();
card_container.remove_all(); card_container.remove_all();
@ -40,6 +55,7 @@ namespace StudySystemClient {
stderr.printf("Error refreshing activities: %s\n", stderr.printf("Error refreshing activities: %s\n",
e.message); e.message);
} }
refreshing_indicator.hide();
} }
private async void log_session(string subject, ActivityType type, private async void log_session(string subject, ActivityType type,
@ -53,88 +69,38 @@ namespace StudySystemClient {
} }
} }
private class ActivityCard : Gtk.Frame { private class RefreshingIndicator {
public signal void session_logged(string subject, ActivityType type, private Gtk.Overlay overlay;
int minutes); private Gtk.Frame frame;
public ActivityCard(Activity activity) { public RefreshingIndicator(Gtk.Overlay overlay) {
add_css_class("card"); this.overlay = overlay;
var content = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 12); var label = new Gtk.Label("Refreshing");
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; 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(label);
content.append(spinner);
var adjustment frame = new Gtk.Frame(null);
= new Gtk.Adjustment(DEFAULT_LENGTH, 10, 480, 10, 10, 0); frame.halign = Gtk.Align.CENTER;
input = new Gtk.SpinButton(adjustment, 1, 0); frame.valign = Gtk.Align.START;
input.numeric = true; frame.add_css_class("osd");
content.append(input); frame.set_child(content);
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() { public void show() {
session_logged((int)input.value); overlay.add_overlay(frame);
reset();
popdown();
} }
private void reset() { public void hide() {
input.value = DEFAULT_LENGTH; overlay.remove_overlay(frame);
} }
} }
} }

View File

@ -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;
}
}
}

View File

@ -10,8 +10,9 @@ configure_file(
lib = library( lib = library(
'study-system-client', 'study-system-client',
sources: files( sources: files(
'activity.vala',
'activities_view.vala', 'activities_view.vala',
'activity.vala',
'activity_card.vala',
'client.vala', 'client.vala',
'connection.vala', 'connection.vala',
'der.vala', 'der.vala',

View File

@ -23,3 +23,12 @@
margin-top: -2px; margin-top: -2px;
margin-left: 3px; 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;
}