diff --git a/apps.json b/apps.json
index 560d6505b..1d7852250 100644
--- a/apps.json
+++ b/apps.json
@@ -3442,5 +3442,22 @@
"data": [
{"name":"app.json"}
]
+},
+{ "id": "shortcuts",
+ "name": "Shortcuts",
+ "shortName":"Shortcuts",
+ "icon": "app.png",
+ "version":"0.01",
+ "description": "Quickly load your favourite apps from (almost) any watch face.",
+ "tags": "tool",
+ "type": "bootloader",
+ "readme": "README.md",
+ "storage": [
+ {"name":"shortcuts.boot.js","url":"boot.js"},
+ {"name":"shortcuts.settings.js","url":"settings.js"}
+ ],
+ "data": [
+ {"name":"shortcuts.json"}
+ ]
}
]
diff --git a/apps/shortcuts/ChangeLog b/apps/shortcuts/ChangeLog
new file mode 100644
index 000000000..2286a7f70
--- /dev/null
+++ b/apps/shortcuts/ChangeLog
@@ -0,0 +1 @@
+0.01: New App!
\ No newline at end of file
diff --git a/apps/shortcuts/README.md b/apps/shortcuts/README.md
new file mode 100644
index 000000000..d8965cf15
--- /dev/null
+++ b/apps/shortcuts/README.md
@@ -0,0 +1,7 @@
+# Shortcuts
+
+Any installed app can be assigned to BTN1 and BTN3 and launched directly from compatible watch faces. This works with any watch face that uses `Bangle.setUI("clock")`.
+
+## Credits
+
+Shortcut icon by Icons8
\ No newline at end of file
diff --git a/apps/shortcuts/app.png b/apps/shortcuts/app.png
new file mode 100644
index 000000000..9bc8b9d3a
Binary files /dev/null and b/apps/shortcuts/app.png differ
diff --git a/apps/shortcuts/boot.js b/apps/shortcuts/boot.js
new file mode 100644
index 000000000..4e3e02da6
--- /dev/null
+++ b/apps/shortcuts/boot.js
@@ -0,0 +1,16 @@
+(function() {
+ var sui = Bangle.setUI;
+ Bangle.setUI = function(mode, cb) {
+ if (mode!="clock") return sui(mode,cb);
+ return sui("clockupdown", (dir) => {
+ let settings = require("Storage").readJSON("shortcuts.json", 1)||{};
+ console.log(settings);
+ if (dir == -1) {
+ if (settings.BTN1) load(settings.BTN1);
+ } else if (dir == 1) {
+ if (settings.BTN3) load(settings.BTN3);
+ }
+ });
+ };
+})();
+
\ No newline at end of file
diff --git a/apps/shortcuts/settings.js b/apps/shortcuts/settings.js
new file mode 100644
index 000000000..419b186d6
--- /dev/null
+++ b/apps/shortcuts/settings.js
@@ -0,0 +1,66 @@
+(function(back) {
+ const s = require("Storage");
+ const apps = s
+ .list(/\.info$/)
+ .map(app => {
+ var a = s.readJSON(app, 1);
+ return a && (a.type=="app" || a.type=="clock" || !a.type) && {n: a.name, src: a.src};
+ })
+ .filter(Boolean);
+ apps.sort((a, b) => {
+ if (a.n < b.n) return -1;
+ if (a.n > b.n) return 1;
+ return 0;
+ });
+ apps.push({n: "NONE", src: null});
+
+ const settings = s.readJSON("shortcuts.json", 1) || {
+ BTN1: null,
+ BTN3: null
+ };
+
+ function showApps(btn) {
+ function format(v) {
+ return v === settings[btn] ? "*" : "";
+ }
+
+ function onchange(v) {
+ settings[btn] = v;
+ s.writeJSON("shortcuts.json", settings);
+ }
+
+ const btnMenu = {
+ "": {
+ title: `Apps for ${btn}`
+ },
+ "< Back": () => E.showMenu(mainMenu)
+ };
+
+ if (apps.length > 0) {
+ for (let a of apps) {
+ btnMenu[a.n] = {
+ value: a.src,
+ format: format,
+ onchange: onchange
+ };
+ }
+ } else {
+ btnMenu["...No Apps..."] = {
+ value: undefined,
+ format: () => "",
+ onchange: () => {}
+ };
+ }
+
+ E.showMenu(btnMenu);
+ }
+
+ const mainMenu = {
+ "": { title: "Shortcuts Settings" },
+ "< Back": back,
+ "BTN1 app": () => showApps("BTN1"),
+ "BTN3 app": () => showApps("BTN3")
+ };
+ E.showMenu(mainMenu);
+});
+
\ No newline at end of file