Skip to content
This repository has been archived by the owner on Mar 12, 2024. It is now read-only.

Commit

Permalink
feat: notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
lleyton committed Feb 5, 2024
1 parent fb348ee commit dddb2e4
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 46 deletions.
1 change: 0 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"mesonbuild.configureOnOpen": true,
"[meson]": {
"editor.defaultFormatter": "mesonbuild.mesonbuild"
},
Expand Down
3 changes: 3 additions & 0 deletions data/app.desktop.in.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[Desktop Entry]
Type = Application
Name = Skiff
Comment = Privacy-first end-to-end encrypted email
Icon = @ICON_NAME@
Exec = @COMMAND@
Categories = GTK;GNOME;Office;Email;Calendar;ContactManagement
DBusActivatable=true
X-GNOME-UsesNotifications=true
3 changes: 3 additions & 0 deletions data/app.service.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[D-BUS Service]
Name=@APP_ID@
Exec=@BINDIR@/@APP_ID@ --gapplication-service
7 changes: 0 additions & 7 deletions data/desktop.in.in

This file was deleted.

11 changes: 11 additions & 0 deletions data/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ if compile_schemas.found()
)
endif

dbus_service = configure_file(
input: 'app.service.in',
output: meson.project_name() + '.service',
configuration: {
'APP_ID': meson.project_name(),
'BINDIR': join_paths(get_option('prefix'), get_option('bindir')),
},
install: true,
install_dir: join_paths(get_option('datadir'), 'dbus-1', 'services'),
)

# GResources allow you to bundle and reference assets within your application.
# Resources are specified using a gresource file.
# For more information about GResources, see: https://docs.gtk.org/gio/struct.Resource.html
Expand Down
31 changes: 31 additions & 0 deletions src/Application.vala
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
public class SkiffDesktop.Application : He.Application {
private const GLib.ActionEntry APP_ENTRIES[] = {
{ "quit", quit },
{ "open-thread", action_open_thread, "s" },
{ "mark-as-read", action_mark_as_read, "s" },
{ "trash-thread", action_trash_thread, "s" },
{ "archive-thread", action_archive_thread, "s" },
};

public Application () {
Expand Down Expand Up @@ -33,4 +37,31 @@ public class SkiffDesktop.Application : He.Application {

new MainWindow (this);
}

// TODO: if someone wants to refactor this and throw the actions into enums, be my guest

private void action_open_thread (SimpleAction action, Variant? parameter) {
var thread_id = parameter.get_string (null);
debug ("Got open-thread action with thread id: %s", thread_id);
this.active_window?.present ();
(this.active_window as MainWindow)?.message_handler.send_notification_action (thread_id, "openThread");
}

private void action_mark_as_read (SimpleAction action, Variant? parameter) {
var thread_id = parameter.get_string (null);
debug ("Got mark-as-read action with thread id: %s", thread_id);
(this.active_window as MainWindow)?.message_handler.send_notification_action (thread_id, "markAsRead");
}

private void action_trash_thread (SimpleAction action, Variant? parameter) {
var thread_id = parameter.get_string (null);
debug ("Got trash-thread action with thread id: %s", thread_id);
(this.active_window as MainWindow)?.message_handler.send_notification_action (thread_id, "sendToTrash");
}

private void action_archive_thread (SimpleAction action, Variant? parameter) {
var thread_id = parameter.get_string (null);
debug ("Got archive-thread action with thread id: %s", thread_id);
(this.active_window as MainWindow)?.message_handler.send_notification_action (thread_id, "archive");
}
}
29 changes: 5 additions & 24 deletions src/MainWindow.vala
Original file line number Diff line number Diff line change
@@ -1,29 +1,15 @@
[GtkTemplate (ui = "/com/fyralabs/SkiffDesktop/mainwindow.ui")]
public class SkiffDesktop.MainWindow : He.ApplicationWindow {
private const GLib.ActionEntry APP_ENTRIES[] = {
private const GLib.ActionEntry WINDOW_ENTRIES[] = {
{ "about", action_about },
};
private const string BASE_URL = "https://app.skiff.com/";

[GtkChild]
private unowned Gtk.Box main_box;
private WebKit.WebView webview = new WebKit.WebView ();
private WebKit.UserScript script = new WebKit.UserScript (
"""
window.IsSkiffWindowsDesktop = true
window.chrome = {
webview: {
postMessage: (v) => window.webkit.messageHandlers.skiffDesktop.postMessage(v),
addEventListener: (_, listener) => window._skiffListener = listener,
removeEventListener: (_, listener) => delete window._skiffListener,
}
}
""",
WebKit.UserContentInjectedFrames.TOP_FRAME,
WebKit.UserScriptInjectionTime.START,
null,
null
);

public MessageHandler message_handler;

public MainWindow (He.Application application) {
Object (
Expand Down Expand Up @@ -107,12 +93,7 @@ public class SkiffDesktop.MainWindow : He.ApplicationWindow {
}

construct {
var message_handler = new MessageHandler ();

var content_manager = webview.get_user_content_manager ();
content_manager.add_script (script);
content_manager.script_message_received.connect (message_handler.on_script_message);
content_manager.register_script_message_handler ("skiffDesktop", null);
this.message_handler = new MessageHandler (webview);

var network_session = webview.get_network_session ();
var website_data_manager = network_session.get_website_data_manager ();
Expand Down Expand Up @@ -141,6 +122,6 @@ public class SkiffDesktop.MainWindow : He.ApplicationWindow {

webview.load_uri (BASE_URL);

add_action_entries (APP_ENTRIES, this);
add_action_entries (WINDOW_ENTRIES, this);
}
}
136 changes: 122 additions & 14 deletions src/MessageHandler.vala
Original file line number Diff line number Diff line change
@@ -1,40 +1,148 @@
public class SkiffDesktop.MessageHandler {
private class NotificationItem : Object {
private WebKit.WebView webview;
private WebKit.UserScript script = new WebKit.UserScript (
"""
window.IsSkiffWindowsDesktop = true
window.chrome = {
webview: {
postMessage: (v) => window.webkit.messageHandlers.skiffDesktop.postMessage(v),
addEventListener: (_, listener) => window._skiffListener = listener,
removeEventListener: (_, listener) => delete window._skiffListener,
}
}
""",
WebKit.UserContentInjectedFrames.TOP_FRAME,
WebKit.UserScriptInjectionTime.START,
null,
null
);

private class NotificationItem : Object, Json.Serializable {
public string title { get; set; }
public string body { get; set; }
public string threadID { get; set; }
public string emailID { get; set; }
public string thread_id { get; set; }
public string email_id { get; set; }

public override unowned ParamSpec? find_property (string name) {
switch (name) {
case "threadID":
name = "thread_id";
break;
case "emailID":
name = "email_id";
break;
}

return this.get_class ().find_property (name);
}
}

private class NotificationData : Object {
public NotificationItem[] notificationData { get; set; }
private class UnreadData : Object, Json.Serializable {
public int num_unread { get; set; }

public override unowned ParamSpec? find_property (string name) {
switch (name) {
case "numUnread":
name = "num_unread";
break;
}

return this.get_class ().find_property (name);
}
}

private void display_notification (NotificationItem notification_item) {
var notification = new Notification (notification_item.title);
notification.set_body (notification_item.body);

notification.set_default_action_and_target ("app.open-thread", "s", notification_item.thread_id);
notification.add_button_with_target(_("Mark As Read"), "app.mark-as-read", "s", notification_item.thread_id);
// notification.add_button_with_target(_("Mark As Spam"), "app.mark-as-spam", "s", notification_item.thread_id); kinda useless IMO (plus, goes over GNOME's limit :/)
notification.add_button_with_target(_("Trash"), "app.trash-thread", "s", notification_item.thread_id);
notification.add_button_with_target(_("Archive"), "app.archive-thread", "s", notification_item.thread_id);

GLib.Application.get_default ().send_notification ("meowy", notification);
}

private class UnreadData : Object {
public int numUnread { get; set; }
public void send_notification_action (string thread_id, string action) {
var root = new Json.Builder ()
.begin_object ()
.set_member_name ("type")
.add_string_value ("notificationAction")
.set_member_name ("data")
.begin_object ()
.set_member_name ("action")
.add_string_value (action)
.set_member_name ("threadId")
.add_string_value (thread_id)
.end_object ()
.end_object ()
.get_root ();

var generator = new Json.Generator ();
generator.set_root (root);

string? json_string = generator.to_data (null);

var dict = new VariantDict ();
dict.insert ("message", "s", json_string);
var args = dict.end ();

this.webview.call_async_javascript_function (
"""
if ('_skiffListener' in window) {
window._skiffListener({ data: message })
}
""",
-1,
args,
null,
null,
null
);
}

public void on_script_message (JSC.Value val) {
var parser = new Json.Parser ();
parser.load_from_data (val.to_string ());

var root = parser.get_root ();
var message_type = root.get_object ().get_string_member ("type");
var root_object = root.get_object ();
var message_type = root_object.get_string_member ("type");
var message_data = root_object.get_member ("data");

switch (message_type) {
case "unreadMailCount":
var unread_data = Json.gobject_deserialize (typeof (UnreadData), root) as UnreadData;
var unread_data = Json.gobject_deserialize (typeof (UnreadData), message_data) as UnreadData;
assert (unread_data != null);
print ("unread count: %d\n", unread_data.numUnread);

debug ("Received unread count of %d", unread_data.num_unread);
// TODO: do something with this count
break;
case "newMessageNotifications":
var notification_data = Json.gobject_deserialize (typeof (NotificationData), root) as NotificationData;
assert (notification_data != null);
print ("notifications: %d\n", notification_data.notificationData.length);
var notification_data = message_data.get_object ();
var notification_array = notification_data.get_array_member ("notificationData");

debug ("Received %u new message notifications", notification_array.get_length ());

foreach (var element in notification_array.get_elements ()) {
var notification_item = Json.gobject_deserialize (typeof (NotificationItem), element) as NotificationItem;
assert (notification_item != null);
display_notification (notification_item);
}
break;
default:
print ("Received unknown message %s\n", val.to_string ());
debug ("Received unknown message %s", val.to_string ());
break;
}
}

public MessageHandler (WebKit.WebView webview) {
this.webview = webview;

var content_manager = webview.get_user_content_manager ();
content_manager.add_script (this.script);
content_manager.script_message_received.connect (this.on_script_message);
content_manager.register_script_message_handler ("skiffDesktop", null);
}
}

0 comments on commit dddb2e4

Please sign in to comment.