Regularly refresh activities and update cards in-place
This commit is contained in:
parent
3e99cc293a
commit
8123c5375d
@ -1,23 +1,27 @@
|
||||
namespace StudySystemClient {
|
||||
public class ActivitiesView : CardArea {
|
||||
public class ActivitiesView : CardArea<ActivityCard>, IRefreshable {
|
||||
private const uint REFRESH_PERIOD_MS = 30000;
|
||||
|
||||
private Client client;
|
||||
private Refresher refresher;
|
||||
private bool pending_sort;
|
||||
|
||||
public ActivitiesView(Client client) {
|
||||
base();
|
||||
|
||||
this.client = client;
|
||||
this.map.connect(refresh);
|
||||
refresher = new Refresher(this, REFRESH_PERIOD_MS);
|
||||
pending_sort = false;
|
||||
this.map.connect(() => {
|
||||
refresher.start();
|
||||
refresh.begin();
|
||||
});
|
||||
}
|
||||
|
||||
private async void refresh() {
|
||||
if (!client.connected)
|
||||
return;
|
||||
try {
|
||||
var activities = yield client.list_activities();
|
||||
clear();
|
||||
foreach (var activity in activities) {
|
||||
var card = new ActivityCard(activity);
|
||||
card.session_logged.connect(log_session);
|
||||
add(card);
|
||||
}
|
||||
update_cards(activities);
|
||||
} catch (ClientError e) {
|
||||
stderr.printf("Error refreshing activities: %s\n",
|
||||
e.message);
|
||||
@ -33,5 +37,78 @@ namespace StudySystemClient {
|
||||
stderr.printf("Error logging session: %s\n", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
private void handle_pending_sort() {
|
||||
if (pending_sort) {
|
||||
container.sort(compare_cards);
|
||||
pending_sort = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void update_cards(Array<Activity> activities) {
|
||||
update_existing_cards(activities);
|
||||
for (uint i = 0; i < activities.length; ++i)
|
||||
create_card(activities.index(i));
|
||||
if (log_in_progress())
|
||||
pending_sort = true;
|
||||
else
|
||||
container.sort(compare_cards);
|
||||
}
|
||||
|
||||
private void update_existing_cards(Array<Activity> activities) {
|
||||
var to_remove = new List<ActivityCard>();
|
||||
foreach (var card in container) {
|
||||
if (!update_existing_card(card, activities))
|
||||
to_remove.append(card);
|
||||
}
|
||||
foreach (var card in to_remove)
|
||||
container.remove(card);
|
||||
}
|
||||
|
||||
private bool update_existing_card(ActivityCard card,
|
||||
Array<Activity> activities) {
|
||||
var activity_index = find_activity(card, activities);
|
||||
if (activity_index == null)
|
||||
return false;
|
||||
var priority = activities.index(activity_index).priority;
|
||||
card.update_priority(priority);
|
||||
activities._remove_index_fast(activity_index);
|
||||
return true;
|
||||
}
|
||||
|
||||
private uint? find_activity(ActivityCard card,
|
||||
Array<Activity> activities) {
|
||||
for (uint i = 0; i < activities.length; ++i) {
|
||||
if (activities.index(i).subject == card.activity.subject
|
||||
&& activities.index(i).type == card.activity.type)
|
||||
return i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void create_card(Activity activity) {
|
||||
var card = new ActivityCard(activity);
|
||||
card.session_logged.connect(log_session);
|
||||
card.log_closed.connect(handle_pending_sort);
|
||||
container.append(card);
|
||||
}
|
||||
|
||||
private bool log_in_progress() {
|
||||
foreach (var card in container) {
|
||||
if (card.logging)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int compare_cards(ActivityCard card1,
|
||||
ActivityCard card2) {
|
||||
if (card1.activity.priority < card2.activity.priority)
|
||||
return -1;
|
||||
else if (card1.activity.priority > card2.activity.priority)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,19 @@ namespace StudySystemClient {
|
||||
public class ActivityCard : Card {
|
||||
public signal void session_logged(string subject, ActivityType type,
|
||||
int minutes);
|
||||
public signal void log_closed();
|
||||
|
||||
public Activity activity { get; private set; }
|
||||
public bool logging { get; private set; }
|
||||
|
||||
private Gtk.Label priority_label;
|
||||
|
||||
public ActivityCard(Activity activity) {
|
||||
base();
|
||||
|
||||
this.activity = activity;
|
||||
logging = false;
|
||||
|
||||
var subject = new Gtk.Label(activity.subject);
|
||||
subject.halign = Gtk.Align.START;
|
||||
subject.add_css_class("activity-subject");
|
||||
@ -14,13 +23,14 @@ namespace StudySystemClient {
|
||||
type.add_css_class("activity-type");
|
||||
var separator = new Gtk.Label("·");
|
||||
separator.add_css_class("activity-priority");
|
||||
var priority = new Gtk.Label("%0.2f".printf(activity.priority));
|
||||
priority.add_css_class("activity-priority");
|
||||
|
||||
priority_label = new Gtk.Label(priority_text(activity.priority));
|
||||
priority_label.add_css_class("activity-priority");
|
||||
|
||||
var details = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
|
||||
details.append(type);
|
||||
details.append(separator);
|
||||
details.append(priority);
|
||||
details.append(priority_label);
|
||||
|
||||
var text = new Gtk.Box(Gtk.Orientation.VERTICAL, 6);
|
||||
text.hexpand = true;
|
||||
@ -42,10 +52,27 @@ namespace StudySystemClient {
|
||||
|
||||
var log_session_popover = new LogSessionPopover();
|
||||
log_session_popover.set_parent(button);
|
||||
button.clicked.connect(() => log_session_popover.popup());
|
||||
log_session_popover.closed.connect(() => {
|
||||
logging = false;
|
||||
log_closed();
|
||||
});
|
||||
log_session_popover.session_logged.connect((minutes) => {
|
||||
session_logged(activity.subject, activity.type, minutes);
|
||||
});
|
||||
|
||||
button.clicked.connect(() => {
|
||||
logging = true;
|
||||
log_session_popover.popup();
|
||||
});
|
||||
}
|
||||
|
||||
public void update_priority(double new_priority) {
|
||||
activity = { activity.subject, activity.type, new_priority };
|
||||
priority_label.set_text(priority_text(new_priority));
|
||||
}
|
||||
|
||||
private static string priority_text(double priority) {
|
||||
return "%0.2f".printf(priority);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,35 +6,23 @@ namespace StudySystemClient {
|
||||
}
|
||||
}
|
||||
|
||||
public class CardArea : Gtk.Box {
|
||||
private Gtk.FlowBox flow_box;
|
||||
public class CardArea<T> : Gtk.Box {
|
||||
protected IterableBox<T> container;
|
||||
|
||||
public CardArea() {
|
||||
hexpand = vexpand = true;
|
||||
margin_top = margin_bottom = margin_start = margin_end = 0;
|
||||
|
||||
flow_box = new Gtk.FlowBox();
|
||||
flow_box.homogeneous = true;
|
||||
flow_box.min_children_per_line = 1;
|
||||
flow_box.max_children_per_line = 1;
|
||||
flow_box.selection_mode = Gtk.SelectionMode.NONE;
|
||||
flow_box.valign = Gtk.Align.START;
|
||||
container = new IterableBox<T>(Gtk.Orientation.VERTICAL, 6);
|
||||
container.valign = Gtk.Align.START;
|
||||
|
||||
var scrolled_window = new Gtk.ScrolledWindow();
|
||||
scrolled_window.hscrollbar_policy = Gtk.PolicyType.NEVER;
|
||||
scrolled_window.hexpand = scrolled_window.vexpand = true;
|
||||
scrolled_window.add_css_class("card-container");
|
||||
scrolled_window.set_child(flow_box);
|
||||
scrolled_window.set_child(container);
|
||||
|
||||
append(scrolled_window);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
flow_box.remove_all();
|
||||
}
|
||||
|
||||
public void add(Card card) {
|
||||
flow_box.append(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
52
client/src/iterable_box.vala
Normal file
52
client/src/iterable_box.vala
Normal file
@ -0,0 +1,52 @@
|
||||
namespace StudySystemClient {
|
||||
public class IterableBox<T> : Gtk.Box {
|
||||
private List<T> elements;
|
||||
|
||||
public IterableBox(Gtk.Orientation orientation, int spacing) {
|
||||
this.orientation = orientation;
|
||||
this.spacing = spacing;
|
||||
elements = new List<T>();
|
||||
}
|
||||
|
||||
public new void append(T element) {
|
||||
elements.append(element);
|
||||
base.append(element as Gtk.Widget);
|
||||
}
|
||||
|
||||
public new void remove(T element) {
|
||||
elements.remove(element);
|
||||
base.remove(element as Gtk.Widget);
|
||||
}
|
||||
|
||||
public Iterator<Type, T> iterator() {
|
||||
return new Iterator<Type, T>(elements);
|
||||
}
|
||||
|
||||
public void sort(CompareDataFunc<T> comparison) {
|
||||
elements.sort_with_data(comparison);
|
||||
foreach (var element in elements)
|
||||
reorder_child_after(element as Gtk.Widget, null);
|
||||
}
|
||||
|
||||
public class Iterator<Type, T> {
|
||||
private unowned List<T> head;
|
||||
private unowned T value;
|
||||
|
||||
public Iterator(List<T> elements) {
|
||||
head = elements;
|
||||
}
|
||||
|
||||
public bool next() {
|
||||
if (head.is_empty())
|
||||
return false;
|
||||
value = head.data;
|
||||
head = head.next;
|
||||
return true;
|
||||
}
|
||||
|
||||
public unowned T get() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ lib = library(
|
||||
'client.vala',
|
||||
'connection.vala',
|
||||
'der.vala',
|
||||
'iterable_box.vala',
|
||||
'main_window.vala',
|
||||
'periodic.vala',
|
||||
'refresher.vala',
|
||||
|
Loading…
x
Reference in New Issue
Block a user