Merge pull request #1051 from crazysaem/master

Update Pattern Launcher: Show saved patterns with linked apps
master
Gordon Williams 2021-12-13 09:04:38 +00:00 committed by GitHub
commit 1edcc1f79e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 338 additions and 148 deletions

View File

@ -4829,9 +4829,10 @@
"id": "ptlaunch",
"name": "Pattern Launcher",
"shortName": "Pattern Launcher",
"version": "0.02",
"version": "0.10",
"description": "Directly launch apps from the clock screen with custom patterns.",
"icon": "app.png",
"screenshots": [{"url":"main_menu_add.png"}, {"url":"add_pattern.png"}, {"url":"select_app.png"}, {"url":"main_menu_manage.png"}, {"url":"manage_patterns.png"}],
"tags": "tools",
"supports": ["BANGLEJS2"],
"readme": "README.md",

View File

@ -1,2 +1,4 @@
0.01: Initial creation of the pattern launch app
0.02: Turn on lcd when launching an app if the lock screen was disabled in the settings
0.03: Make tap to confirm new pattern more reliable. Also allow for easier creation of single circle patterns.
0.10: Improve the management of existing patterns: Draw the linked pattern on the left hand side of the app name within a scroller, similar to the default launcher. Slighlty clean up the code to make it less horrible.

View File

@ -8,25 +8,32 @@ 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
## Add Pattern Screenshots
![](main_menu.png)
![](main_menu_add.png)
![](add_pattern.png)
![](select_app.png)
## Manage Pattern Screenshots
![](main_menu_manage.png)
![](manage_patterns.png)
## Detailed Steps
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
- You can also draw a single-circle pattern (meaning a single tap on one circle) instead of drawing a 'complex' pattern
- 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!
- Manage created patterns (second entry)
- To manage your patterns first select "Manage Patterns"
- You will now see a scrollabe list of patterns + linked apps
- If you want to deletion a pattern (and unlink the app) simply tap on it, and confirm the deletion
- 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

View File

@ -1,26 +1,6 @@
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 storage = require("Storage");
var showMainMenu = () => {
log("loading patterns");
@ -36,7 +16,7 @@ var showMainMenu = () => {
},
"Add Pattern": () => {
log("creating pattern");
createPattern().then((pattern) => {
recognizeAndDrawPattern().then((pattern) => {
log("got pattern");
log(pattern);
log(pattern.length);
@ -73,17 +53,32 @@ var showMainMenu = () => {
});
});
},
"Remove Pattern": () => {
"Manage Patterns": () => {
log("selecting pattern through app");
getStoredPatternViaApp(storedPatterns).then((pattern) => {
showScrollerContainingAppsWithPatterns().then((selected) => {
var pattern = selected.pattern;
var appName = selected.appName;
if (pattern === "back") {
showMainMenu();
} else {
E.showPrompt(appName + "\n\npattern:\n" + pattern, {
title: "Delete?",
buttons: { Yes: true, No: false },
}).then((confirm) => {
if (confirm) {
E.showMessage("Deleting...");
delete storedPatterns[pattern];
storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
showMainMenu();
} else {
showMainMenu();
}
});
}
});
},
Settings: () => {
var settings = storedPatterns["settings"] || {};
var settings = storedPatterns.settings || {};
var settingsmenu = {
"": {
@ -98,7 +93,7 @@ var showMainMenu = () => {
if (settings.lockDisabled) {
settingsmenu["Enable lock"] = () => {
settings.lockDisabled = false;
storedPatterns["settings"] = settings;
storedPatterns.settings = settings;
Bangle.setOptions({ lockTimeout: 1000 * 30 });
storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
showMainMenu();
@ -106,7 +101,7 @@ var showMainMenu = () => {
} else {
settingsmenu["Disable lock"] = () => {
settings.lockDisabled = true;
storedPatterns["settings"] = settings;
storedPatterns.settings = settings;
storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
Bangle.setOptions({ lockTimeout: 1000 * 60 * 60 * 24 * 365 });
showMainMenu();
@ -119,12 +114,8 @@ var showMainMenu = () => {
E.showMenu(mainmenu);
};
var drawCircle = (circle) => {
g.fillCircle(circle.x, circle.y, CIRCLE_RADIUS);
};
var positions = [];
var createPattern = () => {
var recognizeAndDrawPattern = () => {
return new Promise((resolve) => {
E.showMenu();
g.clear();
@ -147,13 +138,29 @@ var createPattern = () => {
setWatch(() => finishHandler(), BTN);
setTimeout(() => Bangle.on("tap", finishHandler), 250);
positions = [];
var dragHandler = (position) => {
log(position);
positions.push(position);
debounce().then(() => {
if (isFinished) {
return;
}
// This might actually be a 'tap' event.
// Use this check in addition to the actual tap handler to make it more reliable
if (pattern.length > 0 && positions.length === 2) {
if (
positions[0].x === positions[1].x &&
positions[0].y === positions[1].y
) {
finishHandler();
positions = [];
return;
}
}
E.showMessage("Calculating...");
var t0 = Date.now();
@ -269,18 +276,7 @@ var createPattern = () => {
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);
drawCirclesWithPattern(pattern);
});
};
@ -341,56 +337,256 @@ var getSelectedApp = () => {
});
};
var getStoredPatternViaApp = (storedPatterns) => {
E.showMessage("Loading patterns...");
log("getStoredPatternViaApp");
//////
// manage pattern related variables and functions
// - draws all saved patterns and their linked app names
// - uses the scroller to allow the user to browse through them
//////
var scrollerFont = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
var drawBackButton = (r) => {
g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1);
g.setFont(scrollerFont)
.setFontAlign(-1, 0)
.drawString("< Back", 64, r.y + 32);
};
var drawAppWithPattern = (i, r, storedPatterns) => {
log("draw app with pattern");
log({ i: i, r: r, storedPatterns: storedPatterns });
var storedPattern = storedPatterns[i];
var pattern = storedPattern.pattern;
var app = storedPattern.app;
g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1);
g.drawLine(r.x, r.y, 176, r.y);
drawCirclesWithPattern(pattern, {
enableCaching: true,
scale: 0.33,
offset: { x: 1, y: 3 + r.y },
});
g.setColor(0, 0, 0);
if (!storedPattern.wrappedAppName) {
storedPattern.wrappedAppName = g
.wrapString(app.name, g.getWidth() - 64)
.join("\n");
}
log(g.getWidth());
log(storedPattern.wrappedAppName);
g.setFont(scrollerFont)
.setFontAlign(-1, 0)
.drawString(storedPattern.wrappedAppName, 64, r.y + 32);
};
var showScrollerContainingAppsWithPatterns = () => {
var storedPatternsArray = getStoredPatternsArray();
log("drawing scroller for stored patterns");
log(storedPatternsArray);
log(storedPatternsArray.length);
g.clear();
var c = Math.max(storedPatternsArray.length + 1, 3);
return new Promise((resolve) => {
var selectPatternMenu = {
"": {
title: "Select App",
E.showScroller({
h: 64,
c: c,
draw: (i, r) => {
log("draw");
log({ i: i, r: r });
if (i <= 0) {
drawBackButton(r);
} else if (i <= storedPatternsArray.length) {
drawAppWithPattern(i - 1, r, storedPatternsArray);
}
},
"< Cancel": () => {
log("cancel");
showMainMenu();
select: (i) => {
log("selected: " + i);
var pattern = "back";
var appName = "";
if (i > 0) {
var storedPattern = storedPatternsArray[i - 1];
pattern = storedPattern.pattern.join("");
appName = storedPattern.app.name;
}
clearCircleDrawingCache();
resolve({ pattern: pattern, appName: appName });
},
});
});
};
log(storedPatterns);
var patterns = Object.keys(storedPatterns);
log(patterns);
//////
// storage related functions:
// - stored patterns
// - stored settings
//////
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++;
var getStoredPatternsMap = () => {
log("loading stored patterns map");
var storedPatternsMap = storage.readJSON("ptlaunch.patterns.json", 1) || {};
delete storedPatternsMap.settings;
log(storedPatternsMap);
return storedPatternsMap;
};
var getStoredPatternsArray = () => {
var storedPatternsMap = getStoredPatternsMap();
log("converting stored patterns map to array");
var patterns = Object.keys(storedPatternsMap);
var storedPatternsArray = [];
for (var i = 0; i < patterns.length; i++) {
var pattern = "" + patterns[i];
storedPatternsArray.push({
pattern: pattern
.split("")
.map((circleIndex) => parseInt(circleIndex, 10)),
app: storedPatternsMap[pattern].app,
});
}
selectPatternMenu[appName] = () => {
log("pattern via app selected");
log(storedPatternsArray);
return storedPatternsArray;
};
//////
// circle related variables and functions:
// - the circle array itself
// - the radius and the squared radius of the circles
// - circle draw function
//////
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 drawCircle = (circle, drawBuffer, scale) => {
if (!drawBuffer) {
drawBuffer = g;
}
if (!scale) {
scale = 1;
}
var x = circle.x * scale;
var y = circle.y * scale;
var r = CIRCLE_RADIUS * scale;
log("drawing circle");
log({ x: x, y: y, r: r });
drawBuffer.fillCircle(x, y, r);
};
var cachedCirclesDrawings = {};
var clearCircleDrawingCache = () => {
cachedCirclesDrawings = {};
};
var drawCirclesWithPattern = (pattern, options) => {
if (!pattern || pattern.length === 0) {
pattern = [];
}
if (!options) {
options = {};
}
var enableCaching = options.enableCaching;
var scale = options.scale;
var offset = options.offset;
if (!enableCaching) {
enableCaching = false;
}
if (!scale) {
scale = 1;
}
if (!offset) {
offset = { x: 0, y: 0 };
}
log("drawing circles with pattern, scale and offset");
log(pattern);
log(app);
resolve(pattern);
};
}
}
}
log(scale);
log(offset);
// cache drawn patterns. especially useful for the manage pattern menu
var image = cachedCirclesDrawings[pattern.join("")];
if (!image) {
log("circle image not cached");
var drawBuffer = Graphics.createArrayBuffer(
g.getWidth() * scale,
g.getHeight() * scale,
1,
{ msb: true }
);
drawBuffer.setColor(1);
CIRCLES.forEach((circle) => drawCircle(circle, drawBuffer, scale));
drawBuffer.setColor(0);
drawBuffer.setFontAlign(0, 0);
drawBuffer.setFont("6x8", 4 * scale);
pattern.forEach((circleIndex, patternIndex) => {
var circle = CIRCLES[circleIndex];
drawBuffer.drawString(
patternIndex + 1,
circle.x * scale,
circle.y * scale
);
});
E.showMenu(selectPatternMenu);
});
image = {
width: drawBuffer.getWidth(),
height: drawBuffer.getHeight(),
bpp: 1,
buffer: drawBuffer.buffer,
};
showMainMenu();
if (enableCaching) {
cachedCirclesDrawings[pattern.join("")] = image;
}
} else {
log("using cached circle image");
}
g.drawImage(image, offset.x, offset.y);
};
var cloneCirclesArray = () => {
var circlesClone = Array(CIRCLES.length);
for (var i = 0; i < CIRCLES.length; i++) {
circlesClone[i] = CIRCLES[i];
}
return circlesClone;
};
//////
// lib functions
// misc lib functions
//////
var log = (message) => {
if (DEBUG) {
console.log(JSON.stringify(message));
}
};
var debounceTimeoutId;
var debounce = (delay) => {
if (debounceTimeoutId) {
@ -405,12 +601,8 @@ var debounce = (delay) => {
});
};
var cloneCirclesArray = () => {
var circlesClone = Array(CIRCLES.length);
//////
// run main function
//////
for (var i = 0; i < CIRCLES.length; i++) {
circlesClone[i] = CIRCLES[i];
}
return circlesClone;
};
showMainMenu();

View File

@ -5,10 +5,18 @@ var log = (message) => {
}
};
var storedPatterns;
var positions = [];
var dragHandler = (position) => {
positions.push(position);
debounce().then(() => {
log(positions.length);
var CIRCLE_RADIUS = 25;
var CIRCLE_RADIUS_2 = CIRCLE_RADIUS * CIRCLE_RADIUS;
var CIRCLES = [
var circles = [
{ x: 25, y: 25, i: 0 },
{ x: 87, y: 25, i: 1 },
{ x: 150, y: 25, i: 2 },
@ -19,16 +27,6 @@ var CIRCLES = [
{ 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;
@ -38,92 +36,92 @@ var dragHandler = (position) => {
for (var i = 0; i < positions.length; i += step) {
p = positions[i];
circle = circlesClone[0];
circle = circles[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);
circles.splice(0, 1);
}
}
circle = circlesClone[1];
circle = circles[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);
circles.splice(1, 1);
}
}
circle = circlesClone[2];
circle = circles[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);
circles.splice(2, 1);
}
}
circle = circlesClone[3];
circle = circles[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);
circles.splice(3, 1);
}
}
circle = circlesClone[4];
circle = circles[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);
circles.splice(4, 1);
}
}
circle = circlesClone[5];
circle = circles[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);
circles.splice(5, 1);
}
}
circle = circlesClone[6];
circle = circles[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);
circles.splice(6, 1);
}
}
circle = circlesClone[7];
circle = circles[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);
circles.splice(7, 1);
}
}
circle = circlesClone[8];
circle = circles[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);
circles.splice(8, 1);
}
}
}
@ -163,16 +161,6 @@ var debounce = (delay) => {
});
};
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) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB