diff --git a/core/Objects/Item.vala b/core/Objects/Item.vala index 5cdba8939..b73ae8b96 100644 --- a/core/Objects/Item.vala +++ b/core/Objects/Item.vala @@ -134,6 +134,14 @@ public class Objects.Item : Objects.BaseObject { } } + GLib.DateTime _completed_date; + public GLib.DateTime completed_date { + get { + _completed_date = Utils.Datetime.get_date_from_string (completed_at); + return _completed_date; + } + } + public bool has_parent { get { return Services.Database.get_default ().get_item (parent_id) != null; @@ -695,7 +703,7 @@ public class Objects.Item : Objects.BaseObject { }); } - public void update_async_timeout (string update_id = "") { + public void update_async_timeout (string update_id = "", string key = "") { if (update_timeout_id != 0) { Source.remove (update_timeout_id); } @@ -705,12 +713,12 @@ public class Objects.Item : Objects.BaseObject { loading = true; if (project.backend_type == BackendType.LOCAL) { - Services.Database.get_default ().update_item (this, update_id); + Services.Database.get_default ().update_item (this, update_id, key); loading = false; } else if (project.backend_type == BackendType.TODOIST) { Services.Todoist.get_default ().update.begin (this, (obj, res) => { Services.Todoist.get_default ().update.end (res); - Services.Database.get_default ().update_item (this, update_id); + Services.Database.get_default ().update_item (this, update_id, key); loading = false; }); } else if (project.backend_type == BackendType.CALDAV) { @@ -718,7 +726,7 @@ public class Objects.Item : Objects.BaseObject { HttpResponse response = Services.CalDAV.Core.get_default ().add_task.end (res); if (response.status) { - Services.Database.get_default ().update_item (this, update_id); + Services.Database.get_default ().update_item (this, update_id, key); } loading = false; diff --git a/core/Objects/ObjectEvent.vala b/core/Objects/ObjectEvent.vala new file mode 100644 index 000000000..7b9457650 --- /dev/null +++ b/core/Objects/ObjectEvent.vala @@ -0,0 +1,109 @@ +/* +* Copyright © 2024 Alain M. (https://github.com/alainm23/planify) +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public +* License as published by the Free Software Foundation; either +* version 3 of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301 USA +* +* Authored by: Alain M. +*/ + +public class Objects.ObjectEvent : GLib.Object { + public int64 id { get; set; default = 0; } + public string event_date { get; set; default = ""; } + public string event_type { get; set; default = ""; } + public string extra_data { get; set; default = ""; } + public string object_id { get; set; default = ""; } + public string object_type { get; set; default = ""; } + public string parent_item_id { get; set; default = ""; } + public string parent_project_id { get; set; default = ""; } + + public static Objects.ObjectEvent for_add_item (Objects.Item item) { + Objects.ObjectEvent return_value = new Objects.ObjectEvent (); + + return_value.event_date = new GLib.DateTime.now_local ().to_string (); + return_value.event_type = "added"; + return_value.object_type = "item"; + return_value.object_id = item.id; + return_value.parent_project_id = item.project_id; + return_value.extra_data = generate_extradata ("content", item.content, item.project.name, item.project.color); + + return return_value; + } + + public static Objects.ObjectEvent for_update_item (Objects.Item item, string key) { + Objects.ObjectEvent return_value = new Objects.ObjectEvent (); + + return_value.event_date = new GLib.DateTime.now_local ().to_string (); + return_value.event_type = "updated"; + return_value.object_type = "item"; + return_value.object_id = item.id; + return_value.parent_project_id = item.project_id; + return_value.extra_data = generate_extradata ("content", item.content, item.project.name, item.project.color); + + return return_value; + } + + public static string generate_extradata (string key, string value, string parent_project_name, string parent_project_color) { + var builder = new Json.Builder (); + + builder.begin_object (); + + builder.set_member_name ("client"); + builder.add_string_value ("Planify"); + + builder.set_member_name (key); + builder.add_string_value (value); + + builder.set_member_name ("parent_project_color"); + builder.add_string_value (parent_project_color); + + builder.set_member_name ("parent_project_name"); + builder.add_string_value (parent_project_name); + + builder.end_object (); + + Json.Generator generator = new Json.Generator (); + Json.Node root = builder.get_root (); + generator.set_root (root); + + return generator.to_data (null); + } + + public static string _generate_extradata (Objects.Item item, string key) { + var builder = new Json.Builder (); + + builder.begin_object (); + + builder.set_member_name ("client"); + builder.add_string_value ("Planify"); + + builder.set_member_name ("content"); + builder.add_string_value (item.content); + + builder.set_member_name ("parent_project_color"); + builder.add_string_value (item.project.color); + + builder.set_member_name ("parent_project_name"); + builder.add_string_value (item.project.name); + + builder.end_object (); + + Json.Generator generator = new Json.Generator (); + Json.Node root = builder.get_root (); + generator.set_root (root); + + return generator.to_data (null); + } +} \ No newline at end of file diff --git a/core/QuickAdd.vala b/core/QuickAdd.vala index a3de8f540..23c00a23e 100644 --- a/core/QuickAdd.vala +++ b/core/QuickAdd.vala @@ -4,8 +4,7 @@ public class Layouts.QuickAdd : Adw.Bin { private Gtk.Entry content_entry; private Widgets.LoadingButton submit_button; - private Widgets.Markdown.Buffer current_buffer; - private Widgets.Markdown.EditView markdown_edit_view; + private Widgets.HyperTextView description_textview; private Widgets.ItemLabels item_labels; private Widgets.ProjectPicker.ProjectPickerButton project_picker_button; private Widgets.ScheduleButton schedule_button; @@ -76,16 +75,16 @@ public class Layouts.QuickAdd : Adw.Bin { content_box.append (content_entry); - current_buffer = new Widgets.Markdown.Buffer (); - - markdown_edit_view = new Widgets.Markdown.EditView () { + description_textview = new Widgets.HyperTextView (_("Add a description…")) { + height_request = 64, left_margin = 14, right_margin = 6, top_margin = 6, - connect_typing = false + wrap_mode = Gtk.WrapMode.WORD_CHAR, + hexpand = true }; - - markdown_edit_view.buffer = current_buffer; + + description_textview.remove_css_class ("view"); item_labels = new Widgets.ItemLabels (item) { margin_start = 6, @@ -128,11 +127,11 @@ public class Layouts.QuickAdd : Adw.Bin { quick_add_content.add_css_class ("card"); quick_add_content.add_css_class ("sidebar-card"); quick_add_content.append (content_box); - quick_add_content.append (markdown_edit_view); + quick_add_content.append (description_textview); quick_add_content.append (item_labels); quick_add_content.append (action_box); - submit_button = new Widgets.LoadingButton (LoadingButtonType.LABEL, _("Add")) { + submit_button = new Widgets.LoadingButton (LoadingButtonType.LABEL, _("Add To-Do")) { valign = CENTER, css_classes = { "suggested-action", "border-radius-6" } }; @@ -281,15 +280,15 @@ public class Layouts.QuickAdd : Adw.Bin { return false; }); - // var description_controller_key = new Gtk.EventControllerKey (); - // description_textview.add_controller (description_controller_key); - // description_controller_key.key_pressed.connect ((keyval, keycode, state) => { - // if ((ctrl_pressed || shift_pressed) && keyval == 65293) { - // add_item (); - // } + var description_controller_key = new Gtk.EventControllerKey (); + description_textview.add_controller (description_controller_key); + description_controller_key.key_pressed.connect ((keyval, keycode, state) => { + if ((ctrl_pressed || shift_pressed) && keyval == 65293) { + add_item (); + } - // return false; - // }); + return false; + }); var event_controller_key = new Gtk.EventControllerKey (); ((Gtk.Widget) this).add_controller (event_controller_key); @@ -331,7 +330,7 @@ public class Layouts.QuickAdd : Adw.Bin { } item.content = content_entry.get_text (); - item.description = current_buffer.get_all_text ().chomp (); + item.description = description_textview.get_text (); if (item.project.backend_type == BackendType.LOCAL) { item.id = Util.get_default ().generate_id (); @@ -381,7 +380,7 @@ public class Layouts.QuickAdd : Adw.Bin { reset_item (); content_entry.text = ""; - current_buffer.set_text (""); + description_textview.set_text (""); schedule_button.reset (); priority_button.reset (); pin_button.reset (); @@ -481,4 +480,4 @@ public class Layouts.QuickAdd : Adw.Bin { item.child_order = index; item.custom_order = true; } -} +} \ No newline at end of file diff --git a/core/Services/Database.vala b/core/Services/Database.vala index abd09edd8..199f796fc 100644 --- a/core/Services/Database.vala +++ b/core/Services/Database.vala @@ -383,6 +383,23 @@ public class Services.Database : GLib.Object { ); """; + if (db.exec (sql, null, out errormsg) != Sqlite.OK) { + warning (errormsg); + } + + sql = """ + CREATE TABLE IF NOT EXISTS ObjectEvents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + event_date TEXT, + event_type TEXT, + extra_data TEXT, + object_id TEXT, + object_type TEXT, + parent_item_id TEXT, + parent_project_id TEXT + ); + """; + if (db.exec (sql, null, out errormsg) != Sqlite.OK) { warning (errormsg); } @@ -1277,6 +1294,7 @@ public class Services.Database : GLib.Object { if (stmt.step () == Sqlite.DONE) { add_item (item, insert); + // add_item_event (Objects.ObjectEvent.for_add_item (item)); } else { warning ("Error: %d: %s", db.errcode (), db.errmsg ()); } @@ -1692,7 +1710,7 @@ public class Services.Database : GLib.Object { stmt.reset (); } - public void update_item (Objects.Item item, string update_id = "") { + public void update_item (Objects.Item item, string update_id = "", string key = "") { item.updated_at = new GLib.DateTime.now_local ().to_string (); Sqlite.Statement stmt; @@ -1730,6 +1748,7 @@ public class Services.Database : GLib.Object { if (stmt.step () == Sqlite.DONE) { item.updated (update_id); item_updated (item, update_id); + // add_item_event (Objects.ObjectEvent.for_update_item (item, key)); } else { warning ("Error: %d: %s", db.errcode (), db.errmsg ()); } @@ -1843,6 +1862,29 @@ public class Services.Database : GLib.Object { } } + private void add_item_event (Objects.ObjectEvent object_event) { + Sqlite.Statement stmt; + + sql = """ + INSERT OR IGNORE INTO ObjectEvents (event_date, event_type, extra_data, object_id, object_type, parent_project_id) + VALUES ($event_date, $event_type, $extra_data, $object_id, $object_type, $parent_project_id); + """; + + db.prepare_v2 (sql, sql.length, out stmt); + set_parameter_str (stmt, "$event_date", object_event.event_date); + set_parameter_str (stmt, "$event_type", object_event.event_type); + set_parameter_str (stmt, "$extra_data", object_event.extra_data); + set_parameter_str (stmt, "$object_id", object_event.object_id); + set_parameter_str (stmt, "$object_type", object_event.object_type); + set_parameter_str (stmt, "$parent_project_id", object_event.parent_project_id); + + if (stmt.step () != Sqlite.DONE) { + warning ("Error: %d: %s", db.errcode (), db.errmsg ()); + } + + stmt.reset (); + } + /* Quick Find */ diff --git a/core/meson.build b/core/meson.build index c76f2a346..06064d5a1 100644 --- a/core/meson.build +++ b/core/meson.build @@ -82,6 +82,7 @@ core_files = files( 'Objects/Section.vala', 'Objects/Promise.vala', 'Objects/Attachment.vala', + 'Objects/ObjectEvent.vala', 'Objects/Filters/Pinboard.vala', 'Objects/Filters/Scheduled.vala', diff --git a/src/Layouts/ItemRow.vala b/src/Layouts/ItemRow.vala index 8834eca5d..4760819aa 100644 --- a/src/Layouts/ItemRow.vala +++ b/src/Layouts/ItemRow.vala @@ -819,14 +819,18 @@ public class Layouts.ItemRow : Layouts.ItemBase { private void update_content_description () { - if (item.content != content_textview.buffer.text || - item.description != current_buffer.get_all_text ().chomp ()) { + if (item.content != content_textview.buffer.text) { item.content = content_textview.buffer.text; content_label.label = Util.get_default ().markup_string (item.content); content_label.tooltip_text = item.content; + item.update_async_timeout (update_id); + return; + } + if (item.description != current_buffer.get_all_text ().chomp ()) { item.description = current_buffer.get_all_text ().chomp (); item.update_async_timeout (update_id); + return; } } diff --git a/src/Layouts/SectionRow.vala b/src/Layouts/SectionRow.vala index 83659a643..53be3c360 100644 --- a/src/Layouts/SectionRow.vala +++ b/src/Layouts/SectionRow.vala @@ -620,18 +620,10 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { Objects.Item item2 = ((Layouts.ItemRow) lbbefore).item; if (section.project.sort_order == 0 || section.project.sort_order == 2) { - if (item1.has_due && item2.has_due) { - var date1 = item1.due.datetime; - var date2 = item2.due.datetime; - - return date1.compare (date2); - } + var date1 = item1.completed_date; + var date2 = item2.completed_date; - if (!item1.has_due && item2.has_due) { - return 1; - } - - return 0; + return date2.compare (date1); } if (section.project.sort_order == 1) {