Interactive Notifications v2

This commit is contained in:
Araxeus
2023-01-08 14:50:38 +02:00
parent 027d4ce3f0
commit 70522173b7
13 changed files with 305 additions and 273 deletions

View File

@ -1,31 +1,38 @@
const { notificationImage, icons, save_temp_icons } = require("./utils");
const { notificationImage, icons, save_temp_icons, secondsToMinutes, ToastStyles } = require("./utils");
const getSongControls = require('../../providers/song-controls');
const registerCallback = require("../../providers/song-info");
const { changeProtocolHandler } = require("../../providers/protocol-handler");
const { Notification, app } = require("electron");
const { Notification, app, ipcMain } = require("electron");
const path = require('path');
let songControls;
let config;
let savedNotification;
const config = require("./config");
module.exports = (win, _config) => {
let songControls;
let savedNotification;
// TODO create banner function
/** @param {Electron.BrowserWindow} win */
module.exports = (win) => {
songControls = getSongControls(win);
config = _config;
if (app.isPackaged && !config.smallInteractive) save_temp_icons();
let currentSeconds = 0;
ipcMain.on('apiLoaded', () => win.webContents.send('setupTimeChangedListener'));
ipcMain.on('timeChanged', (_, t) => currentSeconds = t);
if (app.isPackaged) save_temp_icons();
let lastSongInfo = { url: undefined };
// Register songInfoCallback
registerCallback(songInfo => {
if (!songInfo.isPaused && (songInfo.url !== lastSongInfo.url || config.unpauseNotification)) {
if (!songInfo.isPaused && (songInfo.url !== lastSongInfo.url || config.get("unpauseNotification"))) {
lastSongInfo = { ...songInfo };
sendXML(songInfo);
}
});
//TODO on app before close, close notification
// TODO on app before close, close notification
app.once("before-quit", () => {
savedNotification?.close();
});
@ -34,9 +41,9 @@ module.exports = (win, _config) => {
(cmd) => {
if (Object.keys(songControls).includes(cmd)) {
songControls[cmd]();
if (cmd === 'pause' || (cmd === 'play' && !config.unpauseNotification)) {
if (cmd === 'pause' || (cmd === 'play' && !config.get("unpauseNotification"))) {
setImmediate(() =>
sendXML({ ...lastSongInfo, isPaused: cmd === 'pause' })
sendXML({ ...lastSongInfo, isPaused: cmd === 'pause', elapsedSeconds: currentSeconds })
);
}
}
@ -45,20 +52,20 @@ module.exports = (win, _config) => {
}
function sendXML(songInfo) {
const imgSrc = notificationImage(songInfo, true);
const iconSrc = notificationImage(songInfo);
savedNotification?.close();
savedNotification = new Notification({
title: songInfo.title || "Playing",
body: songInfo.artist,
icon: imgSrc,
icon: iconSrc,
silent: true,
// https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/schema-root
// https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-schema
// https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=xml
// https://learn.microsoft.com/en-us/uwp/api/windows.ui.notifications.toasttemplatetype
toastXml: get_xml_custom(),
toastXml: get_xml(songInfo, iconSrc),
});
savedNotification.on("close", (_) => {
@ -68,209 +75,131 @@ function sendXML(songInfo) {
savedNotification.show();
}
const get_xml = (songInfo, iconSrc) => {
switch (config.get("style")) {
default:
case ToastStyles.logo:
case ToastStyles.legacy:
return xml_logo(songInfo, iconSrc);
case ToastStyles.banner_top_custom:
return xml_banner_top_custom(songInfo, iconSrc);
case ToastStyles.hero:
return xml_hero(songInfo, iconSrc);
case ToastStyles.banner_bottom:
return xml_banner_bottom(songInfo, iconSrc);
case ToastStyles.banner_centered_bottom:
return xml_banner_centered_bottom(songInfo, iconSrc);
case ToastStyles.banner_centered_top:
return xml_banner_centered_top(songInfo, iconSrc);
};
}
const iconLocation = app.isPackaged ?
path.resolve(app.getPath("userData"), 'icons') :
path.resolve(__dirname, '..', '..', 'assets/media-icons-black');
const display = (kind) => {
if (config.get("style") === ToastStyles.legacy ) {
return `content="${icons[kind]}"`;
} else {
return `\
content="${kind.charAt(0).toUpperCase() + kind.slice(1)}"\
imageUri="file:///${path.resolve(__dirname, iconLocation, `${kind}.png`)}"
`;
}
}
const getButton = (kind) =>
const getButton = (kind) =>
`<action ${display(kind)} activationType="protocol" arguments="youtubemusic://${kind}"/>`;
const display = (kind) =>
config.smallInteractive ?
`content="${icons[kind]}"` :
`content="${kind.charAt(0).toUpperCase() + kind.slice(1)}" imageUri="file:///${path.resolve(__dirname, iconLocation, `${kind}.png`)}"`;
const get_xml = (songInfo, options, imgSrc) => `
<toast useButtonStyles="true">
<audio silent="true" />
<visual>
<binding template="ToastImageAndText02">
<image id="1" src="${imgSrc}" name="Image" />
<text id="1">${songInfo.title}</text>
<text id="2">${songInfo.artist}</text>
</binding>
</visual>
const getButtons = (isPaused) => `\
<actions>
${getButton('previous')}
${songInfo.isPaused ? getButton('play') : getButton('pause')}
${isPaused ? getButton('play') : getButton('pause')}
${getButton('next')}
</actions>
</toast>`
</actions>\
`;
// **************************************************** //
// PREMADE TEMPLATES FOR TESTING
// DELETE AFTER TESTING
// **************************************************** //
const get_xml_custom = () => xml_banner_centered_top;
const xml_logo_ascii = `
<toast useButtonStyles="true">
const toast = (content, isPaused) => `\
<toast>
<audio silent="true" />
<visual>
<binding template="ToastGeneric">
<image id="1" src="file:///C:/Git/test/toasters/assets/surtur_rising_cover.jpg" name="Image" placement="appLogoOverride"/>
<text id="1">The Last Stand of Frej</text>
<text id="2">Amon Amarth</text>
${content}
</binding>
</visual>
<actions>
<action content="" activationType="protocol" arguments="youtubemusic://pause}"/>
<action content="‖" activationType="protocol" arguments="youtubemusic://pause}"/>
<action content="" activationType="protocol" arguments="youtubemusic://pause}"/>
</actions>
</toast>
`;
${getButtons(isPaused)}
</toast>`;
const xml_logo = ({title, artist, isPaused}, imgSrc) => toast(`\
<image id="1" src="${imgSrc}" name="Image" placement="appLogoOverride"/>
<text id="1">${title}</text>
<text id="2">${artist}</text>\
`, isPaused);
const xml_logo_icons_notext =`
<toast useButtonStyles="true">
<audio silent="true" />
<visual>
<binding template="ToastGeneric">
<image id="1" src="file:///C:/Git/test/toasters/assets/surtur_rising_cover.jpg" name="Image" placement="appLogoOverride"/>
<text id="1">The Last Stand of Frej</text>
<text id="2">Amon Amarth</text>
</binding>
</visual>
const xml_hero = ({title, artist, isPaused}, imgSrc) => toast(`\
<image id="1" src="${imgSrc}" name="Image" placement="hero"/>
<text id="1">${title}</text>
<text id="2">${artist}</text>\
`, isPaused);
<actions>
<action content=""
imageUri="file:///C:/Git/youtube-music/assets/media-icons-black/previous.png"
activationType="protocol" arguments="youtubemusic://pause}" />
<action content=""
imageUri="file:///C:/Git/youtube-music/assets/media-icons-black/pause.png"
activationType="protocol" arguments="youtubemusic://pause}" />
<action content=""
imageUri="file:///C:/Git/youtube-music/assets/media-icons-black/next.png"
activationType="protocol" arguments="youtubemusic://pause}" />
</actions>
</toast>
`;
const xml_banner_bottom = ({title, artist, isPaused}, imgSrc) => toast(`\
<image id="1" src="${imgSrc}" name="Image" />
<text id="1">${title}</text>
<text id="2">${artist}</text>\
`, isPaused);
const buttons_icons = `
<actions>
<action content="Previous"
imageUri="file:///C:/Git/youtube-music/assets/media-icons-black/previous.png"
activationType="protocol" arguments="youtubemusic://pause}" />
<action content="Pause"
imageUri="file:///C:/Git/youtube-music/assets/media-icons-black/pause.png"
activationType="protocol" arguments="youtubemusic://pause}" />
<action content="Next"
imageUri="file:///C:/Git/youtube-music/assets/media-icons-black/next.png"
activationType="protocol" arguments="youtubemusic://pause}" />
</actions>
`;
const xml_logo_icons = `
<toast useButtonStyles="true">
<audio silent="true" />
<visual>
<binding template="ToastGeneric">
<image id="1" src="file:///C:/Git/test/toasters/assets/surtur_rising_cover.jpg" name="Image" placement="appLogoOverride"/>
<text id="1">The Last Stand of Frej</text>
<text id="2">Amon Amarth</text>
</binding>
</visual>
${buttons_icons}
</toast>
`;
const xml_hero = `
<toast useButtonStyles="true">
<audio silent="true" />
<visual>
<binding template="ToastGeneric">
<image id="1" src="file:///C:/Git/test/toasters/assets/surtur_rising_banner.jpg" name="Image" placement="hero"/>
<text id="1">The Last Stand of Frej</text>
<text id="2">Amon Amarth</text>
</binding>
</visual>
${buttons_icons}
</toast>
`;
const xml_banner_bottom = `
<toast useButtonStyles="true">
<audio silent="true" />
<visual>
<binding template="ToastGeneric">
<image id="1" src="file:///C:/Git/test/toasters/assets/surtur_rising_banner.jpg" name="Image" />
<text id="1">The Last Stand of Frej</text>
<text id="2">Amon Amarth</text>
</binding>
</visual>
${buttons_icons}
</toast>
`;
const xml_banner_top_custom = `
<toast useButtonStyles="true">
<audio silent="true" />
<visual>
<binding template="ToastGeneric">
<image id="1" src="file:///C:/Git/test/toasters/assets/surtur_rising_banner.jpg" name="Image" />
const xml_banner_top_custom = (songInfo, imgSrc) => toast(`\
<image id="1" src="${imgSrc}" name="Image" />
<text></text>
<group>
<subgroup>
<text hint-style="body">The Last Stand of Frej</text>
<text hint-style="captionSubtle">Amon Amarth</text>
<text hint-style="body">${songInfo.title}</text>
<text hint-style="captionSubtle">${songInfo.artist}</text>
</subgroup>
<subgroup hint-textStacking="bottom">
<text hint-style="captionSubtle" hint-wrap="true" hint-align="right">Surtur Rising</text>
<text hint-style="captionSubtle" hint-wrap="true" hint-align="right">2011</text>
</subgroup>
</group>
</binding>
</visual>
${xml_more_data(songInfo)}
</group>\
`, songInfo.isPaused);
${buttons_icons}
</toast>
const xml_more_data = ({ album, elapsedSeconds, songDuration })=> `\
<subgroup hint-textStacking="bottom">
${album ?
`<text hint-style="captionSubtle" hint-wrap="true" hint-align="right">${album}</text>` : ''}
<text hint-style="captionSubtle" hint-wrap="true" hint-align="right">${secondsToMinutes(elapsedSeconds)} / ${secondsToMinutes(songDuration)}</text>
</subgroup>\
`;
const xml_banner_centered_bottom = `
<toast useButtonStyles="true">
<audio silent="true" />
<visual>
<binding template="ToastGeneric">
const xml_banner_centered_bottom = ({title, artist, isPaused}, imgSrc) => toast(`\
<text></text>
<group>
<subgroup hint-weight="1" hint-textStacking="center">
<text hint-align="center" hint-style="subHeader">The Last Stand of Frej</text>
<text hint-align="center" hint-style="SubtitleSubtle">Amon Amarth</text>
<text hint-align="center" hint-style="${titleFontPicker(title)}">${title}</text>
<text hint-align="center" hint-style="SubtitleSubtle">${artist}</text>
</subgroup>
</group>
<image id="1" src="file:///C:/Git/test/toasters/assets/surtur_rising_banner.jpg" name="Image" hint-removeMargin="true" />
</binding>
</visual>
<image id="1" src="${imgSrc}" name="Image" hint-removeMargin="true" />\
`, isPaused);
${buttons_icons}
</toast>
`;
const xml_banner_centered_top = `
<toast useButtonStyles="true">
<audio silent="true" />
<visual>
<binding template="ToastGeneric">
<image id="1" src="file:///C:/Git/test/toasters/assets/surtur_rising_banner.jpg" name="Image" />
const xml_banner_centered_top = ({title, artist, isPaused}, imgSrc) => toast(`\
<image id="1" src="${imgSrc}" name="Image" />
<text></text>
<group>
<subgroup hint-weight="1" hint-textStacking="center">
<text hint-align="center" hint-style="subHeader">The Last Stand of Frej</text>
<text hint-align="center" hint-style="SubtitleSubtle">Amon Amarth</text>
<text hint-align="center" hint-style="${titleFontPicker(title)}">${title}</text>
<text hint-align="center" hint-style="SubtitleSubtle">${artist}</text>
</subgroup>
</group>
</binding>
</visual>
</group>\
`, isPaused);
${buttons_icons}
</toast>
`;
const titleFontPicker = (title) => {
if (title.length <= 13) {
return 'Header';
} else if (title.length <= 22) {
return 'Subheader';
} else if (title.length <= 26) {
return 'Title';
} else {
return 'Subtitle';
}
}