Merge branch 'master' into iconlaunch
|
@ -1,2 +1,3 @@
|
|||
0.01: New app!
|
||||
0.02: Design improvements and fixes.
|
||||
0.02: Design improvements and fixes.
|
||||
0.03: Indicate battery level through line occurrence.
|
|
@ -8,14 +8,16 @@ The original output of stable diffusion is shown here:
|
|||
|
||||

|
||||
|
||||
And my implementation is shown here:
|
||||
My implementation is shown below. Note that horizontal lines occur randomly, but the
|
||||
probability is correlated with the battery level. So if your screen contains only
|
||||
a few lines its time to charge your bangle again ;)
|
||||
|
||||

|
||||
|
||||
|
||||
# Thanks to
|
||||
The great open source community: I used an open source diffusion model (https://github.com/CompVis/stable-diffusion)
|
||||
to generate a watch face for the open source smartwatch BangleJs.
|
||||
The great open-source community: I used an open-source diffusion model (https://github.com/CompVis/stable-diffusion)
|
||||
to generate a watch face for the open-source smartwatch BangleJs.
|
||||
|
||||
## Creator
|
||||
- [David Peer](https://github.com/peerdavid).
|
|
@ -37,8 +37,15 @@ function drawBackground() {
|
|||
g.setFontAlign(0,0);
|
||||
g.setColor(g.theme.fg);
|
||||
|
||||
y = 0;
|
||||
var bat = E.getBattery() / 100.0;
|
||||
var y = 0;
|
||||
while(y < H){
|
||||
// Show less lines in case of small battery level.
|
||||
if(Math.random() > bat){
|
||||
y += 5;
|
||||
continue;
|
||||
}
|
||||
|
||||
y += 3 + Math.floor(Math.random() * 10);
|
||||
g.drawLine(0, y, W, y);
|
||||
g.drawLine(0, y+1, W, y+1);
|
||||
|
@ -103,7 +110,7 @@ function drawDate(){
|
|||
g.setFontAlign(0,0);
|
||||
g.setFontGochiHand();
|
||||
|
||||
var text = ("0"+date.getDate()).substr(-2) + "/" + ("0"+date.getMonth()).substr(-2);
|
||||
var text = ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2);
|
||||
var w = g.stringWidth(text);
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(cx-w/2-4, 20, cx+w/2+4, 40+12);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "AI Clock",
|
||||
"shortName":"AI Clock",
|
||||
"icon": "aiclock.png",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",
|
||||
|
|
|
@ -26,3 +26,4 @@
|
|||
0.12: Allow configuration of update interval
|
||||
0.13: Load step goal from Bangle health app as fallback
|
||||
Memory optimizations
|
||||
0.14: Support to show big weather info
|
||||
|
|
|
@ -9,10 +9,11 @@ It can show the following information (this can be configured):
|
|||
* Steps distance
|
||||
* Heart rate (automatically updates when screen is on and unlocked)
|
||||
* Battery (including charging status and battery low warning)
|
||||
* Weather (requires [weather app](https://banglejs.com/apps/#weather))
|
||||
* Weather (requires [OWM weather provider](https://banglejs.com/apps/?id=owmweather))
|
||||
* Humidity or wind speed as circle progress
|
||||
* Temperature inside circle
|
||||
* Condition as icon below circle
|
||||
* Big weather icon next to clock
|
||||
* Time and progress until next sunrise or sunset (requires [my location app](https://banglejs.com/apps/#mylocation))
|
||||
* Temperature, air pressure or altitude from internal pressure sensor
|
||||
|
||||
|
@ -27,6 +28,8 @@ The color of each circle can be configured. The following colors are available:
|
|||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## Ideas
|
||||
* Show compass heading
|
||||
|
@ -35,4 +38,5 @@ The color of each circle can be configured. The following colors are available:
|
|||
Marco ([myxor](https://github.com/myxor))
|
||||
|
||||
## Icons
|
||||
Icons taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0
|
||||
Most of the icons are taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0 except the big weather icons which are from
|
||||
[icons8](https://icons8.com/icon/set/weather/small--static--black)
|
||||
|
|
|
@ -22,7 +22,7 @@ let settings = Object.assign(
|
|||
if (settings.stepGoal == undefined) {
|
||||
let d = storage.readJSON("health.json", true) || {};
|
||||
settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.stepGoal : undefined;
|
||||
|
||||
|
||||
if (settings.stepGoal == undefined) {
|
||||
d = storage.readJSON("wpedom.json", true) || {};
|
||||
settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
|
||||
|
@ -39,6 +39,7 @@ let location = getLocation();
|
|||
|
||||
const showWidgets = settings.showWidgets || false;
|
||||
const circleCount = settings.circleCount || 3;
|
||||
const showBigWeather = settings.showBigWeather || false;
|
||||
|
||||
let hrtValue;
|
||||
let now = Math.round(new Date().getTime() / 1000);
|
||||
|
@ -116,17 +117,41 @@ function draw() {
|
|||
|
||||
// time
|
||||
g.setFontRobotoRegular50NumericOnly();
|
||||
g.setFontAlign(0, -1);
|
||||
g.setColor(colorFg);
|
||||
g.drawString(locale.time(new Date(), 1), w / 2, h1 + 6);
|
||||
if (!showBigWeather) {
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString(locale.time(new Date(), 1), w / 2, h1 + 6);
|
||||
}
|
||||
else {
|
||||
g.setFontAlign(-1, -1);
|
||||
g.drawString(locale.time(new Date(), 1), 2, h1 + 6);
|
||||
}
|
||||
now = Math.round(new Date().getTime() / 1000);
|
||||
|
||||
// date & dow
|
||||
g.setFontRobotoRegular21();
|
||||
g.setFontAlign(0, 0);
|
||||
g.drawString(locale.date(new Date()), w / 2, h2);
|
||||
g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset);
|
||||
|
||||
if (!showBigWeather) {
|
||||
g.setFontAlign(0, 0);
|
||||
g.drawString(locale.date(new Date()), w / 2, h2);
|
||||
g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset);
|
||||
} else {
|
||||
g.setFontAlign(-1, 0);
|
||||
g.drawString(locale.date(new Date()), 2, h2);
|
||||
g.drawString(locale.dow(new Date()), 2, h2 + dowOffset, 1);
|
||||
}
|
||||
|
||||
// weather
|
||||
if (showBigWeather) {
|
||||
const weather = getWeather();
|
||||
const tempString = weather ? locale.temp(weather.temp - 273.15) : undefined;
|
||||
g.setFontAlign(1, 0);
|
||||
if (tempString) g.drawString(tempString, w, h2);
|
||||
|
||||
const code = weather ? weather.code : -1;
|
||||
const icon = getWeatherIconByCode(code, true);
|
||||
if (icon) g.drawImage(icon, w - 48, h1, {scale:0.75});
|
||||
}
|
||||
|
||||
drawCircle(1);
|
||||
drawCircle(2);
|
||||
drawCircle(3);
|
||||
|
@ -417,7 +442,6 @@ function drawWeather(w) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
function drawSunProgress(w) {
|
||||
if (!w) w = getCircleXPosition("sunprogress");
|
||||
const percent = getSunProgress();
|
||||
|
@ -557,20 +581,22 @@ function windAsBeaufort(windInKmh) {
|
|||
* Choose weather icon to display based on weather conditition code
|
||||
* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
|
||||
*/
|
||||
function getWeatherIconByCode(code) {
|
||||
function getWeatherIconByCode(code, big) {
|
||||
const codeGroup = Math.round(code / 100);
|
||||
if (big == undefined) big = false;
|
||||
|
||||
// weather icons:
|
||||
const weatherCloudy = atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA");
|
||||
const weatherSunny = atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA");
|
||||
const weatherMoon = atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA");
|
||||
const weatherPartlyCloudy = atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA");
|
||||
const weatherRainy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA");
|
||||
const weatherPartlyRainy = atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA");
|
||||
const weatherSnowy = atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA");
|
||||
const weatherFoggy = atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA");
|
||||
const weatherStormy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA");
|
||||
|
||||
const weatherCloudy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAAAAAfg+AAAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD3gAAAAAAAAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP////////8Af////////gA////////8AAf//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA");
|
||||
const weatherSunny = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAwADwADAAAAHgAPAAeAAAAfAA8AD4AAAA+ADwAfAAAAB8APAD4AAAAD4B+AfAAAAAHw//D4AAAAAPv//fAAAAAAf///4AAAAAA/4H/AAAAAAB+AH4AAAAAAPgAHwAAAAAA8AAPAAAAAAHwAA+AAAAAAeAAB4AAAAAB4AAHgAAAAAPAAAPAAAA//8AAA//8AD//wAAD//wAP//AAAP//AA//8AAA//8AAADwAADwAAAAAHgAAeAAAAAAeAAB4AAAAAB8AAPgAAAAADwAA8AAAAAAPgAHwAAAAAAfgB+AAAAAAD/gf8AAAAAAf///4AAAAAD7//3wAAAAAfD/8PgAAAAD4B+AfAAAAAfADwA+AAAAD4APAB8AAAAfAA8AD4AAAB4ADwAHgAAADAAPAAMAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA");
|
||||
const weatherMoon = big ? atob("QEDBAP//wxgAAAYAAAAPAAAAD4AAAA8AAAAPwAAADwAAAA/gAAAPAAAAB/APAP/wAAAH+A8A//AAAAf4DwD/8AAAB/wPAP/wAAAH/gAADwAAAAe+AAAPAAAAB54AAA8AAAAHngAADwAAAAePAAAAAAAAD48OAAAAAAAPDw+AAAAAAB8PD8AAAAAAHg8P4AAAAAA+DwPwAAAAAHwfAfgAAAAB+D4A/AAA8AfwfgB/8AD//+D+AD/8AP//wfgAH/4Af/8B8AAf/wB//APgAAgfgD+AA8AAAAfAH8AHwAAAA+AP8B+AAAAB4Af//4AAAAHgA///gAAAAPAA//8AAAAA8AAf/wAAAADwAAAAAAAAAPAAAAAAAAAA8AcAAAAAAADwD+AAAAAAAfAfgAAAAAAB+D4AAAAAAAB8fAAAAAAAAD54AAAAAAAAHngAAAAAAAAe8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAPeAAAAAAAAB54AAAAAAAAHnwAAAAAAAA+PgAAAAAAAHwfgAAAAAAB+A/////////wB////////+AD////////wAB///////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA");
|
||||
const weatherPartlyCloudy = big ? atob("QEDBAP//wxgAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAABwAPAA4AAAAHgA8AHgAAAAfADwA+AAAAA+AfgHwAAAAB8P/w+AAAAAD7//3wAAAAAH///+BAAAAAP+B/wOAAAAAfgB+B8AAAAD4AD8H4AAAAPAA/wPwAAAB8AH+Af/AAAHgA/AA//AAAeAH4AB/+AADwAfAAH/8A//AD4AAIH4D/8AfAAAAHwP/wB4AAAAPg//AHgAAAAeAA8B+AAAAB4AB4fwAAAADwAHn/AAAAAPAAff8AAAAA8AA/8AAAAADwAD/AAAAAAPAEH4AAAAAA8A4PgAAAAAHwHgcAAAAAAfg+AwAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD3gAAAAAAAAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP////////8Af////////gA////////8AAf//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA");
|
||||
const weatherRainy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4APAA8AAfg+AA8ADwAAfHwADwAPAAA+eAAPAA8AAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AADw8PDwAP8AAPDw8PAA/wAA8PDw8AD3gADw8PDwAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP/w8PDw8P8Af/Dw8PDw/gA/8PDw8PD8AAfw8PDw8OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAPAAAAAAAPAA8AAAAAAA8ADwAAAAAADwAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA");
|
||||
const weatherPartlyRainy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAA8AAfg+AAAADwAAfHwAAAAPAAA+eAAAAA8AAB54AAAADwAAHvAAAAAPAAAP8AAAAA8AAA/wAAAADwAAD/AAAA8PAAAP8AAADw8AAA/wAAAPDwAAD3gAAA8PAAAeeAAADw8AAB58AAAPDwAAPj4AAA8PAAB8H4AADw8AAfgP//8PDw//8Af//w8PD//gA///Dw8P/8AAf/8PDw/+AAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA");
|
||||
const weatherSnowy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAADwAfg+AAAAAPAAfHwAAAAA8AA+eAAAAADwAB54AA8AD/8AHvAADwAP/wAP8AAPAA//AA/wAA8AD/8AD/AA//AA8AAP8AD/8ADwAA/wAP/wAPAAD3gA//AA8AAeeAAPAAAAAB58AA8AAAAAPj4ADwAAAAB8H4APAAAAAfgP/wAA8A//8Af/AADwD//gA/8AAPAP/8AAfwAA8A/+AAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA");
|
||||
const weatherFoggy = big ? atob("QEDBAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAwADwADAAAAHgAPAAeAAAAfAA8AD4AAAA+ADwAfAAAAB8APAD4AAAAD4B+AfAAAAAHw//D4AAAAAPv//fAAAAAAf///4AAAAAA/4H/AAAAAAB+AH4AAAAAAPgAHwAAAAAA8AAPAAAAAAHwAA+AAAAAAeAAB4AAAAAB4AAHgAAAAAPAAAPAAAAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AD///AADwAAAP//8AAeAAAA///wAB4AAAD///AAPgAAAAAAAAA8AAAAAAAAAHwAAAAAAAAB+AAAAAAAAAf8AAAAD///D/4AAAAP//8P3wAAAA///w8PgAAAD///CAfAAAAAAAAAA+AAAAAAAAAB8AAAAAAAAAD4AAAAAAAAAHgAAP//8PAAMAAA///w8AAAAAD///DwAAAAAP//8PAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA");
|
||||
const weatherStormy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAAAAAfg+AAAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAD/AAHvAAAAAf4AAP8AAAAB/gAA/wAAAAP8AAD/AAAAA/gAAP8AAAAH+AAA/wAAAAfwAAD3gAAAD/AAAeeAAAAP4AAB58AAAB/AAAPj4AAAH8AAB8H4AAA/gAAfgP//+D//D/8Af//4f/4P/gA///B//B/8AAf/8P/8P+AAAAAAAPgAAAAAAAAB8AAAAAAAAAHwAAAAAAAAA+AAAAAAAAADwAAAAAAAAAfAAAAAAAAAB4AAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA");
|
||||
const unknown = big ? atob("QEDBAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAH//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAA/AAD4AAAAAD4H4HwAAAAAfB/4PgAAAAB8P/weAAAAAHg//h4AAAAA+Hw+HwAAAAD4eB8PAAAAAP/wDw8AAAAA//APDwAAAAD/8A8PAAAAAH/gDw8AAAAAAAAfDwAAAAAAAH4fAAAAAAAB/B4AAAAAAAf4HgAAAAAAD/A+AAAAAAAfwHwAAAAAAD8A+AAAAAAAPgH4AAAAAAB8B/AAAAAAAHgf4AAAAAAA+H+AAAAAAADwfwAAAAAAAPD8AAAAAAAA8PAAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAADw8AAAAAAAAPDwAAAAAAAA8PAAAAAAAADw8AAAAAAAAP/wAAAAAAAA//AAAAAAAAD/8AAAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : undefined;
|
||||
|
||||
switch (codeGroup) {
|
||||
case 2:
|
||||
return weatherStormy;
|
||||
|
@ -607,7 +633,7 @@ function getWeatherIconByCode(code) {
|
|||
return weatherCloudy;
|
||||
}
|
||||
default:
|
||||
return undefined;
|
||||
return unknown;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,5 +22,6 @@
|
|||
"circle3colorizeIcon": true,
|
||||
"circle4colorizeIcon": false,
|
||||
"hrmValidity": 60,
|
||||
"updateInterval": 60
|
||||
"updateInterval": 60,
|
||||
"showBigWeather": false
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "circlesclock",
|
||||
"name": "Circles clock",
|
||||
"shortName":"Circles clock",
|
||||
"version":"0.13",
|
||||
"version":"0.14",
|
||||
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
||||
|
|
After Width: | Height: | Size: 4.4 KiB |
|
@ -68,6 +68,11 @@
|
|||
return x + 's';
|
||||
},
|
||||
onchange: x => save('updateInterval', x),
|
||||
},
|
||||
/*LANG*/'show big weather': {
|
||||
value: !!settings.showBigWeather,
|
||||
format: () => (settings.showBigWeather ? 'Yes' : 'No'),
|
||||
onchange: x => save('showBigWeather', x),
|
||||
}
|
||||
};
|
||||
E.showMenu(menu);
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Make selection of background activity more explicit
|
||||
0.03: Fix listener for accel always active
|
||||
Use custom UI with swipes instead of leftright
|
||||
|
|
|
@ -318,16 +318,24 @@ function triangle (x, y, width, height){
|
|||
];
|
||||
}
|
||||
|
||||
function onSwipe(dir){
|
||||
if (dir < 0) {
|
||||
nextScreen();
|
||||
} else if (dir > 0) {
|
||||
switchMenu();
|
||||
} else {
|
||||
nextScreen();
|
||||
}
|
||||
}
|
||||
|
||||
function setButtons(){
|
||||
Bangle.setUI("leftright", (dir)=>{
|
||||
if (dir < 0) {
|
||||
nextScreen();
|
||||
} else if (dir > 0) {
|
||||
switchMenu();
|
||||
} else {
|
||||
nextScreen();
|
||||
}
|
||||
});
|
||||
let options = {
|
||||
mode: "custom",
|
||||
swipe: onSwipe,
|
||||
btn: nextScreen,
|
||||
touch: nextScreen
|
||||
};
|
||||
Bangle.setUI(options);
|
||||
}
|
||||
|
||||
function getApproxFileSize(name){
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "gpstrek",
|
||||
"name": "GPS Trekking",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}],
|
||||
|
|
|
@ -23,10 +23,6 @@ function onGPS(fix) {
|
|||
if(fix.fix) state.currentPos = fix;
|
||||
}
|
||||
|
||||
Bangle.on('accel', function(e) {
|
||||
state.acc = e;
|
||||
});
|
||||
|
||||
function onMag(e) {
|
||||
if (!state.compassHeading) state.compassHeading = e.heading;
|
||||
|
||||
|
@ -73,12 +69,17 @@ function onPressure(e) {
|
|||
}
|
||||
}
|
||||
|
||||
function onAcc (e){
|
||||
state.acc = e;
|
||||
}
|
||||
|
||||
function start(bg){
|
||||
Bangle.on('GPS', onGPS);
|
||||
Bangle.on("HRM", onPulse);
|
||||
Bangle.on("mag", onMag);
|
||||
Bangle.on("step", onStep);
|
||||
Bangle.on("pressure", onPressure);
|
||||
Bangle.on('accel', onAcc);
|
||||
|
||||
Bangle.setGPSPower(1, "gpstrek");
|
||||
Bangle.setHRMPower(1, "gpstrek");
|
||||
|
@ -96,8 +97,19 @@ function stop(bg){
|
|||
if (bg){
|
||||
if (state.active) bgChanged = true;
|
||||
state.active = false;
|
||||
saveState();
|
||||
} else if (!state.active) {
|
||||
Bangle.setGPSPower(0, "gpstrek");
|
||||
Bangle.setHRMPower(0, "gpstrek");
|
||||
Bangle.setCompassPower(0, "gpstrek");
|
||||
Bangle.setBarometerPower(0, "gpstrek");
|
||||
Bangle.removeListener('GPS', onGPS);
|
||||
Bangle.removeListener("HRM", onPulse);
|
||||
Bangle.removeListener("mag", onMag);
|
||||
Bangle.removeListener("step", onStep);
|
||||
Bangle.removeListener("pressure", onPressure);
|
||||
Bangle.removeListener('accel', onAcc);
|
||||
}
|
||||
saveState();
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
0.04: Support new fast app switching
|
||||
0.05: Allow to directly eval apps instead of loading
|
||||
0.06: Cache apps for faster start
|
||||
0.07: Add swipe-to-exit
|
||||
0.07: Read app icons on demand
|
||||
Add swipe-to-exit
|
||||
|
|
|
@ -22,10 +22,6 @@
|
|||
}) };
|
||||
s.writeJSON("launch.cache.json", launchCache);
|
||||
}
|
||||
let apps = launchCache.apps;
|
||||
apps.forEach((app) => {
|
||||
if (app.icon) app.icon = s.read(app.icon);
|
||||
});
|
||||
let scroll = 0;
|
||||
let selectedItem = -1;
|
||||
const R = Bangle.appRect;
|
||||
|
@ -37,12 +33,13 @@
|
|||
g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1);
|
||||
let x = 0;
|
||||
for (let i = itemI * appsN; i < appsN * (itemI + 1); i++) {
|
||||
if (!apps[i]) break;
|
||||
if (!launchCache.apps[i]) break;
|
||||
x += whitespace;
|
||||
if (!apps[i].icon) {
|
||||
if (!launchCache.apps[i].icon) {
|
||||
g.setFontAlign(0, 0, 0).setFont("12x20:2").drawString("?", x + r.x + iconSize / 2, r.y + iconSize / 2);
|
||||
} else {
|
||||
g.drawImage(apps[i].icon, x + r.x, r.y);
|
||||
if (!launchCache.apps[i].icondata) launchCache.apps[i].icondata = s.read(launchCache.apps[i].icon);
|
||||
g.drawImage(launchCache.apps[i].icondata, x + r.x, r.y);
|
||||
}
|
||||
if (selectedItem == i) {
|
||||
g.drawRect(
|
||||
|
@ -69,7 +66,7 @@
|
|||
};
|
||||
let lastIsDown = false;
|
||||
let drawText = function(i) {
|
||||
const selectedApp = apps[selectedItem];
|
||||
const selectedApp = launchCache.apps[selectedItem];
|
||||
const idy = (selectedItem - (selectedItem % 3)) / 3;
|
||||
if (!selectedApp || i != idy) return;
|
||||
const appY = idxToY(idy) + iconSize / 2;
|
||||
|
@ -87,13 +84,13 @@
|
|||
let selectItem = function(id, e) {
|
||||
const iconN = E.clip(Math.floor((e.x - R.x) / itemSize), 0, appsN - 1);
|
||||
const appId = id * appsN + iconN;
|
||||
if( settings.direct && apps[appId])
|
||||
if( settings.direct && launchCache.apps[appId])
|
||||
{
|
||||
loadApp(apps[appId].src);
|
||||
loadApp(launchCache.apps[appId].src);
|
||||
return;
|
||||
}
|
||||
if (appId == selectedItem && apps[appId]) {
|
||||
const app = apps[appId];
|
||||
if (appId == selectedItem && launchCache.apps[appId]) {
|
||||
const app = launchCache.apps[appId];
|
||||
if (!app.src || s.read(app.src) === undefined) {
|
||||
E.showMessage( /*LANG*/ "App Source\nNot found");
|
||||
} else {
|
||||
|
@ -125,7 +122,7 @@
|
|||
};
|
||||
drawItems();
|
||||
g.flip();
|
||||
const itemsN = Math.ceil(apps.length / appsN);
|
||||
const itemsN = Math.ceil(launchCache.apps.length / appsN);
|
||||
let onDrag = function(e) {
|
||||
g.setColor(g.theme.fg);
|
||||
g.setBgColor(g.theme.bg);
|
||||
|
@ -190,7 +187,8 @@
|
|||
loadApp = function(name) {
|
||||
Bangle.setUI();
|
||||
if (watch) clearWatch(watch);
|
||||
apps = [];
|
||||
delete launchCache;
|
||||
delete launchHash;
|
||||
delete drawItemAuto;
|
||||
delete drawText;
|
||||
delete selectItem;
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
0.01: Inital release.
|
||||
0.02: Misc fixes. Add Search and play.
|
||||
0.03: Simplify "Search and play" function after some bugfixes to Podcast
|
||||
Addict.
|
||||
0.04: New layout.
|
||||
0.05: Add widget field, tweak layout.
|
|
@ -0,0 +1,21 @@
|
|||
Requires Gadgetbridge 71.0 or later. Allow intents in Gadgetbridge in order for this app to work.
|
||||
|
||||
Touch input:
|
||||
|
||||
Press the different ui elements to control Podcast Addict and open menus.
|
||||
Press left or right arrow to move backward/forward in current playlist.
|
||||
|
||||
Swipe input:
|
||||
|
||||
Swipe left/right to jump backward/forward within the current podcast episode.
|
||||
Swipe up/down to change the volume.
|
||||
|
||||
It's possible to start a podcast by searching with the remote. It's also possible to change the playback speed.
|
||||
|
||||
The swipe logic was inspired by the implementation in [rigrig](https://git.tubul.net/rigrig/)'s Scrolling Messages.
|
||||
|
||||
Podcast Addict Remote was created by [thyttan](https://github.com/thyttan/).
|
||||
|
||||
Podcast Addict is developed by [Xavier Guillemane](https://twitter.com/xguillem) and can be installed via the [Google Play Store](https://play.google.com/store/apps/details?id=com.bambuna.podcastaddict&hl=en_US&gl=US).
|
||||
|
||||
The Podcast Addict icon is used with permission.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwgHEhvdABnQDwwVNAAYtTGI4WSGAgWTGAYXUGAJGUGAQXXCyoXKmf/AAPznogQn4WCAAQYP6YWFDB4WFJQhFSA4gwMIYogEGBffLg0zKAYwKRgwTDBQP9Ix09n7DCpowBJBKNEBwIXBAQIsBMwgXKIQReCDoRgJOwYQDLQU/poMBC5B2DIAUzLwIKBnoXBPBAXEIQQVDA4IXNCIQXaWAgXNI4kzNQoXLO4wXLU4a+CU4gXR7ovBcIoXIBobMFPAgXILQKPDmgxCR5omDc4QAHC5ITCC6hgCC6hICC6owBC6phBC6zcFAAMzeogALdQjdBC6AZCeYfTmczAwfQhocOAAwXYgAXVgAXVFwMAJCgXCDCYWDJKYWEGKAtEA=="))
|
|
@ -0,0 +1,375 @@
|
|||
/*
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"", flags:["flag1", "flag2",...], categories:["category1","category2",...], mimetype:"", data:"", package:"", class:"", target:"", extra:{someKey:"someValueOrString"}}));
|
||||
|
||||
Podcast Addict is developed by Xavier Guillemane and can be downloaded on Google Play Store: https://play.google.com/store/apps/details?id=com.bambuna.podcastaddict&hl=en_US&gl=US
|
||||
|
||||
Podcast Addict can be controlled through the sending of remote commands called 'Intents'.
|
||||
Some 3rd parties apps specialized in task automation will then allow you to control Podcast Addict. For example, you will be able to wake up to the sound of your playlist or to start automatically playing when some NFC tag has been detected.
|
||||
In Tasker, you just need to copy/paste one of the following intent in the task Action field ("Misc" action type then select "Send Itent") .
|
||||
If you prefer Automate It, you can use the Podcast Addict plugin that will save you some configuration time (https://play.google.com/store/apps/details?id=com.smarterapps.podcastaddictplugin )
|
||||
Before using an intent make sure to set the following:
|
||||
Package: com.bambuna.podcastaddict
|
||||
Class (UPDATE intent only): com.bambuna.podcastaddict.receiver.PodcastAddictBroadcastReceiver
|
||||
Class (every other intent): com.bambuna.podcastaddict.receiver.PodcastAddictPlayerReceiver
|
||||
Here are the supported commands (Intents) :
|
||||
com.bambuna.podcastaddict.service.player.toggle – Toggle the playlist
|
||||
com.bambuna.podcastaddict.service.player.stop – Stop the player and release its resources
|
||||
com.bambuna.podcastaddict.service.player.play – Start playing the playlist
|
||||
com.bambuna.podcastaddict.service.player.pause – Pause the playlist
|
||||
com.bambuna.podcastaddict.service.player.nexttrack – Start playing next track
|
||||
com.bambuna.podcastaddict.service.player.previoustrack – Start playing previous track
|
||||
com.bambuna.podcastaddict.service.player.jumpforward – Jump 30s forward
|
||||
com.bambuna.podcastaddict.service.player.jumpbackward – Jump 15s backward
|
||||
com.bambuna.podcastaddict.service.player.1xspeed - Disable the variable playback speed
|
||||
com.bambuna.podcastaddict.service.player.1.5xspeed – Force the playback speed at 1.5x
|
||||
com.bambuna.podcastaddict.service.player.2xspeed – Force the playback speed at 2.0x
|
||||
com.bambuna.podcastaddict.service.player.stoptimer – Disable the timer
|
||||
com.bambuna.podcastaddict.service.player.15mntimer – Set the timer at 15 minutes
|
||||
com.bambuna.podcastaddict.service.player.30mntimer – Set the timer at 30 minutes
|
||||
com.bambuna.podcastaddict.service.player.60mntimer – Set the timer at 1 hour
|
||||
com.bambuna.podcastaddict.service.update – Trigger podcasts update
|
||||
com.bambuna.podcastaddict.openmainscreen – Open the app on the Main screen
|
||||
com.bambuna.podcastaddict.openplaylist – Open the app on the Playlist screen
|
||||
com.bambuna.podcastaddict.openplayer – Open the app on the Player screen
|
||||
com.bambuna.podcastaddict.opennewepisodes – Open the app on the New episodes screen
|
||||
com.bambuna.podcastaddict.opendownloadedepisodes – Open the app on the Downloaded episodes screen
|
||||
com.bambuna.podcastaddict.service.player.playfirstepisode – Start playing the first episode in the playlist
|
||||
com.bambuna.podcastaddict.service.player.customspeed – Select playback speed
|
||||
In order to use this intent you need to pass a float argument called "arg1". Valid values are within [0.1, 5.0]
|
||||
com.bambuna.podcastaddict.service.player.customtimer – Start a custom timer
|
||||
In order to use this intent you need to pass an int argument called "arg1" containing the number of minutes. Valid values are within [1, 1440]
|
||||
com.bambuna.podcastaddict.service.player.deletecurrentskipnexttrack – Delete the current episode and skip to the next one. It behaves the same way as long pressing on the player >| button, but doesn't display any confirmation popup.
|
||||
com.bambuna.podcastaddict.service.player.deletecurrentskipprevioustrack – Delete the current episode and skip to the previous one. It behaves the same way as long pressing on the player |< button, but doesn't display any confirmation popup.
|
||||
com.bambuna.podcastaddict.service.player.boostVolume – Toggle the Volume Boost audio effect
|
||||
You can pass a, optional boolean argument called "arg1" in order to create a ON or OFF button for the volume boost. Without this parameter the app will just toggle the current value
|
||||
com.bambuna.podcastaddict.service.player.quickBookmark – Creates a bookmark at the current playback position so you can easily retrieve it later.
|
||||
com.bambuna.podcastaddict.service.download.pause – Pause downloads
|
||||
com.bambuna.podcastaddict.service.download.resume – Resume downloads
|
||||
com.bambuna.podcastaddict.service. download.toggle – Toggle downloads
|
||||
com.bambuna.podcastaddict.service.player.favorite – Mark the current episode playing as favorite.
|
||||
com.bambuna.podcastaddict.openplaylist – Open the app on the Playlist screen
|
||||
You can pass an optional string argument called "arg1" in order to select the playlist to open. Without this parameter the app will open the current playlist
|
||||
Here's how it works:
|
||||
##AUDIO## will open the Audio playlist screen
|
||||
##VIDEO## will open the Video playlist screen
|
||||
##RADIO## will open the Radio screen
|
||||
Any other argument will be used as a CATEGORY name. The app will then open this category under the playlist CUSTOM tab
|
||||
You can pass an optional boolean argument called "arg2" in order to select if the app UI should be opened. Without this parameter the playlist will be displayed
|
||||
You can pass an optional boolean argument called "arg3" in order to select if the app should start playing the selected playlist. Without this parameter the playback won't start
|
||||
Since v2020.3
|
||||
com.bambuna.podcastaddict.service.full_backup – Trigger a full backup of the app data (relies on the app automatic backup settings for the folder and the # of backup to keep)
|
||||
This task takes a lot of resources and might take up to a minute to complete, so please avoid using the app at the same time
|
||||
Since v2020.15
|
||||
com.bambuna.podcastaddict.service.player.toggletimer – This will toggle the Sleep Timer using the last duration and parameter used in the app.
|
||||
Since v2020.16
|
||||
com.bambuna.podcastaddict.service.player.togglespeed – This will toggle the Playback speed for the episode currently playing (alternate between selected speed and 1.0x).
|
||||
*/
|
||||
|
||||
var R;
|
||||
var backToMenu = false;
|
||||
var dark = g.theme.dark; // bool
|
||||
|
||||
// The main layout of the app
|
||||
function gfx() {
|
||||
//Bangle.drawWidgets();
|
||||
R = Bangle.appRect;
|
||||
marigin = 8;
|
||||
// g.drawString(str, x, y, solid)
|
||||
g.clearRect(R);
|
||||
g.reset();
|
||||
|
||||
if (dark) {g.setColor(0xFD20);} else {g.setColor(0xF800);} // Orange on dark theme, RED on light theme.
|
||||
g.setFont("4x6:2");
|
||||
g.setFontAlign(1, 0, 0);
|
||||
g.drawString("->", R.x2 - marigin, R.y + R.h/2);
|
||||
|
||||
g.setFontAlign(-1, 0, 0);
|
||||
g.drawString("<-", R.x + marigin, R.y + R.h/2);
|
||||
|
||||
g.setFontAlign(-1, 0, 1);
|
||||
g.drawString("<-", R.x + R.w/2, R.y + marigin);
|
||||
|
||||
g.setFontAlign(1, 0, 1);
|
||||
g.drawString("->", R.x + R.w/2, R.y2 - marigin);
|
||||
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.drawString("Play\nPause", R.x + R.w/2, R.y + R.h/2);
|
||||
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.drawString("Menu", R.x + 2*marigin, R.y + 2*marigin);
|
||||
|
||||
g.setFontAlign(-1, 1, 0);
|
||||
g.drawString("Wake", R.x + 2*marigin, R.y + R.h - 2*marigin);
|
||||
|
||||
g.setFontAlign(1, -1, 0);
|
||||
g.drawString("Srch", R.x + R.w - 2*marigin, R.y + 2*marigin);
|
||||
|
||||
g.setFontAlign(1, 1, 0);
|
||||
g.drawString("Speed", R.x + R.w - 2*marigin, R.y + R.h - 2*marigin);
|
||||
}
|
||||
|
||||
// Touch handler for main layout
|
||||
function touchHandler(_, xy) {
|
||||
x = xy.x;
|
||||
y = xy.y;
|
||||
len = (R.w<R.h+1)?(R.w/3):(R.h/3);
|
||||
|
||||
// doing a<b+1 seemed faster than a<=b, also using a>b-1 instead of a>b.
|
||||
if ((R.x-1<x && x<R.x+len) && (R.y-1<y && y<R.y+len)) {
|
||||
//Menu
|
||||
Bangle.removeAllListeners("touch");
|
||||
Bangle.removeAllListeners("swipe");
|
||||
backToMenu = true;
|
||||
E.showMenu(paMenu);
|
||||
} else if ((R.x-1<x && x<R.x+len) && (R.y2-len<y && y<R.y2+1)) {
|
||||
//Wake
|
||||
gadgetbridgeWake();
|
||||
gadgetbridgeWake();
|
||||
} else if ((R.x2-len<x && x<R.x2+1) && (R.y-1<y && y<R.y+len)) {
|
||||
//Srch
|
||||
Bangle.removeAllListeners("touch");
|
||||
Bangle.removeAllListeners("swipe");
|
||||
E.showMenu(searchMenu);
|
||||
} else if ((R.x2-len<x && x<R.x2+1) && (R.y2-len<y && y<R.y2+1)) {
|
||||
//Speed
|
||||
Bangle.removeAllListeners("touch");
|
||||
Bangle.removeAllListeners("swipe");
|
||||
E.showMenu(speedMenu);
|
||||
} else if ((R.x-1<x && x<R.x+len) && (R.y+R.h/2-len/2<y && y<R.y+R.h/2+len/2)) {
|
||||
//Previous
|
||||
btMsg("service", standardCls, "player.previoustrack");
|
||||
} else if ((R.x2-len+1<x && x<R.x2+1) && (R.y+R.h/2-len/2<y && y<R.y+R.h/2+len/2)) {
|
||||
//Next
|
||||
btMsg("service", standardCls, "player.nexttrack");
|
||||
} else if ((R.x-1<x && x<R.x2+1) && (R.y-1<y && y<R.y2+1)){
|
||||
//play/pause
|
||||
btMsg("service", standardCls, "player.toggle");
|
||||
}
|
||||
}
|
||||
|
||||
// Swipe handler for main layout, used to jump backward and forward within a podcast episode.
|
||||
function swipeHandler(LR, _) {
|
||||
if (LR==-1) {
|
||||
btMsg("service", standardCls, "player.jumpforward");
|
||||
}
|
||||
if (LR==1) {
|
||||
btMsg("service", standardCls, "player.jumpbackward");
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation input on the main layout
|
||||
function setUI() {
|
||||
// Bangle.setUI code from rigrig's smessages app for volume control: https://git.tubul.net/rigrig/BangleApps/src/branch/personal/apps/smessages/app.js
|
||||
Bangle.setUI(
|
||||
{mode : "updown", back : load},
|
||||
ud => {
|
||||
if (ud) Bangle.musicControl(ud>0 ? "volumedown" : "volumeup");
|
||||
}
|
||||
);
|
||||
Bangle.on("touch", touchHandler);
|
||||
Bangle.on("swipe", swipeHandler);
|
||||
}
|
||||
|
||||
/*
|
||||
The functions for interacting with Android and the Podcast Addict app
|
||||
*/
|
||||
|
||||
pkg = "com.bambuna.podcastaddict";
|
||||
standardCls = pkg + ".receiver.PodcastAddictPlayerReceiver";
|
||||
updateCls = pkg + ".receiver.PodcastAddictBroadcastReceiver";
|
||||
speed = 1.0;
|
||||
|
||||
simpleSearch = "";
|
||||
|
||||
function simpleSearchTerm() { // input a simple search term without tags, overrides search with tags (artist and track)
|
||||
require("textinput").input({
|
||||
text: simpleSearch
|
||||
}).then(result => {
|
||||
simpleSearch = result;
|
||||
}).then(() => {
|
||||
E.showMenu(searchMenu);
|
||||
});
|
||||
}
|
||||
|
||||
function searchPlayWOTags() { //make a search and play using entered terms
|
||||
searchString = simpleSearch;
|
||||
Bluetooth.println(JSON.stringify({
|
||||
t: "intent",
|
||||
action: "android.media.action.MEDIA_PLAY_FROM_SEARCH",
|
||||
package: pkg,
|
||||
target: "activity",
|
||||
extra: {
|
||||
query: searchString
|
||||
},
|
||||
flags: ["FLAG_ACTIVITY_NEW_TASK"]
|
||||
}));
|
||||
}
|
||||
|
||||
function gadgetbridgeWake() {
|
||||
Bluetooth.println(JSON.stringify({
|
||||
t: "intent",
|
||||
target: "activity",
|
||||
flags: ["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_CLEAR_TASK", "FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS", "FLAG_ACTIVITY_NO_ANIMATION"],
|
||||
package: "gadgetbridge",
|
||||
class: "nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity"
|
||||
}));
|
||||
}
|
||||
|
||||
// For stringing together the action for Podcast Addict to perform
|
||||
function actFn(actName, activOrServ) {
|
||||
return "com.bambuna.podcastaddict." + (activOrServ == "service" ? "service." : "") + actName;
|
||||
}
|
||||
|
||||
// Send the intent message to Gadgetbridge
|
||||
function btMsg(activOrServ, cls, actName, xtra) {
|
||||
|
||||
Bluetooth.println(JSON.stringify({
|
||||
t: "intent",
|
||||
action: actFn(actName, activOrServ),
|
||||
package: pkg,
|
||||
class: cls,
|
||||
target: "broadcastreceiver",
|
||||
extra: xtra
|
||||
}));
|
||||
}
|
||||
|
||||
// Get back to the main layout
|
||||
function backToGfx() {
|
||||
E.showMenu();
|
||||
g.clear();
|
||||
g.reset();
|
||||
Bangle.removeAllListeners("touch");
|
||||
Bangle.removeAllListeners("swipe");
|
||||
setUI();
|
||||
gfx();
|
||||
backToMenu = false;
|
||||
}
|
||||
|
||||
// Podcast Addict Menu
|
||||
var paMenu = {
|
||||
"": {
|
||||
title: " ",
|
||||
back: backToGfx
|
||||
},
|
||||
"Controls": () => {
|
||||
E.showMenu(controlMenu);
|
||||
},
|
||||
"Speed Controls": () => {
|
||||
E.showMenu(speedMenu);
|
||||
},
|
||||
"Search and play": () => {
|
||||
E.showMenu(searchMenu);
|
||||
},
|
||||
"Navigate and play": () => {
|
||||
E.showMenu(navigationMenu);
|
||||
},
|
||||
"Wake the android": () => {
|
||||
gadgetbridgeWake();
|
||||
gadgetbridgeWake();
|
||||
},
|
||||
"Exit PA Remote": ()=>{load();}
|
||||
};
|
||||
|
||||
|
||||
var controlMenu = {
|
||||
"": {
|
||||
title: " ",
|
||||
back: () => {if (backToMenu) E.showMenu(paMenu);
|
||||
if (!backToMenu) backToGfx();
|
||||
}
|
||||
},
|
||||
"Toggle Play/Pause": () => {
|
||||
btMsg("service", standardCls, "player.toggle");
|
||||
},
|
||||
"Jump Backward": () => {
|
||||
btMsg("service", standardCls, "player.jumpbackward");
|
||||
},
|
||||
"Jump Forward": () => {
|
||||
btMsg("service", standardCls, "player.jumpforward");
|
||||
},
|
||||
"Previous": () => {
|
||||
btMsg("service", standardCls, "player.previoustrack");
|
||||
},
|
||||
"Next": () => {
|
||||
btMsg("service", standardCls, "player.nexttrack");
|
||||
},
|
||||
"Play": () => {
|
||||
btMsg("service", standardCls, "player.play");
|
||||
},
|
||||
"Pause": () => {
|
||||
btMsg("service", standardCls, "player.pause");
|
||||
},
|
||||
"Stop": () => {
|
||||
btMsg("service", standardCls, "player.stop");
|
||||
},
|
||||
"Update": () => {
|
||||
btMsg("service", updateCls, "update");
|
||||
},
|
||||
"Messages Music Controls": () => {
|
||||
load("messagesmusic.app.js");
|
||||
},
|
||||
};
|
||||
|
||||
var speedMenu = {
|
||||
"": {
|
||||
title: " ",
|
||||
back: () => {if (backToMenu) E.showMenu(paMenu);
|
||||
if (!backToMenu) backToGfx();
|
||||
}
|
||||
},
|
||||
"Regular Speed": () => {
|
||||
speed = 1.0;
|
||||
btMsg("service", standardCls, "player.1xspeed");
|
||||
},
|
||||
"1.5x Regular Speed": () => {
|
||||
speed = 1.5;
|
||||
btMsg("service", standardCls, "player.1.5xspeed");
|
||||
},
|
||||
"2x Regular Speed": () => {
|
||||
speed = 2.0;
|
||||
btMsg("service", standardCls, "player.2xspeed");
|
||||
},
|
||||
//"Faster" : ()=>{speed+=0.1; speed=((speed>5.0)?5.0:speed); btMsg("service",standardCls,"player.customspeed",{arg1:speed});},
|
||||
//"Slower" : ()=>{speed-=0.1; speed=((speed<0.1)?0.1:speed); btMsg("service",standardCls,"player.customspeed",{arg1:speed});},
|
||||
};
|
||||
|
||||
var searchMenu = {
|
||||
"": {
|
||||
title: " ",
|
||||
|
||||
back: () => {if (backToMenu) E.showMenu(paMenu);
|
||||
if (!backToMenu) backToGfx();}
|
||||
|
||||
},
|
||||
"Search term": () => {
|
||||
simpleSearchTerm();
|
||||
},
|
||||
"Execute search and play": () => {
|
||||
btMsg("service", standardCls, "player.play");
|
||||
setTimeout(() => {
|
||||
searchPlayWOTags();
|
||||
setTimeout(() => {
|
||||
btMsg("service", standardCls, "player.play");
|
||||
}, 200);
|
||||
}, 1500);
|
||||
},
|
||||
"Simpler search and play" : searchPlayWOTags,
|
||||
};
|
||||
|
||||
var navigationMenu = {
|
||||
"": {
|
||||
title: " ",
|
||||
back: () => {if (backToMenu) E.showMenu(paMenu);
|
||||
if (!backToMenu) backToGfx();}
|
||||
},
|
||||
"Open Main Screen": () => {
|
||||
btMsg("activity", standardCls, "openmainscreen");
|
||||
},
|
||||
"Open Player Screen": () => {
|
||||
btMsg("activity", standardCls, "openplayer");
|
||||
},
|
||||
};
|
||||
|
||||
Bangle.loadWidgets();
|
||||
setUI();
|
||||
gfx();
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"id": "podadrem",
|
||||
"name": "Podcast Addict Remote",
|
||||
"shortName": "PA Remote",
|
||||
"version": "0.05",
|
||||
"description": "Control Podcast Addict on your android device.",
|
||||
"readme": "README.md",
|
||||
"type": "app",
|
||||
"tags": "remote,podcast,podcasts,radio,player,intent,intents,gadgetbridge,podadrem,pa remote",
|
||||
"icon": "app.png",
|
||||
"screenshots" : [ {"url":"screenshot1.png"}, {"url":"screenshot2.png"} ],
|
||||
"supports": ["BANGLEJS2"],
|
||||
"dependencies": { "textinput":"type"},
|
||||
"storage": [
|
||||
{"name":"podadrem.app.js","url":"app.js"},
|
||||
{"name":"podadrem.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.1 KiB |
|
@ -55,4 +55,6 @@
|
|||
0.48: Allow reading custom themes from files
|
||||
0.49: Now reloads settings properly after 'Calibrate Battery'
|
||||
0.50: Add Bangle.js 2 touchscreen calibration - for 2v16 or 2v15 cutting edge builds
|
||||
0.51: Add option for left-handed users
|
||||
0.51: Add setting for configuring a launcher
|
||||
0.52: Add option for left-handed users
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.51",
|
||||
"version": "0.52",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
|
|
@ -87,6 +87,7 @@ function showSystemMenu() {
|
|||
/*LANG*/'LCD': ()=>showLCDMenu(),
|
||||
/*LANG*/'Locale': ()=>showLocaleMenu(),
|
||||
/*LANG*/'Select Clock': ()=>showClockMenu(),
|
||||
/*LANG*/'Select Launcher': ()=>showLauncherMenu(),
|
||||
/*LANG*/'Date & Time': ()=>showSetTimeMenu()
|
||||
};
|
||||
|
||||
|
@ -685,6 +686,35 @@ function showClockMenu() {
|
|||
}
|
||||
return E.showMenu(clockMenu);
|
||||
}
|
||||
function showLauncherMenu() {
|
||||
var launcherApps = require("Storage").list(/\.info$/)
|
||||
.map(app => {var a=storage.readJSON(app, 1);return (a&&a.type == "launch")?a:undefined})
|
||||
.filter(app => app) // filter out any undefined apps
|
||||
.sort((a, b) => a.sortorder - b.sortorder);
|
||||
const launcherMenu = {
|
||||
'': {
|
||||
'title': /*LANG*/'Select Launcher',
|
||||
},
|
||||
'< Back': ()=>showSystemMenu(),
|
||||
};
|
||||
launcherApps.forEach((app, index) => {
|
||||
var label = app.name;
|
||||
if ((!settings.launcher && index === 0) || (settings.launcher === app.src)) {
|
||||
label = "* " + label;
|
||||
}
|
||||
launcherMenu[label] = () => {
|
||||
if (settings.launcher !== app.src) {
|
||||
settings.launcher = app.src;
|
||||
updateSettings();
|
||||
showMainMenu();
|
||||
}
|
||||
};
|
||||
});
|
||||
if (launcherApps.length === 0) {
|
||||
launcherMenu[/*LANG*/"No Launchers Found"] = () => { };
|
||||
}
|
||||
return E.showMenu(launcherMenu);
|
||||
}
|
||||
|
||||
function showSetTimeMenu() {
|
||||
d = new Date();
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
0.01: New app.
|
||||
0.02: Restructure menu.
|
||||
0.03: change handling of intent extras.
|
||||
0.04: New layout.
|
||||
0.05: Add widgets field. Tweak layout.
|
|
@ -0,0 +1,21 @@
|
|||
Requires Gadgetbridge 71.0 or later. Allow intents in Gadgetbridge in order for this app to work.
|
||||
|
||||
Touch input:
|
||||
|
||||
Press the different ui elements to control Podcast Addict and open menus. Press left or right arrow to go to previous/next track.
|
||||
|
||||
Swipe input:
|
||||
|
||||
Swipe left/right to go to previous/next track. Swipe up/down to change the volume.
|
||||
|
||||
It's possible to start tracks by searching with the remote. Search term without tags will override search with tags.
|
||||
|
||||
To start playing 'from cold' the command for previous/next track via touch or swipe can be used. Pressing just play/pause is not guaranteed to initiate spotify in all circumstances (this will probably change with subsequent releases).
|
||||
|
||||
In order to search to play or start music from the 'Saved' menu the Android device must be awake and unlocked. The remote can wake and unlock the device if the Bangle.js has been added as a 'trusted device' under Android Settings->Security->Smart Lock->Trusted devices.
|
||||
|
||||
The swipe logic was inspired by the implementation in [rigrig](https://git.tubul.net/rigrig/)'s Scrolling Messages.
|
||||
|
||||
Spotify Remote was created by [thyttan](https://github.com/thyttan/).
|
||||
|
||||
<a target="_blank" href="https://icons8.com/icon/63316/spotify">Spotify</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwhC/AFV3AAQVVDKQWHDB0HC5NwCyoYMCxZJKFxgwKCxowJC6xGOJBAWPGA4MGogXOIwdCmf/AAczkhIKC4VzCogAD+YZEC49PC5AABmgXO+czJYoYDC4gfCuRYGoUjDAZ4GUJlyn4XNukjIwMzmVHBAU/+YXKoZ0GmQLCDgQXIU5IVDC5JVCIwIECDA5HIR4hkBDAX0C5YAHOoIXJa4QRDoUikiOEm7vKE4YADmZ1FC5N/R48nC5tzFQMiokimYYHC4h4KJwX3Ow6QMOwoXGSAoAKIwrBNFxIXZJBxGHGB4WIGBouJDBgWLJJYWMDBIWODIwVRAH4AXA="))
|
|
@ -0,0 +1,281 @@
|
|||
/*
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"", flags:["flag1", "flag2",...], categories:["category1","category2",...], mimetype:"", data:"", package:"", class:"", target:"", extra:{someKey:"someValueOrString"}}));
|
||||
*/
|
||||
|
||||
var R;
|
||||
var backToMenu = false;
|
||||
var isPaused = true;
|
||||
var dark = g.theme.dark; // bool
|
||||
|
||||
// The main layout of the app
|
||||
function gfx() {
|
||||
//Bangle.drawWidgets();
|
||||
R = Bangle.appRect;
|
||||
marigin = 8;
|
||||
// g.drawString(str, x, y, solid)
|
||||
g.clearRect(R);
|
||||
g.reset();
|
||||
|
||||
if (dark) {g.setColor(0x07E0);} else {g.setColor(0x03E0);} // Green on dark theme, DarkGreen on light theme.
|
||||
g.setFont("4x6:2");
|
||||
g.setFontAlign(1, 0, 0);
|
||||
g.drawString("->", R.x2 - marigin, R.y + R.h/2);
|
||||
|
||||
g.setFontAlign(-1, 0, 0);
|
||||
g.drawString("<-", R.x + marigin, R.y + R.h/2);
|
||||
|
||||
g.setFontAlign(-1, 0, 1);
|
||||
g.drawString("<-", R.x + R.w/2, R.y + marigin);
|
||||
|
||||
g.setFontAlign(1, 0, 1);
|
||||
g.drawString("->", R.x + R.w/2, R.y2 - marigin);
|
||||
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.drawString("Play\nPause", R.x + R.w/2, R.y + R.h/2);
|
||||
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.drawString("Menu", R.x + 2*marigin, R.y + 2*marigin);
|
||||
|
||||
g.setFontAlign(-1, 1, 0);
|
||||
g.drawString("Wake", R.x + 2*marigin, R.y + R.h - 2*marigin);
|
||||
|
||||
g.setFontAlign(1, -1, 0);
|
||||
g.drawString("Srch", R.x + R.w - 2*marigin, R.y + 2*marigin);
|
||||
|
||||
g.setFontAlign(1, 1, 0);
|
||||
g.drawString("Saved", R.x + R.w - 2*marigin, R.y + R.h - 2*marigin);
|
||||
}
|
||||
|
||||
// Touch handler for main layout
|
||||
function touchHandler(_, xy) {
|
||||
x = xy.x;
|
||||
y = xy.y;
|
||||
len = (R.w<R.h+1)?(R.w/3):(R.h/3);
|
||||
|
||||
// doing a<b+1 seemed faster than a<=b, also using a>b-1 instead of a>b.
|
||||
if ((R.x-1<x && x<R.x+len) && (R.y-1<y && y<R.y+len)) {
|
||||
//Menu
|
||||
Bangle.removeAllListeners("touch");
|
||||
Bangle.removeAllListeners("swipe");
|
||||
backToMenu = true;
|
||||
E.showMenu(spotifyMenu);
|
||||
} else if ((R.x-1<x && x<R.x+len) && (R.y2-len<y && y<R.y2+1)) {
|
||||
//Wake
|
||||
gadgetbridgeWake();
|
||||
gadgetbridgeWake();
|
||||
} else if ((R.x2-len<x && x<R.x2+1) && (R.y-1<y && y<R.y+len)) {
|
||||
//Srch
|
||||
Bangle.removeAllListeners("touch");
|
||||
Bangle.removeAllListeners("swipe");
|
||||
E.showMenu(searchMenu);
|
||||
} else if ((R.x2-len<x && x<R.x2+1) && (R.y2-len<y && y<R.y2+1)) {
|
||||
//Saved
|
||||
Bangle.removeAllListeners("touch");
|
||||
Bangle.removeAllListeners("swipe");
|
||||
E.showMenu(savedMenu);
|
||||
} else if ((R.x-1<x && x<R.x+len) && (R.y+R.h/2-len/2<y && y<R.y+R.h/2+len/2)) {
|
||||
//Previous
|
||||
spotifyWidget("PREVIOUS");
|
||||
} else if ((R.x2-len+1<x && x<R.x2+1) && (R.y+R.h/2-len/2<y && y<R.y+R.h/2+len/2)) {
|
||||
//Next
|
||||
spotifyWidget("NEXT");
|
||||
} else if ((R.x-1<x && x<R.x2+1) && (R.y-1<y && y<R.y2+1)){
|
||||
//play/pause
|
||||
playPause = isPaused?"play":"pause";
|
||||
Bangle.musicControl(playPause);
|
||||
isPaused = !isPaused;
|
||||
}
|
||||
}
|
||||
|
||||
// Swipe handler for main layout, used to jump backward and forward within a podcast episode.
|
||||
function swipeHandler(LR, _) {
|
||||
if (LR==-1) {
|
||||
spotifyWidget("NEXT");
|
||||
}
|
||||
if (LR==1) {
|
||||
spotifyWidget("PREVIOUS");
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation input on the main layout
|
||||
function setUI() {
|
||||
// Bangle.setUI code from rigrig's smessages app for volume control: https://git.tubul.net/rigrig/BangleApps/src/branch/personal/apps/smessages/app.js
|
||||
Bangle.setUI(
|
||||
{mode : "updown", back : load},
|
||||
ud => {
|
||||
if (ud) Bangle.musicControl(ud>0 ? "volumedown" : "volumeup");
|
||||
}
|
||||
);
|
||||
Bangle.on("touch", touchHandler);
|
||||
Bangle.on("swipe", swipeHandler);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Get back to the main layout
|
||||
function backToGfx() {
|
||||
E.showMenu();
|
||||
g.clear();
|
||||
g.reset();
|
||||
Bangle.removeAllListeners("touch");
|
||||
Bangle.removeAllListeners("swipe");
|
||||
setUI();
|
||||
gfx();
|
||||
backToMenu = false;
|
||||
}
|
||||
|
||||
/*
|
||||
The functions for interacting with Android and the Spotify app
|
||||
*/
|
||||
|
||||
simpleSearch = "";
|
||||
function simpleSearchTerm() { // input a simple search term without tags, overrides search with tags (artist and track)
|
||||
require("textinput").input({text:simpleSearch}).then(result => {simpleSearch = result;}).then(() => {E.showMenu(searchMenu);});
|
||||
}
|
||||
|
||||
artist = "";
|
||||
function artistSearchTerm() { // input artist to search for
|
||||
require("textinput").input({text:artist}).then(result => {artist = result;}).then(() => {E.showMenu(searchMenu);});
|
||||
}
|
||||
|
||||
track = "";
|
||||
function trackSearchTerm() { // input track to search for
|
||||
require("textinput").input({text:track}).then(result => {track = result;}).then(() => {E.showMenu(searchMenu);});
|
||||
}
|
||||
|
||||
album = "";
|
||||
function albumSearchTerm() { // input album to search for
|
||||
require("textinput").input({text:album}).then(result => {album = result;}).then(() => {E.showMenu(searchMenu);});
|
||||
}
|
||||
|
||||
function searchPlayWOTags() {//make a spotify search and play using entered terms
|
||||
searchString = simpleSearch;
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:searchString}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
|
||||
}
|
||||
|
||||
function searchPlayWTags() {//make a spotify search and play using entered terms
|
||||
searchString = (artist=="" ? "":("artist:\""+artist+"\"")) + ((artist!="" && track!="") ? " ":"") + (track=="" ? "":("track:\""+track+"\"")) + (((artist!="" && album!="") || (track!="" && album!="")) ? " ":"") + (album=="" ? "":(" album:\""+album+"\""));
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:searchString}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
|
||||
}
|
||||
|
||||
function playVreden() {//Play the track "Vreden" by Sara Parkman via spotify uri-link
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
|
||||
}
|
||||
|
||||
function playVredenAlternate() {//Play the track "Vreden" by Sara Parkman via spotify uri-link
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK"]}));
|
||||
}
|
||||
|
||||
function searchPlayVreden() {//Play the track "Vreden" by Sara Parkman via search and play
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'artist:"Sara Parkman" track:"Vreden"'}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
|
||||
}
|
||||
|
||||
function openAlbum() {//Play EP "The Blue Room" by Coldplay
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:album:3MVb2CWB36x7VwYo5sZmf2", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK"]}));
|
||||
}
|
||||
|
||||
function searchPlayAlbum() {//Play EP "The Blue Room" by Coldplay via search and play
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'album:"The blue room" artist:"Coldplay"', "android.intent.extra.focus":"vnd.android.cursor.item/album"}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
|
||||
}
|
||||
|
||||
function spotifyWidget(action) {
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:("com.spotify.mobile.android.ui.widget."+action), package:"com.spotify.music", target:"broadcastreceiver"}));
|
||||
}
|
||||
|
||||
function gadgetbridgeWake() {
|
||||
Bluetooth.println(JSON.stringify({t:"intent", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_CLEAR_TASK", "FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS", "FLAG_ACTIVITY_NO_ANIMATION"], package:"gadgetbridge", class:"nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity"}));
|
||||
}
|
||||
|
||||
function spotifyPlaylistDW() {
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXcRfaeEbxXIgb:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
|
||||
}
|
||||
|
||||
function spotifyPlaylistDM1() {
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E365VyzxE0mxF:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
|
||||
}
|
||||
|
||||
function spotifyPlaylistDM2() {
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E38LZHLFnrM61:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
|
||||
}
|
||||
|
||||
function spotifyPlaylistDM3() {
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E36RU87qzgBFP:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
|
||||
}
|
||||
|
||||
function spotifyPlaylistDM4() {
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E396gGyCXEBFh:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
|
||||
}
|
||||
|
||||
function spotifyPlaylistDM5() {
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E37a0Tt6CKJLP:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
|
||||
}
|
||||
|
||||
function spotifyPlaylistDM6() {
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E36UIQLQK79od:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
|
||||
}
|
||||
|
||||
function spotifyPlaylistDD() {
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1EfWFiI7QfIAKq:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
|
||||
}
|
||||
|
||||
function spotifyPlaylistRR() {
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXbs0XkE2V8sMO:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
|
||||
}
|
||||
|
||||
// Spotify Remote Menu
|
||||
var spotifyMenu = {
|
||||
"" : { title : " ",
|
||||
back: backToGfx },
|
||||
"Controls" : ()=>{E.showMenu(controlMenu);},
|
||||
"Search and play" : ()=>{E.showMenu(searchMenu);},
|
||||
"Saved music" : ()=>{E.showMenu(savedMenu);},
|
||||
"Wake the android" : function() {gadgetbridgeWake();gadgetbridgeWake();},
|
||||
"Exit Spotify Remote" : ()=>{load();}
|
||||
};
|
||||
|
||||
|
||||
var controlMenu = {
|
||||
"" : { title : " ",
|
||||
back: () => {if (backToMenu) E.showMenu(spotifyMenu);
|
||||
if (!backToMenu) backToGfx();} },
|
||||
"Play" : ()=>{Bangle.musicControl("play");},
|
||||
"Pause" : ()=>{Bangle.musicControl("pause");},
|
||||
"Previous" : ()=>{spotifyWidget("PREVIOUS");},
|
||||
"Next" : ()=>{spotifyWidget("NEXT");},
|
||||
"Play (widget, next then previous)" : ()=>{spotifyWidget("NEXT"); spotifyWidget("PREVIOUS");},
|
||||
"Messages Music Controls" : ()=>{load("messagesmusic.app.js");},
|
||||
};
|
||||
|
||||
var searchMenu = {
|
||||
"" : { title : " ",
|
||||
back: () => {if (backToMenu) E.showMenu(spotifyMenu);
|
||||
if (!backToMenu) backToGfx();} },
|
||||
"Search term w/o tags" : ()=>{simpleSearchTerm();},
|
||||
"Execute search and play w/o tags" : ()=>{searchPlayWOTags();},
|
||||
"Search term w tag \"artist\"" : ()=>{artistSearchTerm();},
|
||||
"Search term w tag \"track\"" : ()=>{trackSearchTerm();},
|
||||
"Search term w tag \"album\"" : ()=>{albumSearchTerm();},
|
||||
"Execute search and play with tags" : ()=>{searchPlayWTags();},
|
||||
};
|
||||
|
||||
var savedMenu = {
|
||||
"" : { title : " ",
|
||||
back: () => {if (backToMenu) E.showMenu(spotifyMenu);
|
||||
if (!backToMenu) backToGfx();} },
|
||||
"Play Discover Weekly" : ()=>{spotifyPlaylistDW();},
|
||||
"Play Daily Mix 1" : ()=>{spotifyPlaylistDM1();},
|
||||
"Play Daily Mix 2" : ()=>{spotifyPlaylistDM2();},
|
||||
"Play Daily Mix 3" : ()=>{spotifyPlaylistDM3();},
|
||||
"Play Daily Mix 4" : ()=>{spotifyPlaylistDM4();},
|
||||
"Play Daily Mix 5" : ()=>{spotifyPlaylistDM5();},
|
||||
"Play Daily Mix 6" : ()=>{spotifyPlaylistDM6();},
|
||||
"Play Daily Drive" : ()=>{spotifyPlaylistDD();},
|
||||
"Play Release Radar" : ()=>{spotifyPlaylistRR();},
|
||||
"Play \"Vreden\" by Sara Parkman via uri-link" : ()=>{playVreden();},
|
||||
"Open \"The Blue Room\" EP (no autoplay)" : ()=>{openAlbum();},
|
||||
"Play \"The Blue Room\" EP via search&play" : ()=>{searchPlayAlbum();},
|
||||
};
|
||||
|
||||
Bangle.loadWidgets();
|
||||
setUI();
|
||||
gfx();
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "spotrem",
|
||||
"name": "Remote for Spotify",
|
||||
"version": "0.05",
|
||||
"description": "Control spotify on your android device.",
|
||||
"readme": "README.md",
|
||||
"type": "app",
|
||||
"tags": "spotify,music,player,remote,control,intent,intents,gadgetbridge,spotrem",
|
||||
"icon": "app.png",
|
||||
"screenshots" : [ {"url":"screenshot1.png"}, {"url":"screenshot2.png"} ],
|
||||
"supports": ["BANGLEJS2"],
|
||||
"dependencies": { "textinput":"type"},
|
||||
"storage": [
|
||||
{"name":"spotrem.app.js","url":"app.js"},
|
||||
{"name":"spotrem.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 3.2 KiB |
|
@ -0,0 +1 @@
|
|||
0.01: New widget!
|
|
@ -0,0 +1,23 @@
|
|||
# Messages Grid Widget
|
||||
|
||||
Widget that displays multiple notification icons in a grid.
|
||||
The widget has a fixed size: if there are multiple notifications it uses smaller
|
||||
icons.
|
||||
It shows a single icon per application, so if you have two SMS messages, the
|
||||
grid only has one SMS icon.
|
||||
If there are multiple messages waiting, the total number is shown in the
|
||||
bottom-right corner.
|
||||
|
||||
Example: one SMS, one Signal, and two WhatsApp messages:
|
||||

|
||||
|
||||
## Installation
|
||||
This widget needs the [`messages`](/?id=messages) app to handle notifications.
|
||||
|
||||
## Settings
|
||||
This widget uses the `Widget` settings from the `messages` app:
|
||||
|
||||
### Widget
|
||||
* `Flash icon` Toggle flashing of the widget icons.
|
||||
<!-- * `Show read` - Also show the widget when there are only old messages. -->
|
||||
* `Widget messages` Not used by this widget, but you should select `Hide` to hide the default widget.
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "widmsggrid",
|
||||
"name": "Messages Grid Widget",
|
||||
"version": "0.01",
|
||||
"description": "Widget that display notification icons in a grid",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"dependencies": {"messages":"app"},
|
||||
"tags": "tool,system",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"widmsggrid.wid.js","url":"widget.js"}
|
||||
],
|
||||
"screenshots": [{"url":"screenshot.png"}]
|
||||
}
|
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,92 @@
|
|||
(function () {
|
||||
if (global.MESSAGES) return; // don't load widget while in the app
|
||||
let settings = require('Storage').readJSON("messages.settings.json", true) || {};
|
||||
const s = {
|
||||
flash: (settings.flash === undefined) ? true : !!settings.flash,
|
||||
showRead: !!settings.showRead,
|
||||
};
|
||||
delete settings;
|
||||
WIDGETS["msggrid"] = {
|
||||
area: "tl", width: 0,
|
||||
flash: s.flash,
|
||||
showRead: s.showRead,
|
||||
init: function() {
|
||||
// runs on first draw
|
||||
delete w.init; // don't run again
|
||||
Bangle.on("touch", w.touch);
|
||||
Bangle.on("message", w.listener);
|
||||
w.listener(); // update status now
|
||||
},
|
||||
draw: function () {
|
||||
if (w.init) w.init();
|
||||
// If we had a setTimeout queued from the last time we were called, remove it
|
||||
if (w.t) {
|
||||
clearTimeout(w.t);
|
||||
delete w.t;
|
||||
}
|
||||
if (!w.width) return;
|
||||
const b = w.flash && w.status === "new" && ((Date.now() / 1000) & 1), // Blink(= inverse colors) on this second?
|
||||
// show multiple icons in a grid, by scaling them down
|
||||
cols = Math.ceil(Math.sqrt(w.srcs.length - 0.1)); // cols===rows, -0.1 to work around rounding error
|
||||
g.reset().clearRect(w.x, w.y, w.x + w.width - 1, w.y + 24)
|
||||
.setClipRect(w.x, w.y, w.x + w.width - 1, w.y + 24); // guard against oversized icons
|
||||
let r = 0, c = 0; // row, column
|
||||
const offset = pos => Math.floor(pos / cols * 24); // pixel offset for position in row/column
|
||||
w.srcs.forEach(src => {
|
||||
const appColor = require("messages").getMessageImageCol(src, require("messages").getMessageImageCol("alert"));
|
||||
let colors = [g.theme.bg, g.setColor(appColor).getColor()];
|
||||
if (b) {
|
||||
if (colors[1] == g.theme.fg) colors = colors.reverse();
|
||||
else colors[1] = g.theme.fg;
|
||||
}
|
||||
g.setColor(colors[1]).setBgColor(colors[0]);
|
||||
g.drawImage(require("messages").getMessageImage(src, "alert"), w.x+offset(c), w.y+offset(r), { scale: 1 / cols });
|
||||
if (++c >= cols) {
|
||||
c = 0;
|
||||
r++;
|
||||
}
|
||||
});
|
||||
if (w.total > 1) {
|
||||
// show total number of messages in bottom-right corner
|
||||
g.reset();
|
||||
if (w.total < 10) g.fillCircle(w.x + w.width - 5, w.y + 20, 4); // single digits get a round background, double digits fill their rectangle
|
||||
g.setColor(g.theme.bg).setBgColor(g.theme.fg)
|
||||
.setFont('6x8').setFontAlign(1, 1)
|
||||
.drawString(w.total, w.x + w.width - 1, w.y + 24, w.total > 9);
|
||||
}
|
||||
if (w.flash && w.status === "new") w.t = setTimeout(w.draw, 1000); // schedule redraw while blinking
|
||||
}, show: function () {
|
||||
w.width = 24;
|
||||
w.srcs = require("messages").getMessages()
|
||||
.filter(m => !['call', 'map', 'music'].includes(m.id))
|
||||
.filter(m => m.new || w.showRead)
|
||||
.map(m => m.src);
|
||||
w.total = w.srcs.length;
|
||||
w.srcs = w.srcs.filter((src, i, uniq) => uniq.indexOf(src) === i); // keep unique entries only
|
||||
Bangle.drawWidgets();
|
||||
Bangle.setLCDPower(1); // turns screen on
|
||||
}, hide: function () {
|
||||
w.width = 0;
|
||||
w.srcs = [];
|
||||
w.total = 0;
|
||||
Bangle.drawWidgets();
|
||||
}, touch: function (b, c) {
|
||||
if (!w || !w.width) return; // widget not shown
|
||||
if (process.env.HWVERSION < 2) {
|
||||
// Bangle.js 1: open app when on clock we touch the side with widget
|
||||
if (!Bangle.CLOCK) return;
|
||||
const m = Bangle.appRect / 2;
|
||||
if ((w.x < m && b !== 1) || (w.x > m && b !== 2)) return;
|
||||
}
|
||||
// Bangle.js 2: open app when touching the widget
|
||||
else if (c.x < w.x || c.x > w.x + w.width || c.y < w.y || c.y > w.y + 24) return;
|
||||
load("messages.app.js");
|
||||
}, listener: function () {
|
||||
w.status = require("messages").status();
|
||||
if (w.status === "new" || (w.status === "old" && w.showRead)) w.show();
|
||||
else w.hide();
|
||||
}
|
||||
};
|
||||
delete s;
|
||||
const w = WIDGETS["msggrid"];
|
||||
})();
|
After Width: | Height: | Size: 10 KiB |