1
0
Fork 0

Merge pull request #1661 from peerdavid/master

New and updated apps using the new "sched" library
master
Gordon Williams 2022-04-06 16:36:23 +01:00 committed by GitHub
commit 37d2ca48e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 979 additions and 116 deletions

2
apps/90sclk/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Fullscreen settings.

13
apps/90sclk/README.md Normal file
View File

@ -0,0 +1,13 @@
# 90s Clock
A watch face in 90s style:
![](screenshot_2.png)
Fullscreen mode can be enabled in the settings:
![](screenshot.png)
## Creator
- [David Peer](https://github.com/peerdavid)

1
apps/90sclk/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgc8+fAgEgwAMDvPnz99BYdl2weHtu27ft2AGBiEcuEAhAPDg4jGgECIRMN23fthUNgP374vBAB3gAgc/gAXNjlx4EDxwJEpAjG/6IBjkBL4UAjVgBAJuCgPHBQMFEIkkyQjFhwEClgXBEYNBwkQJoibCBwNFBAUCEAVAQZAjC/8euPHDon//hKB//xEYMP//jBYP/+ARDNYM///+EYIgBj1B/8fCIUhEYQRB//FUIM/EZU4EYMkEYP/8VhEYUH/gRBWAUfI4MD+AjBoAsBwEH8EB/EDwE4HwYjCuEHWAOHgExEYKbBCIZNB8fAEYQHByE/EwPABAY+BgRHDBANyJQXHNwIjD8CSBj/+BwMSTwOOBYK2D/4CCNYZQB/iJBQwYjCCIcAgeBSoOAWYQjEVoIRCNAIjKAQKJBgAFC8ZoCWwJbDABMHGQPAAoMQB5EDx/4A4gqBZwIGCWwIABuBWC4EBZwPgv/AcwS/EAAcIU4IRBVQIRKEwIjBv0ARIUDCJIjD//x/ARK/5HC/+BCJkcI45uDgECUgQjCWAM4WwUBWYanEAA8cTARWBEYUC5RAHw1YgEOFQXADQPHIIkAhgICuARBh0A23blhHBagIKBsOGjNswhHDEYUUAoTUBhkxEYMwKwU503bvuwXILmCEYMYsumWYYjB85lDEYovBEYXm7fs25EBI4kYtOWNwIjD4+8NYsw4YjGz9/2hrEoOGjVBwE4NYdzNYSwBuEDEYcxaIUA8+atugGogjBiVgWAI"))

144
apps/90sclk/app.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/90sclk/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
apps/90sclk/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

18
apps/90sclk/metadata.json Normal file
View File

@ -0,0 +1,18 @@
{
"id": "90sclk",
"name": "90s Clock",
"version": "0.02",
"description": "A 90s style watch-face",
"readme": "README.md",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_2.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"90sclk.app.js","url":"app.js"},
{"name":"90sclk.img","url":"app-icon.js","evaluate":true},
{"name":"90sclk.settings.js","url":"settings.js"}
]
}

BIN
apps/90sclk/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

31
apps/90sclk/settings.js Normal file
View File

@ -0,0 +1,31 @@
(function(back) {
const SETTINGS_FILE = "90sclk.setting.json";
// initialize with default settings...
const storage = require('Storage')
let settings = {
fullscreen: false,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
settings[key] = saved_settings[key]
}
function save() {
storage.write(SETTINGS_FILE, settings)
}
E.showMenu({
'': { 'title': '90s Clock' },
'< Back': back,
'Full Screen': {
value: settings.fullscreen,
format: () => (settings.fullscreen ? 'Yes' : 'No'),
onchange: () => {
settings.fullscreen = !settings.fullscreen;
save();
},
}
});
})

5
apps/bwclk/ChangeLog Normal file
View File

@ -0,0 +1,5 @@
0.01: New App.
0.02: Use build in function for steps and other improvements.
0.03: Adapt colors based on the theme of the user.
0.04: Steps can be hidden now such that the time is even larger.
0.05: Included icons for information.

16
apps/bwclk/README.md Normal file
View File

@ -0,0 +1,16 @@
# Black & White clock
![](screenshot.png)
## Features
- Fullscreen on/off
- The design is adapted to the theme of your bangle.
- 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.
## Thanks to
<a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a>
## Creator
- [David Peer](https://github.com/peerdavid)

1
apps/bwclk/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgIcah0EgEB/H8iFsAoOY4kMBYMDhmGgXkAoUGiWkAoQQBoAFCjgnCAoM4hgFDuEI+wpC8EKyg1C/0eAoMAsEAiQvBAAeAApQAB/4Ao+P4v/wn0P8Pgn/wnkH4Pjv/j/nn9PH//n/nj/IFF4F88AXBAoM88EcAoPHj//jlDAoOf/+Y+YFHjnnjAjBEIIjD+BHDO9IALA=="))

412
apps/bwclk/app.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/bwclk/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

18
apps/bwclk/metadata.json Normal file
View File

@ -0,0 +1,18 @@
{
"id": "bwclk",
"name": "BlackWhite Clock",
"version": "0.05",
"description": "Black and white clock.",
"readme": "README.md",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"bwclk.app.js","url":"app.js"},
{"name":"bwclk.img","url":"app-icon.js","evaluate":true},
{"name":"bwclk.settings.js","url":"settings.js"}
]
}

BIN
apps/bwclk/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
apps/bwclk/screenshot_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

40
apps/bwclk/settings.js Normal file
View File

@ -0,0 +1,40 @@
(function(back) {
const SETTINGS_FILE = "bwclk.setting.json";
// initialize with default settings...
const storage = require('Storage')
let settings = {
fullscreen: false,
showLock: true,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
settings[key] = saved_settings[key]
}
function save() {
storage.write(SETTINGS_FILE, settings)
}
E.showMenu({
'': { 'title': 'BlackWhite Clock' },
'< Back': back,
'Fullscreen': {
value: settings.fullscreen,
format: () => (settings.fullscreen ? 'Yes' : 'No'),
onchange: () => {
settings.fullscreen = !settings.fullscreen;
save();
},
},
'Show Lock': {
value: settings.showLock,
format: () => (settings.showLock ? 'Yes' : 'No'),
onchange: () => {
settings.showLock = !settings.showLock;
save();
},
}
});
})

View File

@ -17,3 +17,4 @@
0.17: Settings for mph/kph and other minor improvements.
0.18: Fullscreen mode can now be enabled or disabled in the settings.
0.19: Alarms can not go bigger than 100.
0.20: Use alarm for alarm functionality instead of own implementation.

View File

@ -3,7 +3,8 @@
A simple LCARS inspired clock.
Note: To display the steps, the wpedom app is required. To show weather data
such as temperature, humidity or window you BangleJS must be connected
with Gadgetbride and the weather app must be installed.
with Gadgetbride and the weather app must be installed. To use the timer
the "sched" app must be installed on your device.
## Control
* Tap left / right to change between screens.
@ -15,7 +16,7 @@ with Gadgetbride and the weather app must be installed.
* Tab on left/right to switch between different screens.
* Cusomizable data that is shown on screen 1 (steps, weather etc.)
* Shows random and real images of planets.
* Tap on top/bottom of screen 1 to activate an alarm.
* Tap on top/bottom of screen 1 to activate an alarm. Depends on widtmr.
* The lower orange line indicates the battery level.
* Display graphs (day or month) for steps + hrm on the second screen.
@ -36,8 +37,9 @@ Access different screens via tap on the left/ right side of the screen
![](screenshot_1.png)
![](screenshot_2.png)
## Creator
- [David Peer](https://github.com/peerdavid)
## Contributors
- [David Peer](https://github.com/peerdavid).
- [Adam Schmalhofer](https://github.com/adamschmalhofer).
- [Jon Warrington](https://github.com/BartokW).
- [Adam Schmalhofer](https://github.com/adamschmalhofer)
- [Jon Warrington](https://github.com/BartokW)

View File

@ -1,6 +1,7 @@
const TIMER_IDX = "lcars";
const SETTINGS_FILE = "lcars.setting.json";
const locale = require('locale');
const storage = require('Storage')
const storage = require('Storage');
let settings = {
alarm: -1,
dataRow1: "Steps",
@ -124,11 +125,16 @@ Graphics.prototype.setFontAntonioLarge = function(scale) {
*/
var drawTimeout;
function queueDraw() {
// Faster updates during alarm to ensure that it is
// shown correctly...
var timeout = isAlarmEnabled() ? 10000 : 60000;
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}, timeout - (Date.now() % timeout));
}
/**
@ -238,6 +244,7 @@ function drawInfo(){
return;
}
g.setFontAlign(-1, -1, 0);
g.setFontAntonioMedium();
g.setColor(cOrange);
g.clearRect(120, 10, g.getWidth(), 75);
@ -480,9 +487,6 @@ function draw(){
// Queue draw first to ensure that its called in one minute again.
queueDraw();
// First handle alarm to show this correctly afterwards
handleAlarm();
// Next draw the watch face
g.reset();
g.clearRect(0, 0, g.getWidth(), g.getHeight());
@ -561,43 +565,57 @@ function getWeather(){
/*
* Handle alarm
*/
function getCurrentTimeInMinutes(){
return Math.floor(Date.now() / (1000*60));
}
function isAlarmEnabled(){
return settings.alarm >= 0;
try{
var alarm = require('sched');
var alarmObj = alarm.getAlarm(TIMER_IDX);
if(alarmObj===undefined || !alarmObj.on){
return false;
}
return true;
} catch(ex){ }
return false;
}
function getAlarmMinutes(){
var currentTime = getCurrentTimeInMinutes();
return settings.alarm - currentTime;
if(!isAlarmEnabled()){
return -1;
}
var alarm = require('sched');
var alarmObj = alarm.getAlarm(TIMER_IDX);
return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
}
function handleAlarm(){
if(!isAlarmEnabled()){
return;
}
function increaseAlarm(){
try{
var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0;
var alarm = require('sched')
alarm.setAlarm(TIMER_IDX, {
timer : (minutes+5)*60*1000,
});
alarm.reload();
} catch(ex){ }
}
if(getAlarmMinutes() > 0){
return;
}
function decreaseAlarm(){
try{
var minutes = getAlarmMinutes();
minutes -= 5;
// Alarm
var t = 300;
Bangle.buzz(t, 1)
.then(() => new Promise(resolve => setTimeout(resolve, t)))
.then(() => Bangle.buzz(t, 1))
.then(() => new Promise(resolve => setTimeout(resolve, t)))
.then(() => Bangle.buzz(t, 1))
.then(() => new Promise(resolve => setTimeout(resolve, t)))
.then(() => Bangle.buzz(t, 1))
.then(() => new Promise(resolve => setTimeout(resolve, 5E3)))
.then(() => {
// Update alarm state to disabled
settings.alarm = -1;
storage.writeJSON(SETTINGS_FILE, settings);
});
var alarm = require('sched')
alarm.setAlarm(TIMER_IDX, undefined);
if(minutes > 0){
alarm.setAlarm(TIMER_IDX, {
timer : minutes*60*1000,
});
}
alarm.reload();
} catch(ex){ }
}
@ -625,27 +643,6 @@ Bangle.on('charging',function(charging) {
});
function increaseAlarm(){
if(isAlarmEnabled() && getAlarmMinutes() < 95){
settings.alarm += 5;
} else {
settings.alarm = getCurrentTimeInMinutes() + 5;
}
storage.writeJSON(SETTINGS_FILE, settings);
}
function decreaseAlarm(){
if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){
settings.alarm -= 5;
} else {
settings.alarm = -1;
}
storage.writeJSON(SETTINGS_FILE, settings);
}
function feedback(){
Bangle.buzz(40, 0.3);
}

View File

@ -3,7 +3,7 @@
"name": "LCARS Clock",
"shortName":"LCARS",
"icon": "lcars.png",
"version":"0.19",
"version":"0.20",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "Library Computer Access Retrieval System (LCARS) clock.",

View File

@ -1,3 +1,4 @@
0.01: Launch app.
0.02: 12k steps are 360 degrees - improves readability of steps.
0.03: Battery improvements through sleep (no minute updates) and partial updates of drawing.
0.03: Battery improvements through sleep (no minute updates) and partial updates of drawing.
0.04: Use alarm for timer instead of own alarm implementation.

View File

@ -8,8 +8,8 @@ black one the battery level (100% = 360 degrees).
The selected theme is also respected. Note that this watch face is in fullscreen
mode, but widgets are still loaded in background.
## Other features
- Set a timer - simply touch top (+5min.) or bottom (-5 min.).
## Other Features
- Set a timer - simply touch top (+5min.) or bottom (-5 min.). This only works if "sched" is installed.
- If the weather is available through the weather app, the outside temp. will be shown.
- Sleep modus at midnight to save more battery (no minute updates).
- Icons for charging and GPS.
@ -29,5 +29,5 @@ which helped a lot for this development.
Icons from <a href="https://www.flaticon.com/free-icons" title="icons">by Freepik - Flaticon</a>
## Contributors
## Creator
- [David Peer](https://github.com/peerdavid).

View File

@ -3,7 +3,7 @@
"name": "Not Analog",
"shortName":"Not Analog",
"icon": "notanalog.png",
"version":"0.03",
"version":"0.04",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "An analog watch face for people that can not read analog watch faces.",

View File

@ -1,7 +1,7 @@
/**
* NOT ANALOG CLOCK
*/
const TIMER_IDX = "notanalog";
const locale = require('locale');
const storage = require('Storage')
const SETTINGS_FILE = "notanalog.setting.json";
@ -291,7 +291,6 @@ function drawSleep(){
function draw(fastUpdate){
// Execute handlers
handleState(fastUpdate);
handleAlarm();
if(state.sleep){
drawSleep();
@ -377,82 +376,80 @@ Bangle.on('touch', function(btn, e){
* Some helpers
*/
function queueDraw() {
// Faster updates during alarm to ensure that it is
// shown correctly...
var timeout = isAlarmEnabled() ? 10000 : 60000;
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw(true);
}, 60000 - (Date.now() % 60000));
draw();
}, timeout - (Date.now() % timeout));
}
/*
* Handle alarm
*/
function getCurrentTimeInMinutes(){
return Math.floor(Date.now() / (1000*60));
}
function isAlarmEnabled(){
return settings.alarm >= 0;
}
try{
var alarm = require('sched');
var alarmObj = alarm.getAlarm(TIMER_IDX);
if(alarmObj===undefined || !alarmObj.on){
return false;
}
return true;
} catch(ex){ }
return false;
}
function getAlarmMinutes(){
var currentTime = getCurrentTimeInMinutes();
return settings.alarm - currentTime;
}
function handleAlarm(){
if(!isAlarmEnabled()){
return;
return -1;
}
if(getAlarmMinutes() > 0){
return;
}
// Alarm
var t = 300;
Bangle.buzz(t, 1)
.then(() => new Promise(resolve => setTimeout(resolve, t)))
.then(() => Bangle.buzz(t, 1))
.then(() => new Promise(resolve => setTimeout(resolve, t)))
.then(() => Bangle.buzz(t, 1))
.then(() => new Promise(resolve => setTimeout(resolve, t)))
.then(() => Bangle.buzz(t, 1))
.then(() => new Promise(resolve => setTimeout(resolve, 5E3)))
.then(() => {
// Update alarm state to disabled
settings.alarm = -1;
storage.writeJSON(SETTINGS_FILE, settings);
});
var alarm = require('sched');
var alarmObj = alarm.getAlarm(TIMER_IDX);
return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
}
function increaseAlarm(){
if(isAlarmEnabled()){
settings.alarm += 5;
} else {
settings.alarm = getCurrentTimeInMinutes() + 5;
}
storage.writeJSON(SETTINGS_FILE, settings);
try{
var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0;
var alarm = require('sched')
alarm.setAlarm(TIMER_IDX, {
timer : (minutes+5)*60*1000,
});
alarm.reload();
} catch(ex){ }
}
function decreaseAlarm(){
if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){
settings.alarm -= 5;
} else {
settings.alarm = -1;
}
try{
var minutes = getAlarmMinutes();
minutes -= 5;
storage.writeJSON(SETTINGS_FILE, settings);
var alarm = require('sched')
alarm.setAlarm(TIMER_IDX, undefined);
if(minutes > 0){
alarm.setAlarm(TIMER_IDX, {
timer : minutes*60*1000,
});
}
alarm.reload();
} catch(ex){ }
}
function feedback(){
Bangle.buzz(40, 0.6);
}
/*
* Lets start widgets, listen for btn etc.
*/

1
apps/smpltmr/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Release

21
apps/smpltmr/README.md Normal file
View File

@ -0,0 +1,21 @@
# Simple Timer
A simple app to set a timer quickly. Simply tab on top/bottom/left/right
to select the minutes and tab in the middle of the screen to start/stop
the timer. Note that this timer depends on qalarm.
# Overview
If you open the app, you can simply control the timer
by clicking on top, bottom, left or right of the screen.
If you tab at the middle of the screen, the timer is
started / stopped.
![](description.png)
# Creator
[David Peer](https://github.com/peerdavid)
# Thanks to...
Time icon created by <a href="https://www.flaticon.com/free-icons/time" title="time icons">CreativeCons - Flaticon</a>

1
apps/smpltmr/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwMAg//AAXgApcAvAZBhwCBuAFuGoUeAQM4AQM8AQl8Bwn4AQMPgEB+AFBg+AgZYBgED4AHBAoIPBCYIAC/AfCGwQrCGAQ3CAAMcIYQFCJ4QABnoREvIdE/eeAgUB+fPAoUD8/nIIUHz/zJoUPn/5LIUev/8MoU8//+OIU5XwO8AoN7AoPeAoNzAoPOAsrFKg4QBAAPgApYA=="))

124
apps/smpltmr/app.js Normal file
View File

@ -0,0 +1,124 @@
/*
* SIMPLE TIMER
*
* Creator: David Peer
* Date: 02/2022
*/
Bangle.loadWidgets();
const alarm = require("sched");
const TIMER_IDX = "smpltmr";
const screenWidth = g.getWidth();
const screenHeight = g.getHeight();
const cx = parseInt(screenWidth/2);
const cy = parseInt(screenHeight/2)-12;
var minutes = 5;
var interval; //used for the 1 second interval timer
function isTimerEnabled(){
var alarmObj = alarm.getAlarm(TIMER_IDX);
if(alarmObj===undefined || !alarmObj.on){
return false;
}
return true;
}
function getTimerMin(){
var alarmObj = alarm.getAlarm(TIMER_IDX);
return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
}
function setTimer(minutes){
alarm.setAlarm(TIMER_IDX, {
// msg : "Simple Timer",
timer : minutes*60*1000,
});
alarm.reload();
}
function deleteTimer(){
alarm.setAlarm(TIMER_IDX, undefined);
alarm.reload();
}
setWatch(_=>load(), BTN1);
function draw(){
g.clear(1);
Bangle.drawWidgets();
if (interval) {
clearInterval(interval);
}
interval = undefined;
// Write time
g.setFontAlign(0, 0, 0);
g.setFont("Vector", 32).setFontAlign(0,-1);
var started = isTimerEnabled();
var text = minutes + " min.";
if(started){
var min = getTimerMin();
text = min + " min.";
}
var rectWidth = parseInt(g.stringWidth(text) / 2);
if(started){
interval = setInterval(draw, 1000);
g.setColor("#ff0000");
} else {
g.setColor(g.theme.fg);
}
g.fillRect(cx-rectWidth-5, cy-5, cx+rectWidth, cy+30);
g.setColor(g.theme.bg);
g.drawString(text, cx, cy);
}
Bangle.on('touch', function(btn, e){
var left = parseInt(g.getWidth() * 0.25);
var right = g.getWidth() - left;
var upper = parseInt(g.getHeight() * 0.25);
var lower = g.getHeight() - upper;
var isLeft = e.x < left;
var isRight = e.x > right;
var isUpper = e.y < upper;
var isLower = e.y > lower;
var isMiddle = !isLeft && !isRight && !isUpper && !isLower;
var started = isTimerEnabled();
if(isRight && !started){
minutes += 1;
Bangle.buzz(40, 0.3);
} else if(isLeft && !started){
minutes -= 1;
Bangle.buzz(40, 0.3);
} else if(isUpper && !started){
minutes += 5;
Bangle.buzz(40, 0.3);
} else if(isLower && !started){
minutes -= 5;
Bangle.buzz(40, 0.3);
} else if(isMiddle) {
if(!started){
setTimer(minutes);
} else {
deleteTimer();
}
Bangle.buzz(80, 0.6);
}
minutes = Math.max(0, minutes);
draw();
});
g.reset();
draw();

BIN
apps/smpltmr/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,17 @@
{
"id": "smpltmr",
"name": "Simple Timer",
"shortName": "Simple Timer",
"version": "0.01",
"description": "A very simple app to start a timer.",
"icon": "app.png",
"tags": "tool",
"dependencies": {"sched":"type"},
"supports": ["BANGLEJS2"],
"screenshots": [{"url":"screenshot.png"}, {"url": "screenshot_2.png"}],
"readme": "README.md",
"storage": [
{"name":"smpltmr.app.js","url":"app.js"},
{"name":"smpltmr.img","url":"app-icon.js","evaluate":true}
]
}

BIN
apps/smpltmr/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB