Merge pull request #2000 from peerdavid/master
Refactoring and improvements of HomeAssistant, BWClock and Info App.pull/2003/head
|
@ -6,4 +6,5 @@
|
|||
0.06: Design and usability improvements.
|
||||
0.07: Improved positioning.
|
||||
0.08: Select the color of widgets correctly. Additional settings to hide colon.
|
||||
0.09: Larger font size if colon is hidden to improve readability further.
|
||||
0.09: Larger font size if colon is hidden to improve readability further.
|
||||
0.10: HomeAssistant integration if HomeAssistant is installed.
|
|
@ -7,6 +7,7 @@
|
|||
- Tab left/right of screen to show steps, temperature etc.
|
||||
- Enable / disable lock icon in the settings.
|
||||
- If the "sched" app is installed tab top / bottom of the screen to set the timer.
|
||||
- If HomeAssistant is installed, triggers are shown. Simple select the trigger and touch the middle of the screen to send the trigger to HomeAssistant.
|
||||
- The design is adapted to the theme of your bangle.
|
||||
- The colon (e.g. 7:35 = 735) can be hidden now in the settings.
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"id": "bwclk",
|
||||
"name": "BW Clock",
|
||||
"version": "0.09",
|
||||
"version": "0.10",
|
||||
"description": "BW Clock.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}],
|
||||
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
|
After Width: | Height: | Size: 3.3 KiB |
|
@ -1 +1,2 @@
|
|||
0.01: Release
|
||||
0.01: Release
|
||||
0.02: Includeas the ha.lib.js library that can be used by other apps or clocks.
|
|
@ -1,13 +1,15 @@
|
|||
# Home Assistant
|
||||
This app integrates your BangleJs into the HomeAssistant.
|
||||
|
||||
|
||||
# How to use
|
||||
Click on the left and right side of the screen to select the triggers that you
|
||||
configured. Click in the middle of the screen to send the trigger to HomeAssistant.
|
||||
|
||||

|
||||
|
||||
# First Setup
|
||||
|
||||
# Initial Setup
|
||||
1.) First of all, make sure that HomeAssistant and the HomeAssistant Android App works.
|
||||
|
||||
2.) Open your BangleJs Gadgetbridge App, click on the Settings icon of your BangleJs and enable "Allow Intent Access"
|
||||
|
@ -22,6 +24,7 @@ configured. Click in the middle of the screen to send the trigger to HomeAssista
|
|||
This setup must be done only once -- now you are ready to configure your BangleJS to
|
||||
control some devices or entities in your HomeAssistant :)
|
||||
|
||||
|
||||
# Setup Trigger
|
||||
1.) Upload the app and all corresponding triggers through the AppStore UI. You must specify
|
||||
the display name, the trigger as well as an icon.
|
||||
|
@ -38,12 +41,36 @@ The following icons are currently supported:
|
|||
|
||||
3.) Don't forget to select the action that should be executed at the bottom of each automation.
|
||||
|
||||
|
||||
# Default Trigger
|
||||
This app also implements two default trigger that can always be used:
|
||||
- APP_STARTED -- Will be sent whenever the app is started. So you could do some actions already when the app is sarted without the need of any user interaction.
|
||||
- TRIGGER -- Will be sent whenever some trigger is executed. So you could generically listen to that.
|
||||
|
||||
|
||||
# How to use the library (ha.lib.js) in my own app/clk
|
||||
This app inlcludes a library that can be used by other apps or clocks
|
||||
to read all configured intents or to send a trigger. Example code:
|
||||
|
||||
```js
|
||||
// First of all impport the library
|
||||
var ha = require("ha.lib.js");
|
||||
|
||||
// You can read all triggers that a user configured simply via
|
||||
var triggers = ha.getTriggers();
|
||||
|
||||
// Get display name and icon of trigger
|
||||
var display = triggers[0].display;
|
||||
var icon = triggers[0].getIcon();
|
||||
|
||||
// Trigger the first configured trigger
|
||||
ha.sendTrigger(triggers[0].trigger);
|
||||
|
||||
// Send a custom trigger that is not configured by a user
|
||||
ha.sendTrigger("MY_CUSTOM_TRIGGER");
|
||||
```
|
||||
|
||||
|
||||
# FAQ
|
||||
|
||||
## Sometimes the trigger is not executed
|
||||
|
|
|
@ -1,72 +1,10 @@
|
|||
var storage = require("Storage");
|
||||
/**
|
||||
* This app uses the ha library to send trigger to HomeAssistant.
|
||||
*/
|
||||
var ha = require("ha.lib.js");
|
||||
var W = g.getWidth(), H = g.getHeight();
|
||||
var position=0;
|
||||
|
||||
|
||||
// Note: All icons should have 48x48 pixels
|
||||
function getIcon(icon){
|
||||
if(icon == "light"){
|
||||
return {
|
||||
width : 48, height : 48, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("AAMBwAFE4AFDgYFJjgFBnAFBjwXBvAFBh4jBuAFCAQPwAQMHAQPgEQQCBEgcf/AvDn/8Aof//5GDAoJOBh+BAoOB+EP8YFB4fwgfnAoPnGANHAoPjHYQFBHYQFd44pDg47C4/gh/DIIZNFLIplGgF//wFIgZ9BRIUHRII7Ch4FBUIUOAoKzCjwFEhgCBmDpIVooFFh4oCAA4LFC5b7BAob1BAYI="))
|
||||
};
|
||||
} else if(icon == "door"){
|
||||
return {
|
||||
width : 48, height : 48, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("AAM4Aok/4AED///Aov4Aon8DgQGBAv4FpnIFKJv4FweAQFFAgQFB8AFDnADC"))
|
||||
};
|
||||
} else if (icon == "fire"){
|
||||
return {
|
||||
width : 48, height : 48, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("ABsDAokBwAFE4AFE8AFE+AFE/AFJgf8Aon+AocHAokP/8QAokYAoUfAok//88ApF//4kDAo//AgMQAgIFCjgFEjwFCOYIFFHQIFDn/+AoJ/BAoIqBAoN//xCBAoI5BDIPAgP//gFB8AFChYFBgf//EJAogOBAoSgBAoMHAQIFEFgXAAoJEBv4FCNoQFGVYd/wAFEYYIFIvwCBDoV8UwQCBcgUPwDwDfQMBaIYADA"))
|
||||
};
|
||||
}
|
||||
|
||||
// Default is always the HA icon
|
||||
return {
|
||||
width : 48, height : 48, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("AD8BwAFDg/gAocP+AFDj4FEn/8Aod//wFD/1+FAf4j+8AoMD+EPDAUH+OPAoUP+fPAoUfBYk/C4l/EYIwC//8n//FwIFEgYFD4EH+E8nkP8BdBAonjjk44/wj/nzk58/4gAFDF4PgCIMHAoPwhkwh4FB/EEkEfIIWAHwIFC4A+BAoXgg4FDL4IFDL4IFDLIYFkAEQA=="))
|
||||
};
|
||||
}
|
||||
|
||||
// Try to read custom actions, otherwise use default
|
||||
var triggers = [
|
||||
{display: "Not found.", trigger: "NOP", icon: "ha"},
|
||||
];
|
||||
|
||||
try{
|
||||
triggers = storage.read("ha.trigger.json");
|
||||
triggers = JSON.parse(triggers);
|
||||
} catch(e) {
|
||||
// In case there are no user triggers yet, we show the default...
|
||||
}
|
||||
|
||||
|
||||
function sendIntent(trigger){
|
||||
var retries=3;
|
||||
|
||||
while(retries > 0){
|
||||
try{
|
||||
// Send a startup trigger such that we could also execute
|
||||
// an action when the app is started :)
|
||||
Bluetooth.println(JSON.stringify({
|
||||
t:"intent",
|
||||
action:"com.espruino.gadgetbridge.banglejs.HA",
|
||||
extra:{
|
||||
trigger: trigger
|
||||
}})
|
||||
);
|
||||
retries = -1;
|
||||
|
||||
} catch(e){
|
||||
retries--;
|
||||
}
|
||||
}
|
||||
}
|
||||
var triggers = ha.getTriggers();
|
||||
|
||||
|
||||
function draw() {
|
||||
|
@ -78,7 +16,7 @@ function draw() {
|
|||
var w = g.stringWidth(trigger.display);
|
||||
|
||||
g.setFontAlign(-1,-1);
|
||||
var icon = getIcon(trigger.icon);
|
||||
var icon = trigger.getIcon();
|
||||
g.setColor(g.theme.fg).drawImage(icon, 12, H/5-2);
|
||||
g.drawString("Home", icon.width + 20, H/5);
|
||||
g.drawString("Assistant", icon.width + 18, H/5+24);
|
||||
|
@ -112,13 +50,11 @@ Bangle.on('touch', function(btn, e){
|
|||
}
|
||||
|
||||
if(!isRight && !isLeft){
|
||||
|
||||
// Send a default intent that we triggered something.
|
||||
sendIntent("TRIGGER");
|
||||
ha.sendTrigger("TRIGGER");
|
||||
|
||||
// Now send the selected trigger
|
||||
Bangle.buzz(80, 0.6).then(()=>{
|
||||
sendIntent(triggers[position].trigger);
|
||||
ha.sendTrigger(triggers[position].trigger);
|
||||
setTimeout(()=>{
|
||||
Bangle.buzz(80, 0.6);
|
||||
}, 250);
|
||||
|
@ -126,12 +62,14 @@ Bangle.on('touch', function(btn, e){
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
// Send intent that the we started the app.
|
||||
sendIntent("APP_STARTED");
|
||||
ha.sendTrigger("APP_STARTED");
|
||||
|
||||
// Next load the widgets and draw the app
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// Draw app
|
||||
draw();
|
||||
setWatch(_=>load(), BTN1);
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* This library can be used to read all triggers that a user
|
||||
* configured and send a trigger to homeassistant.
|
||||
*/
|
||||
function _getIcon(trigger){
|
||||
icon = trigger.icon;
|
||||
if(icon == "light"){
|
||||
return {
|
||||
width : 48, height : 48, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("AAMBwAFE4AFDgYFJjgFBnAFBjwXBvAFBh4jBuAFCAQPwAQMHAQPgEQQCBEgcf/AvDn/8Aof//5GDAoJOBh+BAoOB+EP8YFB4fwgfnAoPnGANHAoPjHYQFBHYQFd44pDg47C4/gh/DIIZNFLIplGgF//wFIgZ9BRIUHRII7Ch4FBUIUOAoKzCjwFEhgCBmDpIVooFFh4oCAA4LFC5b7BAob1BAYI="))
|
||||
};
|
||||
} else if(icon == "door"){
|
||||
return {
|
||||
width : 48, height : 48, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("AAM4Aok/4AED///Aov4Aon8DgQGBAv4FpnIFKJv4FweAQFFAgQFB8AFDnADC"))
|
||||
};
|
||||
} else if (icon == "fire"){
|
||||
return {
|
||||
width : 48, height : 48, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("ABsDAokBwAFE4AFE8AFE+AFE/AFJgf8Aon+AocHAokP/8QAokYAoUfAok//88ApF//4kDAo//AgMQAgIFCjgFEjwFCOYIFFHQIFDn/+AoJ/BAoIqBAoN//xCBAoI5BDIPAgP//gFB8AFChYFBgf//EJAogOBAoSgBAoMHAQIFEFgXAAoJEBv4FCNoQFGVYd/wAFEYYIFIvwCBDoV8UwQCBcgUPwDwDfQMBaIYADA"))
|
||||
};
|
||||
}
|
||||
|
||||
// Default is always the HA icon
|
||||
return {
|
||||
width : 48, height : 48, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("AD8BwAFDg/gAocP+AFDj4FEn/8Aod//wFD/1+FAf4j+8AoMD+EPDAUH+OPAoUP+fPAoUfBYk/C4l/EYIwC//8n//FwIFEgYFD4EH+E8nkP8BdBAonjjk44/wj/nzk58/4gAFDF4PgCIMHAoPwhkwh4FB/EEkEfIIWAHwIFC4A+BAoXgg4FDL4IFDL4IFDLIYFkAEQA=="))
|
||||
};
|
||||
}
|
||||
|
||||
exports.getTriggers = function(){
|
||||
var triggers = [
|
||||
{display: "Empty", trigger: "NOP", icon: "ha"},
|
||||
];
|
||||
|
||||
try{
|
||||
triggers = require("Storage").read("ha.trigger.json");
|
||||
triggers = JSON.parse(triggers);
|
||||
|
||||
// We lazy load all icons, otherwise, we have to keep
|
||||
// all the icons n times in memory which can be
|
||||
// problematic for embedded devices. Therefore,
|
||||
// we lazy load icons only if needed using the getIcon
|
||||
// method of each trigger...
|
||||
triggers.forEach(trigger => {
|
||||
trigger.getIcon = function(){
|
||||
return _getIcon(trigger);
|
||||
}
|
||||
})
|
||||
} catch(e) {
|
||||
// In case there are no user triggers yet, we show the default...
|
||||
}
|
||||
|
||||
return triggers;
|
||||
}
|
||||
|
||||
exports.sendTrigger = function(triggerName){
|
||||
var retries=3;
|
||||
|
||||
while(retries > 0){
|
||||
try{
|
||||
// Now lets send the trigger that we sould send.
|
||||
Bluetooth.println(JSON.stringify({
|
||||
t:"intent",
|
||||
action:"com.espruino.gadgetbridge.banglejs.HA",
|
||||
extra:{
|
||||
trigger: triggerName
|
||||
}})
|
||||
);
|
||||
retries = -1;
|
||||
|
||||
} catch(e){
|
||||
retries--;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "ha",
|
||||
"name": "HomeAssistant",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Integrates your BangleJS into HomeAssistant.",
|
||||
"icon": "ha.png",
|
||||
"type": "app",
|
||||
|
@ -19,6 +19,7 @@
|
|||
],
|
||||
"storage": [
|
||||
{"name":"ha.app.js","url":"ha.app.js"},
|
||||
{"name":"ha.lib.js","url":"ha.lib.js"},
|
||||
{"name":"ha.img","url":"ha.icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: Release
|
||||
0.01: Release
|
||||
0.02: Recfactoring and show weather data.
|
|
@ -1,27 +1,90 @@
|
|||
var s = require("Storage");
|
||||
const storage = require("Storage");
|
||||
const locale = require('locale');
|
||||
var ENV = process.env;
|
||||
var W = g.getWidth(), H = g.getHeight();
|
||||
var screen = 0;
|
||||
const maxScreen = 2;
|
||||
|
||||
|
||||
var screens = [
|
||||
{
|
||||
name: "General",
|
||||
items: [
|
||||
{name: "Steps", fun: () => getSteps()},
|
||||
{name: "HRM", fun: () => getBpm()},
|
||||
{name: "", fun: () => ""},
|
||||
{name: "Temp.", fun: () => getWeatherTemp()},
|
||||
{name: "Humidity", fun: () => getWeatherHumidity()},
|
||||
{name: "Wind", fun: () => getWeatherWind()},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Hardware",
|
||||
items: [
|
||||
{name: "Battery", fun: () => E.getBattery() + "%"},
|
||||
{name: "Charge?", fun: () => Bangle.isCharging() ? "Yes" : "No"},
|
||||
{name: "TempInt.", fun: () => locale.temp(parseInt(E.getTemperature()))},
|
||||
{name: "Bluetooth", fun: () => NRF.getSecurityStatus().connected ? "Conn" : "NoConn"},
|
||||
{name: "GPS", fun: () => Bangle.isGPSOn() ? "On" : "Off"},
|
||||
{name: "Compass", fun: () => Bangle.isCompassOn() ? "On" : "Off"},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Software",
|
||||
items: [
|
||||
{name: "Firmw.", fun: () => ENV.VERSION},
|
||||
{name: "Boot.", fun: () => getVersion("boot.info")},
|
||||
{name: "Settings.", fun: () => getVersion("setting.info")},
|
||||
{name: "Storage.", fun: () => ""},
|
||||
{name: " Total", fun: () => ENV.STORAGE>>10},
|
||||
{name: " Free", fun: () => require("Storage").getFree()>>10},
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
function getWeatherTemp(){
|
||||
try {
|
||||
var weather = storage.readJSON('weather.json').weather;
|
||||
return locale.temp(weather.temp-273.15);
|
||||
} catch(ex) { }
|
||||
|
||||
return "?";
|
||||
}
|
||||
|
||||
|
||||
function getWeatherHumidity(){
|
||||
try {
|
||||
var weather = storage.readJSON('weather.json').weather;
|
||||
return weather.hum = weather.hum + "%";
|
||||
} catch(ex) { }
|
||||
|
||||
return "?";
|
||||
}
|
||||
|
||||
|
||||
function getWeatherWind(){
|
||||
try {
|
||||
var weather = storage.readJSON('weather.json').weather;
|
||||
var speed = locale.speed(weather.wind).replace("mph", "");
|
||||
return Math.round(speed * 1.609344) + "kph";
|
||||
} catch(ex) { }
|
||||
|
||||
return "?";
|
||||
}
|
||||
|
||||
|
||||
function getVersion(file) {
|
||||
var j = s.readJSON(file,1);
|
||||
var j = storage.readJSON(file,1);
|
||||
var v = ("object"==typeof j)?j.version:false;
|
||||
return v?((v?"v"+v:"Unknown")):"NO ";
|
||||
}
|
||||
|
||||
|
||||
function drawData(name, value, y){
|
||||
g.drawString(name, 5, y);
|
||||
g.drawString(value, 100, y);
|
||||
}
|
||||
|
||||
function getSteps(){
|
||||
try{
|
||||
return Bangle.getHealthStatus("day").steps;
|
||||
} catch(e) {
|
||||
return ">= 2v12";
|
||||
return ">2v12";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,53 +92,36 @@ function getBpm(){
|
|||
try{
|
||||
return Math.round(Bangle.getHealthStatus("day").bpm) + "bpm";
|
||||
} catch(e) {
|
||||
return ">= 2v12";
|
||||
return ">2v12";
|
||||
}
|
||||
}
|
||||
|
||||
function drawData(name, value, y){
|
||||
g.drawString(name, 10, y);
|
||||
g.drawString(value, 100, y);
|
||||
}
|
||||
|
||||
function drawInfo() {
|
||||
g.reset().clearRect(Bangle.appRect);
|
||||
var h=18, y = h;//-h;
|
||||
|
||||
// Header
|
||||
g.setFont("Vector", h+2).setFontAlign(0,-1);
|
||||
g.drawString("--==|| INFO ||==--", W/2, 0);
|
||||
g.drawLine(0,25,W,25);
|
||||
g.drawLine(0,26,W,26);
|
||||
|
||||
// Info body depending on screen
|
||||
g.setFont("Vector",h).setFontAlign(-1,-1);
|
||||
screens[screen].items.forEach(function (item, index){
|
||||
drawData(item.name, item.fun(), y+=h);
|
||||
});
|
||||
|
||||
// Dynamic data
|
||||
if(screen == 0){
|
||||
drawData("Steps", getSteps(), y+=h);
|
||||
drawData("HRM", getBpm(), y+=h);
|
||||
drawData("Battery", E.getBattery() + "%", y+=h);
|
||||
drawData("Voltage", E.getAnalogVRef().toFixed(2) + "V", y+=h);
|
||||
drawData("IntTemp.", locale.temp(parseInt(E.getTemperature())), y+=h);
|
||||
}
|
||||
|
||||
if(screen == 1){
|
||||
drawData("Charging?", Bangle.isCharging() ? "Yes" : "No", y+=h);
|
||||
drawData("Bluetooth", NRF.getSecurityStatus().connected ? "Conn." : "Disconn.", y+=h);
|
||||
drawData("GPS", Bangle.isGPSOn() ? "On" : "Off", y+=h);
|
||||
drawData("Compass", Bangle.isCompassOn() ? "On" : "Off", y+=h);
|
||||
drawData("HRM", Bangle.isHRMOn() ? "On" : "Off", y+=h);
|
||||
}
|
||||
|
||||
// Static data
|
||||
if(screen == 2){
|
||||
drawData("Firmw.", ENV.VERSION, y+=h);
|
||||
drawData("Boot.", getVersion("boot.info"), y+=h);
|
||||
drawData("Settings", getVersion("setting.info"), y+=h);
|
||||
drawData("Storage", "", y+=h);
|
||||
drawData(" Total", ENV.STORAGE>>10, y+=h);
|
||||
drawData(" Free", require("Storage").getFree()>>10, y+=h);
|
||||
}
|
||||
|
||||
if(Bangle.isLocked()){
|
||||
g.setFont("Vector",h-2).setFontAlign(-1,-1);
|
||||
g.drawString("Locked", 0, H-h+2);
|
||||
}
|
||||
|
||||
// Bottom
|
||||
g.drawLine(0,H-h-3,W,H-h-3);
|
||||
g.drawLine(0,H-h-2,W,H-h-2);
|
||||
g.setFont("Vector",h-2).setFontAlign(-1,-1);
|
||||
g.drawString(screens[screen].name, 2, H-h+2);
|
||||
g.setFont("Vector",h-2).setFontAlign(1,-1);
|
||||
g.drawString((screen+1) + "/3", W, H-h+2);
|
||||
g.drawString((screen+1) + "/" + screens.length, W, H-h+2);
|
||||
}
|
||||
|
||||
drawInfo();
|
||||
|
@ -88,14 +134,15 @@ Bangle.on('touch', function(btn, e){
|
|||
var isRight = e.x > right;
|
||||
|
||||
if(isRight){
|
||||
screen = (screen + 1) % (maxScreen+1);
|
||||
screen = (screen + 1) % screens.length;
|
||||
}
|
||||
|
||||
if(isLeft){
|
||||
screen -= 1;
|
||||
screen = screen < 0 ? maxScreen : screen;
|
||||
screen = screen < 0 ? screens.length-1 : screen;
|
||||
}
|
||||
|
||||
Bangle.buzz(40, 0.6);
|
||||
drawInfo();
|
||||
});
|
||||
|
||||
|
@ -104,5 +151,4 @@ Bangle.on('lock', function(isLocked) {
|
|||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
// Bangle.drawWidgets();
|
||||
Bangle.drawWidgets();
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "info",
|
||||
"name": "Info",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "An application that displays information such as battery level, steps etc.",
|
||||
"icon": "info.png",
|
||||
"type": "app",
|
||||
|
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.5 KiB |