Desktop Notifications

This commit is contained in:
Natsumi
2021-01-14 14:14:18 +13:00
parent bb33562bf3
commit d32c1f9078
7 changed files with 281 additions and 42 deletions

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
</startup>
</configuration>
</configuration>

View File

@@ -11,6 +11,10 @@ using System.Linq;
using System.Management;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.IO;
using System.Net;
using Windows.UI.Notifications;
using Windows.Data.Xml.Dom;
namespace VRCX
{
@@ -179,6 +183,30 @@ namespace VRCX
return CpuMonitor.Instance.CpuUsage;
}
public void DesktopNotification(string BoldText, string Text, string ImageURL = "")
{
XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastImageAndText02);
XmlNodeList stringElements = toastXml.GetElementsByTagName("text");
String imagePath = Path.Combine(Program.BaseDirectory, "cache\\toast");
if (ImageURL == String.Empty)
{
imagePath = Path.Combine(Program.BaseDirectory, "VRCX.ico");
}
else
{
using (var client = new WebClient())
{
client.DownloadFile(ImageURL, imagePath);
}
}
stringElements[0].AppendChild(toastXml.CreateTextNode(BoldText));
stringElements[1].AppendChild(toastXml.CreateTextNode(Text));
XmlNodeList imageElements = toastXml.GetElementsByTagName("image");
imageElements[0].Attributes.GetNamedItem("src").NodeValue = imagePath;
ToastNotification toast = new ToastNotification(toastXml);
ToastNotificationManager.CreateToastNotifier("VRCX").Show(toast);
}
public void SetStartup(bool enabled)
{
try

View File

@@ -8,7 +8,7 @@
<OutputType>WinExe</OutputType>
<RootNamespace>VRCX</RootNamespace>
<AssemblyName>VRCX</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
@@ -29,6 +29,7 @@
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -135,6 +136,7 @@
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
@@ -172,7 +174,10 @@
<Version>86.0.241</Version>
</PackageReference>
<PackageReference Include="DiscordRichPresence">
<Version>1.0.166</Version>
<Version>1.0.169</Version>
</PackageReference>
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications">
<Version>6.1.1</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>12.0.3</Version>
@@ -193,7 +198,7 @@
<Version>4.2.0</Version>
</PackageReference>
<PackageReference Include="System.Data.SQLite.Core">
<Version>1.0.113.6</Version>
<Version>1.0.113.7</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@@ -3436,7 +3436,7 @@ speechSynthesis.getVoices();
var { data } = this.gameLogTable;
var i = data.length;
var j = 0;
while (j < 25) {
while (j < 30) {
if (i <= 0) {
break;
}
@@ -3466,7 +3466,7 @@ speechSynthesis.getVoices();
var { data } = this.feedTable;
var i = data.length;
var j = 0;
while (j < 25) {
while (j < 30) {
if (i <= 0) {
break;
}
@@ -3515,8 +3515,8 @@ speechSynthesis.getVoices();
}
return 0;
});
if (arr.length > 25) {
arr.length = 25;
if (arr.length > 30) {
arr.length = 30;
}
sharedRepository.setArray('feeds', arr);
};
@@ -3835,23 +3835,16 @@ speechSynthesis.getVoices();
$app.data.orderFriendsGroup1 = configRepository.getBool('orderFriendGroup1');
$app.data.orderFriendsGroup2 = configRepository.getBool('orderFriendGroup2');
$app.data.orderFriendsGroup3 = configRepository.getBool('orderFriendGroup3');
$app.data.displayVRCPlusIconsAsAvatar = configRepository.getBool('displayVRCPlusIconsAsAvatar');
var saveOrderFriendGroup = function () {
configRepository.setBool('orderFriendGroup0', this.orderFriendsGroup0);
configRepository.setBool('orderFriendGroup1', this.orderFriendsGroup1);
configRepository.setBool('orderFriendGroup2', this.orderFriendsGroup2);
configRepository.setBool('orderFriendGroup3', this.orderFriendsGroup3);
configRepository.setBool('displayVRCPlusIconsAsAvatar', this.displayVRCPlusIconsAsAvatar);
};
$app.watch.orderFriendsGroup0 = saveOrderFriendGroup;
$app.watch.orderFriendsGroup1 = saveOrderFriendGroup;
$app.watch.orderFriendsGroup2 = saveOrderFriendGroup;
$app.watch.orderFriendsGroup3 = saveOrderFriendGroup;
$app.watch.displayVRCPlusIconsAsAvatar = saveOrderFriendGroup;
if (configRepository.getBool('displayVRCPlusIconsAsAvatar') === null) {
$app.data.displayVRCPlusIconsAsAvatar = true;
configRepository.setBool('displayVRCPlusIconsAsAvatar', $app.data.displayVRCPlusIconsAsAvatar);
}
$app.methods.fetchActiveFriend = function (userId) {
this.pendingActiveFriends.add(userId);
@@ -5706,7 +5699,9 @@ speechSynthesis.getVoices();
$app.data.hidePrivateFromFeed = configRepository.getBool('VRCX_hidePrivateFromFeed');
$app.data.hideDevicesFromFeed = configRepository.getBool('VRCX_hideDevicesFromFeed');
$app.data.overlayNotifications = configRepository.getBool('VRCX_overlayNotifications');
$app.data.desktopToast = configRepository.getBool('VRCX_desktopToast');
$app.data.minimalFeed = configRepository.getBool('VRCX_minimalFeed');
$app.data.displayVRCPlusIconsAsAvatar = configRepository.getBool('displayVRCPlusIconsAsAvatar');
$app.data.notificationTTS = configRepository.getBool('VRCX_notificationTTS');
$app.data.notificationTTSVoice = configRepository.getString('VRCX_notificationTTSVoice');
$app.data.notificationTimeout = configRepository.getString('VRCX_notificationTimeout');
@@ -5717,7 +5712,9 @@ speechSynthesis.getVoices();
configRepository.setBool('VRCX_hidePrivateFromFeed', this.hidePrivateFromFeed);
configRepository.setBool('VRCX_hideDevicesFromFeed', this.hideDevicesFromFeed);
configRepository.setBool('VRCX_overlayNotifications', this.overlayNotifications);
configRepository.setBool('VRCX_desktopToast', this.desktopToast);
configRepository.setBool('VRCX_minimalFeed', this.minimalFeed);
configRepository.setBool('displayVRCPlusIconsAsAvatar', this.displayVRCPlusIconsAsAvatar);
AppApi.RefreshVR();
};
$app.data.TTSvoices = speechSynthesis.getVoices();
@@ -5735,7 +5732,9 @@ speechSynthesis.getVoices();
$app.watch.hidePrivateFromFeed = saveOpenVROption;
$app.watch.hideDevicesFromFeed = saveOpenVROption;
$app.watch.overlayNotifications = saveOpenVROption;
$app.watch.desktopToast = saveOpenVROption;
$app.watch.minimalFeed = saveOpenVROption;
$app.watch.displayVRCPlusIconsAsAvatar = saveOpenVROption;
$app.watch.notificationTTS = saveNotificationTTS;
$app.data.isDarkMode = configRepository.getBool('isDarkMode');
$appDarkStyle.disabled = $app.data.isDarkMode === false;
@@ -5761,6 +5760,10 @@ speechSynthesis.getVoices();
$app.watch.isAutoLogin = saveVRCXWindowOption;
//setting defaults
if (configRepository.getBool('displayVRCPlusIconsAsAvatar') === null) {
$app.data.displayVRCPlusIconsAsAvatar = true;
configRepository.setBool('displayVRCPlusIconsAsAvatar', $app.data.displayVRCPlusIconsAsAvatar);
}
if (!configRepository.getString('VRCX_notificationPosition')) {
$app.data.notificationPosition = 'topCenter';
configRepository.setString('VRCX_notificationPosition', $app.data.notificationPosition);
@@ -5907,6 +5910,17 @@ speechSynthesis.getVoices();
}
$app.watch.isGameRunning = isGameRunningStateChange;
sharedRepository.setBool('is_Game_No_VR', false);
var isGameNoVRStateChange = function () {
sharedRepository.setBool('is_Game_No_VR', this.isGameNoVR);
}
$app.watch.isGameNoVR = isGameNoVRStateChange;
var lastLocationStateChange = function () {
sharedRepository.setString('last_location', $app.lastLocation);
}
$app.watch.lastLocation = lastLocationStateChange;
API.$on('LOGIN', function () {
$app.currentUserTreeData = [];
$app.pastDisplayNameTable.data = [];

View File

@@ -555,7 +555,12 @@ i.x-user-status.busy {
.options-container .header {
font-weight: bold;
font-size: 18px;
font-size: 20px;
}
.options-container .sub-header {
font-weight: bold;
font-size: 15px;
}
.options-container-item {

View File

@@ -557,18 +557,34 @@ html
div.options-container-item
span.name(style="min-width:137px") Overlay Button
el-switch(v-model="overlaybutton" inactive-text="Grip" active-text="Menu" :disabled="!openVR")
div.options-container-item
span.name Hide VR Devices
el-switch(v-model="hideDevicesFromFeed" :disabled="!openVR")
div.options-container-item
span.name Hide Private Worlds
el-switch(v-model="hidePrivateFromFeed" :disabled="!openVR")
br
span.sub-header Display Options
div.options-container-item
span.name Minimal Feed Icons
el-switch(v-model="minimalFeed" :disabled="!openVR")
div.options-container-item
span.name Hide Private Worlds
el-switch(v-model="hidePrivateFromFeed" :disabled="!openVR")
div.options-container-item
span.name Hide VR Devices
el-switch(v-model="hideDevicesFromFeed" :disabled="!openVR")
div.options-container-item
el-button(size="small" icon="el-icon-notebook-2" @click="showWristFeedFiltersDialog()" :disabled="!openVR") Wrist Feed Filters
br
span.sub-header Notification Options
div.options-container-item
span.name Overlay Notifications
el-switch(v-model="overlayNotifications" :disabled="!openVR")
div.options-container-item
span.name Desktop Notifications
el-switch(v-model="desktopToast" :disabled="!openVR")
div.options-container-item
el-button(size="small" icon="el-icon-chat-square" @click="showNotyFeedFiltersDialog()" :disabled="!openVR") Notification Filters
el-button(size="small" icon="el-icon-time" @click="promptNotificationTimeout()" :disabled="!overlayNotifications || !openVR") Notification Timeout
el-button(size="small" icon="el-icon-rank" @click="showNotificationPositionDialog()" :disabled="!overlayNotifications || !openVR") Notification Position
br
span.sub-header TTS Options
div.options-container-item
span.name Notification TTS
el-switch(v-model="notificationTTS" :disabled="!openVR")
@@ -577,13 +593,7 @@ html
el-dropdown(@command="(voice) => changeTTSVoice(voice)" trigger="click" size="small")
el-button(v-text="TTSvoices[notificationTTSVoice].name" size="mini" :disabled="!openVR || !notificationTTS")
el-dropdown-menu(#default="dropdown")
el-dropdown-item(v-if="voice" v-for="(voice, index) in TTSvoices" :key="index" v-text="voice.name" :command="index")
div.options-container-item
el-button(size="small" icon="el-icon-notebook-2" @click="showWristFeedFiltersDialog()" :disabled="!openVR") Wrist Feed Filters
el-button(size="small" icon="el-icon-chat-square" @click="showNotyFeedFiltersDialog()" :disabled="!overlayNotifications || !openVR") Notification Filters
div.options-container-item
el-button(size="small" icon="el-icon-time" @click="promptNotificationTimeout()" :disabled="!overlayNotifications || !openVR") Notification Timeout
el-button(size="small" icon="el-icon-rank" @click="showNotificationPositionDialog()" :disabled="!overlayNotifications || !openVR") Notification Position
el-dropdown-item(v-if="voice" v-for="(voice, index) in TTSvoices" :key="index" v-text="voice.name" :command="index")
div.options-container
span.header Application
div.options-container-item

View File

@@ -564,6 +564,113 @@ speechSynthesis.getVoices();
});
};
// API: User
API.cachedUsers = new Map();
API.$on('USER', function (args) {
args.ref = this.applyUser(args.json);
});
API.applyUser = function (json) {
var ref = this.cachedUsers.get(json.id);
if (ref === undefined) {
ref = {
id: '',
username: '',
displayName: '',
userIcon: '',
bio: '',
bioLinks: [],
currentAvatarImageUrl: '',
currentAvatarThumbnailImageUrl: '',
status: '',
statusDescription: '',
state: '',
tags: [],
developerType: '',
last_login: '',
last_platform: '',
allowAvatarCopying: false,
isFriend: false,
location: '',
worldId: '',
instanceId: '',
// VRCX
...json
};
this.cachedUsers.set(ref.id, ref);
} else {
var props = {};
for (var prop in ref) {
if (ref[prop] !== Object(ref[prop])) {
props[prop] = true;
}
}
var $ref = { ...ref };
Object.assign(ref, json);
for (var prop in ref) {
if (ref[prop] !== Object(ref[prop])) {
props[prop] = true;
}
}
var has = false;
for (var prop in props) {
var asis = $ref[prop];
var tobe = ref[prop];
if (asis === tobe) {
delete props[prop];
} else {
has = true;
props[prop] = [
tobe,
asis
];
}
}
}
return ref;
};
/*
params: {
userId: string
}
*/
API.getUser = function (params) {
return this.call(`users/${params.userId}`, {
method: 'GET'
}).then((json) => {
var args = {
json,
params
};
this.$emit('USER', args);
return args;
});
};
/*
params: {
userId: string
}
*/
API.getCachedUser = function (params) {
return new Promise((resolve, reject) => {
var ref = this.cachedUsers.get(params.userId);
if (ref === undefined) {
this.getUser(params).catch(reject).then(resolve);
} else {
resolve({
cache: true,
json: ref,
params,
ref
});
}
});
};
var $app = {
data: {
API,
@@ -574,17 +681,20 @@ speechSynthesis.getVoices();
currentUserStatus: null,
cpuUsage: 0,
isGameRunning: false,
isGameNoVR: false,
lastLocation: '',
lastFeedEntry: [],
feedFilters: [],
wristFeed: [],
notyMap: [],
devices: [],
desktopToastToggle: false,
overlayNotificationsToggle: false,
notificationTTSToggle: false,
notificationTTSVoice: '0',
hideDevicesToggle: false,
isMinimalFeed: false,
displayVRCPlusIconsAsAvatar: false,
notificationPosition: 'topCenter',
notificationTimeout: '3000',
notificationTheme: 'relax'
@@ -613,14 +723,13 @@ speechSynthesis.getVoices();
throw err;
}).then((args) => {
this.initConfigVars();
this.initNotyMap();
if (this.appType === '1') {
this.updateCpuUsageLoop();
}
if (this.appType === '2') {
this.initNotyMap();
}
this.updateLoop();
this.updateCpuUsageLoop();
this.$nextTick(function () {
if (this.appType === '1') {
this.$el.style.display = '';
}
});
return args;
});
}
@@ -630,9 +739,11 @@ speechSynthesis.getVoices();
this.notificationTTSToggle = configRepository.getBool('VRCX_notificationTTS');
this.notificationTTSVoice = configRepository.getString('VRCX_notificationTTSVoice');
this.overlayNotificationsToggle = configRepository.getBool('VRCX_overlayNotifications');
this.desktopToastToggle = configRepository.getBool('VRCX_desktopToast');
this.hidePrivateFromFeed = configRepository.getBool('VRCX_hidePrivateFromFeed');
this.hideDevicesToggle = configRepository.getBool('VRCX_hideDevicesFromFeed');
this.isMinimalFeed = configRepository.getBool('VRCX_minimalFeed');
this.displayVRCPlusIconsAsAvatar = configRepository.getBool('displayVRCPlusIconsAsAvatar');
this.feedFilters = JSON.parse(configRepository.getString('sharedFeedFilters'));
this.notificationPosition = configRepository.getString('VRCX_notificationPosition');
this.notificationTimeout = configRepository.getString('VRCX_notificationTimeout');
@@ -678,8 +789,9 @@ speechSynthesis.getVoices();
this.currentTime = new Date().toJSON();
this.currentUserStatus = sharedRepository.getString('current_user_status');
this.isGameRunning = sharedRepository.getBool('is_game_running');
this.isGameNoVR = sharedRepository.getBool('is_Game_No_VR');
this.lastLocation = sharedRepository.getString('last_location');
if (!this.hideDevicesToggle) {
if ((!this.hideDevicesToggle) && (this.appType === '1')) {
AppApi.GetVRDevices().then((devices) => {
devices.forEach((device) => {
device[2] = parseInt(device[2], 10);
@@ -824,8 +936,14 @@ speechSynthesis.getVoices();
if ((this.currentUserStatus === 'busy') || (!this.isGameRunning)) {
return;
}
notyToPlay.forEach(async (noty) => {
if (this.overlayNotificationsToggle) {
var bias = new Date(Date.now() - 60000).toJSON();
var noty = {};
for (var i = 0; i < notyToPlay.length; i++) {
noty = notyToPlay[i];
if (noty.created_at < bias) {
continue;
}
if ((this.overlayNotificationsToggle) && (!this.isGameNoVR)) {
var text = '';
switch (noty.type) {
case 'OnPlayerJoined':
@@ -927,7 +1045,66 @@ speechSynthesis.getVoices();
break;
}
}
});
if ((this.desktopToastToggle) && (this.isGameNoVR)) {
var imageURL = '';
if (noty.userId) {
await API.getCachedUser({
userId: noty.userId
}).catch((err) => {
throw err;
}).then((args) => {
imageURL = args.json.currentAvatarThumbnailImageUrl;
if ((this.displayVRCPlusIconsAsAvatar) && (args.json.userIcon)) {
imageURL = args.json.userIcon;
}
});
}
switch (noty.type) {
case 'OnPlayerJoined':
AppApi.DesktopNotification(noty.data, 'has joined', imageURL);
break;
case 'OnPlayerLeft':
AppApi.DesktopNotification(noty.data, 'has left', imageURL);
break;
case 'OnPlayerJoining':
AppApi.DesktopNotification(noty.data, 'is joining', imageURL);
break;
case 'GPS':
AppApi.DesktopNotification(noty.displayName, 'is in ' + await this.displayLocation(noty.location[0]), imageURL);
break;
case 'Online':
AppApi.DesktopNotification(noty.displayName, 'has logged in', imageURL);
break;
case 'Offline':
AppApi.DesktopNotification(noty.displayName, 'has logged out', imageURL);
break;
case 'Status':
AppApi.DesktopNotification(noty.displayName, `status is now ${noty.status[0].status} ${noty.status[0].statusDescription}`, imageURL);
break;
case 'invite':
AppApi.DesktopNotification(noty.senderUsername, `has invited you to ${noty.details.worldName}`, imageURL);
break;
case 'requestInvite':
AppApi.DesktopNotification(noty.senderUsername, 'has requested an invite', imageURL);
break;
case 'friendRequest':
AppApi.DesktopNotification(noty.senderUsername, 'has sent you a friend request', imageURL);
break;
case 'Friend':
AppApi.DesktopNotification(noty.displayName, 'has sent you a friend request', imageURL);
break;
case 'Unfriend':
AppApi.DesktopNotification(noty.displayName, 'has unfriended you', imageURL);
break;
case 'TrustLevel':
AppApi.DesktopNotification(noty.displayName, `trust level is now ${noty.trustLevel}`, imageURL);
break;
case 'DisplayName':
AppApi.DesktopNotification(noty.previousDisplayName, `changed their name to ${noty.displayName}`, imageURL);
break;
}
}
}
};
$app.methods.userStatusClass = function (user) {