Add support for adaptive Windows 10 notifications

This commit is contained in:
LAGonauta 2021-03-23 06:52:31 -03:00
parent c40775b7da
commit 359dcbf70c
2 changed files with 147 additions and 69 deletions

View file

@ -6,9 +6,22 @@ using Xmpp;
namespace Dino.Plugins.WindowsNotification {
private delegate void NodeFunction(StanzaNode node);
public enum ActivationType {
Foreground,
Background
}
public enum Scenario {
Basic,
IncomingCall
}
private class Button {
public string title;
public string arguments;
public string imageUri;
public ActivationType activationType;
}
public class ToastNotificationBuilder {
@ -16,13 +29,15 @@ namespace Dino.Plugins.WindowsNotification {
private Gee.List<Button> _buttons = new Gee.ArrayList<Button>();
private string _header = null;
private string _body = null;
private string _imagePath = null;
private string _appLogo = null;
private string _inlineImage = null;
private Scenario _scenario = Scenario.Basic;
public ToastNotificationBuilder() {
}
public ToastNotificationBuilder AddButton(string title, string arguments) {
_buttons.add(new Button() { title = title, arguments = arguments });
public ToastNotificationBuilder AddButton(string title, string arguments, string? imageUri = null, ActivationType activationType = ActivationType.Foreground) {
_buttons.add(new Button() { title = title, arguments = arguments, imageUri = imageUri, activationType = activationType });
return this;
}
@ -36,73 +51,26 @@ namespace Dino.Plugins.WindowsNotification {
return this;
}
public ToastNotificationBuilder SetImage(string image_path) {
_imagePath = image_path;
public ToastNotificationBuilder SetAppLogo(string applogo_path) {
_appLogo = applogo_path;
return this;
}
public ToastNotificationBuilder SetScenario(Scenario scenario) {
_scenario = scenario;
return this;
}
public ToastNotificationBuilder SetInlineImage(string image_path) {
_inlineImage = image_path;
return this;
}
// Legacy templates, for both Windows 8.1 and Windows 10:
// https://docs.microsoft.com/en-us/previous-versions/windows/apps/hh761494(v=win.10)
// Eventually modern adaptive templates might be desired:
// https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=builder-syntax
public async ToastNotification Build() {
ToastTemplateType templateType = _header != null ? ToastTemplateType.ToastText02 : ToastTemplateType.ToastText01;
if (_imagePath != null) {
if (templateType == ToastTemplateType.ToastText02) {
templateType = ToastTemplateType.ToastImageAndText02;
} else {
templateType = ToastTemplateType.ToastImageAndText01;
if (!_supportsModernFeatures) {
return yield BuildFromLegacyTemplate();
}
}
var template = yield BuildStanzaFromXml(ToastNotificationManager.GetTemplateContent(templateType));
{ // add header and body
var attributes = new Gee.ArrayList<StanzaAttribute>();
attributes.add(new StanzaAttribute.build("", "id", "1"));
attributes.add(new StanzaAttribute.build("", "id", "2"));
var nodes = FindRecursive(template, "text", attributes);
foreach (var node in nodes) {
var attr = node.get_attribute_raw("id", "");
if (attr != null) {
if (attr.val == "1") {
if (templateType == ToastTemplateType.ToastText02 || templateType == ToastTemplateType.ToastImageAndText02) {
node.put_node(new StanzaNode.text(_header));
} else {
node.put_node(new StanzaNode.text(_body));
}
} else if (attr.val == "2") {
node.put_node(new StanzaNode.text(_body));
}
}
}
}
{ // add image
var nodes = FindRecursive(template, "image", null);
foreach (var node in nodes) {
var attr = node.get_attribute_raw("src", "");
if (attr != null) {
attr.val = _imagePath;
}
}
}
if (_supportsModernFeatures && _buttons.size > 0) { // allow buttons
var actions = new StanzaNode.build("actions", "", null, null);
foreach (var button in _buttons) {
var attributes = new Gee.ArrayList<StanzaAttribute>();
attributes.add(new StanzaAttribute.build("", "content", button.title));
attributes.add(new StanzaAttribute.build("", "arguments", button.arguments));
var action = new StanzaNode.build("action", "", null, attributes);
actions.get_all_subnodes().add(action);
}
template.get_all_subnodes().add(actions);
}
var xml = ToXml(template);
return new ToastNotification(xml);
return BuildWithToastGeneric();
}
private async StanzaNode BuildStanzaFromXml(string xml) {
@ -158,5 +126,115 @@ namespace Dino.Plugins.WindowsNotification {
ExecuteOnAllSubNodes(node, func);
}
}
// Legacy templates, works on both Windows 8.1 and Windows 10:
// https://docs.microsoft.com/en-us/previous-versions/windows/apps/hh761494(v=win.10)
private async ToastNotification BuildFromLegacyTemplate() {
ToastTemplateType templateType = _header != null ? ToastTemplateType.ToastText02 : ToastTemplateType.ToastText01;
if (_appLogo != null) {
if (templateType == ToastTemplateType.ToastText02) {
templateType = ToastTemplateType.ToastImageAndText02;
} else {
templateType = ToastTemplateType.ToastImageAndText01;
}
}
var template = yield BuildStanzaFromXml(ToastNotificationManager.GetTemplateContent(templateType));
{ // add header and body
var attributes = new Gee.ArrayList<StanzaAttribute>();
attributes.add(new StanzaAttribute.build("", "id", "1"));
attributes.add(new StanzaAttribute.build("", "id", "2"));
var nodes = FindRecursive(template, "text", attributes);
foreach (var node in nodes) {
var attr = node.get_attribute_raw("id", "");
if (attr != null) {
if (attr.val == "1") {
if (templateType == ToastTemplateType.ToastText02 || templateType == ToastTemplateType.ToastImageAndText02) {
node.put_node(new StanzaNode.text(_header));
} else {
node.put_node(new StanzaNode.text(_body));
}
} else if (attr.val == "2") {
node.put_node(new StanzaNode.text(_body));
}
}
}
}
{ // add image
var nodes = FindRecursive(template, "image", null);
foreach (var node in nodes) {
var attr = node.get_attribute_raw("src", "");
if (attr != null) {
attr.val = _appLogo;
}
}
}
var xml = ToXml(template);
return new ToastNotification(xml);
}
// Modern adaptive templates for Windows 10:
// https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=builder-syntax
private ToastNotification BuildWithToastGeneric() {
var toast = new StanzaNode.build("toast", "");
if (_scenario == Scenario.IncomingCall) {
toast.put_attribute("scenario", "incomingCall");
}
{ // add content
var visual = new StanzaNode.build("visual", "");
{
var binding = new StanzaNode.build("binding", "");
binding.put_attribute("template", "ToastGeneric");
if (_header != null) {
var header = new StanzaNode.build("text", "");
header.put_node(new StanzaNode.text(_header));
binding.put_node(header);
}
if (_body != null) {
var body = new StanzaNode.build("text", "");
body.put_node(new StanzaNode.text(_body));
binding.put_node(body);
}
if (_appLogo != null) {
var appLogo = new StanzaNode.build("image", "");
appLogo.put_attribute("placement", "appLogoOverride");
appLogo.put_attribute("src", _appLogo);
binding.put_node(appLogo);
}
if (_inlineImage != null) {
var inlineImage = new StanzaNode.build("image", "");
inlineImage.put_attribute("src", _inlineImage);
binding.put_node(inlineImage);
}
visual.put_node(binding);
}
toast.put_node(visual);
}
if (_buttons.size > 0) { // add actions
var actions = new StanzaNode.build("actions", "");
foreach (var button in _buttons) {
var action = new StanzaNode.build("action", "");
action.put_attribute("content", button.title);
action.put_attribute("arguments", button.arguments);
if (button.activationType == ActivationType.Background) {
action.put_attribute("activationType", "background");
}
actions.put_node(action);
}
toast.put_node(actions);
}
return new ToastNotification(ToXml(toast));
}
}
}

View file

@ -58,7 +58,7 @@ namespace Dino.Plugins.WindowsNotification {
var notification = yield new ToastNotificationBuilder()
.SetHeader(summary)
.SetBody(body)
.SetImage(image_path)
.SetAppLogo(image_path)
.AddButton(_("Accept"), "accept-subscription")
.AddButton(_("Deny"), "deny-subscription")
.Build();
@ -133,7 +133,7 @@ namespace Dino.Plugins.WindowsNotification {
var notification = yield new ToastNotificationBuilder()
.SetHeader(summary)
.SetBody(body)
.SetImage(image_path)
.SetAppLogo(image_path)
.AddButton(_("Accept"), "open-muc-join")
.AddButton(_("Deny"), "deny-invite")
.Build();
@ -170,7 +170,7 @@ namespace Dino.Plugins.WindowsNotification {
var notification = yield new ToastNotificationBuilder()
.SetHeader(summary)
.SetBody(body)
.SetImage(image_path)
.SetAppLogo(image_path)
.AddButton(_("Accept"), "accept-voice-request")
.AddButton(_("Deny"), "deny-voice-request")
.Build();
@ -204,7 +204,7 @@ namespace Dino.Plugins.WindowsNotification {
var notification = yield new ToastNotificationBuilder()
.SetHeader(conversation_display_name)
.SetBody(body)
.SetImage(image_path)
.SetAppLogo(image_path)
.Build();
var notification_id = generate_id();