Create Pattern Launcher app

pull/1037/head
crazysaem 2021-12-08 16:37:42 +00:00
parent b10de5aa7b
commit b9f636d9db
10 changed files with 675 additions and 1 deletions

View File

@ -4800,6 +4800,22 @@
"screenshots":[
{ "url":"screenshot.png" }
]
},
{
"id": "ptlaunch",
"name": "Pattern Launcher",
"shortName": "Pattern Launcher",
"version": "0.01",
"description": "Directly launch apps from the clock screen with custom patterns.",
"icon": "app.png",
"tags": "tools",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{ "name": "ptlaunch.app.js", "url": "app.js" },
{ "name": "ptlaunch.boot.js", "url": "boot.js" },
{ "name": "ptlaunch.img", "url": "app-icon.js", "evaluate": true }
],
"data": [{"name":"ptlaunch.patterns.json"}]
}
]

1
apps/ptlaunch/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Initial creation of the pattern launch app

46
apps/ptlaunch/README.md Normal file
View File

@ -0,0 +1,46 @@
# Pattern Launcher
Directly launch apps from the clock screen with custom patterns.
## Usage
Create patterns and link them to apps in the Pattern Launcher app.
Then launch the linked apps directly from the clock screen by simply drawing the desired pattern.
## Screenshots and detailed steps
![](main_menu.png)
![](add_pattern.png)
![](select_app.png)
From the main menu you can:
- Add a new pattern and link it to an app (first entry)
- To create a new pattern first select "Add Pattern"
- Now draw any pattern you like, this will later launch the linked app from the clock screen
- If you don't like the pattern, simply re-draw it. The previous pattern will be discarded.
- If you are happy with the pattern tap on screen or press the button to continue
- Now select the app you want to launch with the pattern.
- Note, you can bind multiple patterns to the same app.
- Remove linked patterns (second entry)
- To remove a pattern first select "Remove Pattern"
- You will now see a list of apps that have patterns linked to them
- Simply select the app that you want to unlink. This will remove the saved pattern, but not the app itself!
- Note, that you can not actually preview the patterns. This makes removing patterns that are linked to the same app annoying. sorry!
- Disable the lock screen on the clock screen from the settings (third entry)
- To launch the app from the pattern on the clock screen the watch must be unlocked.
- If this annoys you, you can disable the lock on the clock screen from the setting here
## FAQ
1) Nothing happens when I draw on the clock screen!
Please double-check if you actually have a pattern linked to an app.
2) I have a pattern linked to an app and still nothing happens when I draw on the clock screen!
Make sure the watch is unlocked before you start drawing. If this bothers you, you can permanently disable the watch-lock from within the Pattern Launcher app (via the Settings).
3) I have done all that and still nothing happens!
Please note that drawing on the clock screen will not visually show the pattern you drew. It will start the app as soon as the pattern was recognized - this might take 1 or 2 seconds! If still nothing happens, that might be a bug, sorry!

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkE//ziEAiM//4ACmMAgMvA4fziIIBCAUgEAUCBwXxFIYYDCAvyHIgPCGwIgFCA0wAYIRCh49BLQoXB+AHEgYUBgQaCE4JGEHAZGDAAMBAQMjC4UBEw0Aj5PFDIchC4Q/BC5CtIgIXUGwIXDI6EBiCaCCYJ3RIganTa5AnEgbXIewwPGn4ICCA8hgESAoQABmUQgI2CCBQA/AAvzbIRuD/8xNwMTCBTfDPwbYEPAaPDf4LnFB4T/EEAS/Fj7vFZ4LvBgMiFIQXBCAwmEE4Q3BiUikTnHJAQFEJ4XwgERHgI/CJ4oAIC4QYBiYXDCxgXDgUzLQQXIGwpHDLoRHJgJmFO4arCO5MCK4QACh6nCJ4poDCAbGFe4QnEY4IgGG4oOCc4ofCbAj3C/8hiMSAoQYCiMRMQQQKAH4AGkMAJwsyiEBL4wQER4Z+DR5AQFX4ooCX44QGVobvOgMREAUQBwg3B+IXFc4cTmYUBgIXFgImCAAkf/59BkERIgMBBwo/BC5AkDCgwXOAAIMGI5xFBBgR3SJYinXa5A4EfAQQHewoABJAgfCCA/zFAMRn4OC/8xIAIWDCAJGBgIQBA=="))

416
apps/ptlaunch/app.js Normal file
View File

@ -0,0 +1,416 @@
var storage = require("Storage");
var DEBUG = false;
var log = (message) => {
if (DEBUG) {
console.log(JSON.stringify(message));
}
};
var CIRCLE_RADIUS = 25;
var CIRCLE_RADIUS_2 = CIRCLE_RADIUS * CIRCLE_RADIUS;
var CIRCLES = [
{ x: 25, y: 25, i: 0 },
{ x: 87, y: 25, i: 1 },
{ x: 150, y: 25, i: 2 },
{ x: 25, y: 87, i: 3 },
{ x: 87, y: 87, i: 4 },
{ x: 150, y: 87, i: 5 },
{ x: 25, y: 150, i: 6 },
{ x: 87, y: 150, i: 7 },
{ x: 150, y: 150, i: 8 },
];
var showMainMenu = () => {
log("loading patterns");
var storedPatterns = storage.readJSON("ptlaunch.patterns.json", 1) || {};
var mainmenu = {
"": {
title: "Pattern Launcher",
},
"< Back": () => {
log("cancel");
load();
},
"Add Pattern": () => {
log("creating pattern");
createPattern().then((pattern) => {
log("got pattern");
log(pattern);
log(pattern.length);
var confirmPromise = new Promise((resolve) => resolve(true));
if (!!storedPatterns[pattern]) {
log("pattern already exists. show confirmation prompt");
confirmPromise = E.showPrompt("Pattern already exists\nOverwrite?", {
title: "Confirm",
buttons: { Yes: true, No: false },
});
}
confirmPromise.then((confirm) => {
log("confirmPromise resolved: " + confirm);
if (!confirm) {
showMainMenu();
return;
}
log("selecting app");
getSelectedApp().then((app) => {
E.showMessage("Saving...");
log("got app");
log("saving pattern");
storedPatterns[pattern] = {
app: { name: app.name, src: app.src },
};
storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
showMainMenu();
});
});
});
},
"Remove Pattern": () => {
log("selecting pattern through app");
getStoredPatternViaApp(storedPatterns).then((pattern) => {
E.showMessage("Deleting...");
delete storedPatterns[pattern];
storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
showMainMenu();
});
},
Settings: () => {
var settings = storedPatterns["settings"] || {};
var settingsmenu = {
"": {
title: "Pattern Settings",
},
"< Back": () => {
log("cancel");
load();
},
};
if (settings.lockDisabled) {
settingsmenu["Enable lock"] = () => {
settings.lockDisabled = false;
storedPatterns["settings"] = settings;
Bangle.setOptions({ lockTimeout: 1000 * 30 });
storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
showMainMenu();
};
} else {
settingsmenu["Disable lock"] = () => {
settings.lockDisabled = true;
storedPatterns["settings"] = settings;
storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
Bangle.setOptions({ lockTimeout: 1000 * 60 * 60 * 24 * 365 });
showMainMenu();
};
}
E.showMenu(settingsmenu);
},
};
E.showMenu(mainmenu);
};
var drawCircle = (circle) => {
g.fillCircle(circle.x, circle.y, CIRCLE_RADIUS);
};
var positions = [];
var createPattern = () => {
return new Promise((resolve) => {
E.showMenu();
g.clear();
g.setColor(0, 0, 0);
CIRCLES.forEach((circle) => drawCircle(circle));
var pattern = [];
var isFinished = false;
var finishHandler = () => {
if (pattern.length === 0 || isFinished) {
return;
}
log("Pattern is finished.");
isFinished = true;
Bangle.removeListener("drag", dragHandler);
Bangle.removeListener("tap", finishHandler);
resolve(pattern.join(""));
};
setWatch(() => finishHandler(), BTN);
setTimeout(() => Bangle.on("tap", finishHandler), 250);
var dragHandler = (position) => {
positions.push(position);
debounce().then(() => {
if (isFinished) {
return;
}
E.showMessage("Calculating...");
var t0 = Date.now();
log(positions.length);
var circlesClone = cloneCirclesArray();
pattern = [];
var step = Math.floor(positions.length / 100) + 1;
var p, a, b, circle;
for (var i = 0; i < positions.length; i += step) {
p = positions[i];
circle = circlesClone[0];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(0, 1);
}
}
circle = circlesClone[1];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(1, 1);
}
}
circle = circlesClone[2];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(2, 1);
}
}
circle = circlesClone[3];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(3, 1);
}
}
circle = circlesClone[4];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(4, 1);
}
}
circle = circlesClone[5];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(5, 1);
}
}
circle = circlesClone[6];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(6, 1);
}
}
circle = circlesClone[7];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(7, 1);
}
}
circle = circlesClone[8];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(8, 1);
}
}
}
var tx = Date.now();
log(tx - t0);
positions = [];
var t1 = Date.now();
log(t1 - t0);
log("pattern:");
log(pattern);
log("redrawing");
g.clear();
g.setColor(0, 0, 0);
CIRCLES.forEach((circle) => drawCircle(circle));
g.setColor(1, 1, 1);
g.setFontAlign(0, 0);
g.setFont("6x8", 4);
pattern.forEach((circleIndex, patternIndex) => {
var circle = CIRCLES[circleIndex];
g.drawString(patternIndex + 1, circle.x, circle.y);
});
var t2 = Date.now();
log(t2 - t0);
});
};
Bangle.on("drag", dragHandler);
});
};
var getAppList = () => {
var appList = storage
.list(/\.info$/)
.map((appInfoFileName) => {
var appInfo = storage.readJSON(appInfoFileName, 1);
return (
appInfo && {
name: appInfo.name,
// type: appInfo.type,
// icon: appInfo.icon,
sortorder: appInfo.sortorder,
src: appInfo.src,
}
);
})
.filter((app) => app && !!app.src);
appList.sort((a, b) => {
var n = (0 | a.sortorder) - (0 | b.sortorder);
if (n) return n; // do sortorder first
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return 0;
});
return appList;
};
var getSelectedApp = () => {
E.showMessage("Loading apps...");
return new Promise((resolve) => {
var selectAppMenu = {
"": {
title: "Select App",
},
"< Cancel": () => {
log("cancel");
showMainMenu();
},
};
var appList = getAppList();
appList.forEach((app) => {
selectAppMenu[app.name] = () => {
log("app selected");
log(app);
resolve(app);
};
});
E.showMenu(selectAppMenu);
});
};
var getStoredPatternViaApp = (storedPatterns) => {
E.showMessage("Loading patterns...");
log("getStoredPatternViaApp");
return new Promise((resolve) => {
var selectPatternMenu = {
"": {
title: "Select App",
},
"< Cancel": () => {
log("cancel");
showMainMenu();
},
};
log(storedPatterns);
var patterns = Object.keys(storedPatterns);
log(patterns);
patterns.forEach((pattern) => {
if (!!pattern) {
if (!!storedPatterns[pattern]) {
var app = storedPatterns[pattern].app;
if (!!app && !!app.name) {
var appName = app.name;
var i = 0;
while (appName in selectPatternMenu[app.name]) {
appName = app.name + i;
i++;
}
selectPatternMenu[appName] = () => {
log("pattern via app selected");
log(pattern);
log(app);
resolve(pattern);
};
}
}
}
});
E.showMenu(selectPatternMenu);
});
};
showMainMenu();
//////
// lib functions
//////
var debounceTimeoutId;
var debounce = (delay) => {
if (debounceTimeoutId) {
clearTimeout(debounceTimeoutId);
}
return new Promise((resolve) => {
debounceTimeoutId = setTimeout(() => {
debounceTimeoutId = undefined;
resolve();
}, delay || 500);
});
};
var cloneCirclesArray = () => {
var circlesClone = Array(CIRCLES.length);
for (var i = 0; i < CIRCLES.length; i++) {
circlesClone[i] = CIRCLES[i];
}
return circlesClone;
};

BIN
apps/ptlaunch/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

194
apps/ptlaunch/boot.js Normal file
View File

@ -0,0 +1,194 @@
var DEBUG = true;
var log = (message) => {
if (DEBUG) {
console.log(JSON.stringify(message));
}
};
var CIRCLE_RADIUS = 25;
var CIRCLE_RADIUS_2 = CIRCLE_RADIUS * CIRCLE_RADIUS;
var CIRCLES = [
{ x: 25, y: 25, i: 0 },
{ x: 87, y: 25, i: 1 },
{ x: 150, y: 25, i: 2 },
{ x: 25, y: 87, i: 3 },
{ x: 87, y: 87, i: 4 },
{ x: 150, y: 87, i: 5 },
{ x: 25, y: 150, i: 6 },
{ x: 87, y: 150, i: 7 },
{ x: 150, y: 150, i: 8 },
];
var storedPatterns;
var positions = [];
var dragHandler = (position) => {
positions.push(position);
debounce().then(() => {
log(positions.length);
var circlesClone = cloneCirclesArray();
var pattern = [];
var step = Math.floor(positions.length / 100) + 1;
var p, a, b, circle;
for (var i = 0; i < positions.length; i += step) {
p = positions[i];
circle = circlesClone[0];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(0, 1);
}
}
circle = circlesClone[1];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(1, 1);
}
}
circle = circlesClone[2];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(2, 1);
}
}
circle = circlesClone[3];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(3, 1);
}
}
circle = circlesClone[4];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(4, 1);
}
}
circle = circlesClone[5];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(5, 1);
}
}
circle = circlesClone[6];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(6, 1);
}
}
circle = circlesClone[7];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(7, 1);
}
}
circle = circlesClone[8];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circlesClone.splice(8, 1);
}
}
}
positions = [];
pattern = pattern.join("");
if (!!pattern) {
if (!!storedPatterns[pattern]) {
var app = storedPatterns[pattern].app;
if (!!app && !!app.src) {
Bangle.removeListener("drag", dragHandler);
load(app.src);
}
}
}
});
};
var debounceTimeoutId;
var debounce = (delay) => {
if (debounceTimeoutId) {
clearTimeout(debounceTimeoutId);
}
return new Promise((resolve) => {
debounceTimeoutId = setTimeout(() => {
debounceTimeoutId = undefined;
resolve();
}, delay || 500);
});
};
var cloneCirclesArray = () => {
var circlesClone = Array(CIRCLES.length);
for (var i = 0; i < CIRCLES.length; i++) {
circlesClone[i] = CIRCLES[i];
}
return circlesClone;
};
(function () {
var sui = Bangle.setUI;
Bangle.setUI = function (mode, cb) {
sui(mode, cb);
if (!mode) {
Bangle.removeListener("drag", dragHandler);
storedPatterns = {};
return;
}
if (!mode.startsWith("clock")) {
storedPatterns = {};
Bangle.removeListener("drag", dragHandler);
return;
}
var storage = require("Storage");
storedPatterns = storage.readJSON("ptlaunch.patterns.json", 1) || {};
if (Object.keys(storedPatterns).length > 0) {
Bangle.on("drag", dragHandler);
if (storedPatterns.settings.lockDisabled) {
Bangle.setOptions({ lockTimeout: 1000 * 60 * 60 * 24 * 365 });
}
}
};
})();

BIN
apps/ptlaunch/main_menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB