diff --git a/apps/aptsciclk/ChangeLog b/apps/aptsciclk/ChangeLog
new file mode 100644
index 000000000..ed32a45a2
--- /dev/null
+++ b/apps/aptsciclk/ChangeLog
@@ -0,0 +1,8 @@
+0.01: New App!
+0.02: Icons, loading screen
+0.03: Random icon, Shorter "loading" screen
+0.04: Support for light and dark Themes
+0.05: Small bugfix
+0.06: Formatting
+0.07: Added potato GLaDOS and quote functionality when you tap her
+0.08: Fixed drawing issues with the quotes and added more
diff --git a/apps/aptsciclk/README.md b/apps/aptsciclk/README.md
new file mode 100644
index 000000000..718e3d408
--- /dev/null
+++ b/apps/aptsciclk/README.md
@@ -0,0 +1,11 @@
+# Description
+
+This is a simple clock based on the Portal Series.
+
+# Features
+
+The button in the center of the screen is interactable and the warning image will change when it is pressed.
+
+Potato GLaDOS in the bottom left corner is interactable and will display a quote when tapped. (You can add more quotes by editing the `aptsciclkquotes.txt` file seperating each quote with a `^`)
+
+When the app loads the Apeture Science Logo is displayed.
diff --git a/apps/aptsciclk/app-icon.js b/apps/aptsciclk/app-icon.js
new file mode 100644
index 000000000..d2a2dbbd6
--- /dev/null
+++ b/apps/aptsciclk/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwgNKxAACEaIVDDKWAhAXGwAtODA4HBLR4YFD4QWICIhABGAoMBJRBZHC4wwHOQ4IFAgQwGUQ4YBAg4uMJIwDDGAjRLIgYLHc5gXJIwbKLC4hICb4gZKfAhgETJKHJLwwXRUooWKCImAJogXRMopGMNwkIC4oWLYYqtHC5rFJC5h0GIxwsGFyD8CC4wwOIxBIQFwoeBCxrwEFwYXTFgTXReI4uQC4apPC4xNERqBlGFx4XCeJ4nHD4kIIxY3KPoIxNBwYXEJRInEP44iGOgwXFBYYcDChCHHC4wMBC5BnJEoouMGAYXEJJCCJC4pOEcpYKBFIpJFZRQXGD4gWKXBUICxjdFIhwyJOJMAA="))
diff --git a/apps/aptsciclk/app.js b/apps/aptsciclk/app.js
new file mode 100644
index 000000000..c2903cf37
--- /dev/null
+++ b/apps/aptsciclk/app.js
@@ -0,0 +1,368 @@
+const big = g.getWidth()>200;
+const timeFontSize = big?5:4;
+const dateFontSize = big?3:2;
+const gmtFontSize = 2;
+const font = "6x8";
+
+const xyCenter = g.getWidth() / 2;
+const yposTime = xyCenter*0.73;
+const yposDate = xyCenter*0.48;
+const yposYear = xyCenter*1.8;
+
+const buttonTolerance = 20;
+const buttonX = 88;
+const buttonY = 104;
+
+var pause = false; //set to true to pause any sort of drawing (except for quotes)
+
+function getImg(img){
+ if (img == "w0"){//drink
+ return {
+ width : 60, height : 60, bpp : 1,
+ buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOZFIQOD4EABwnwgEDBwf8g/4h4ODwYQBv4OC+AbDAIP+j/HAQIOC4Hwj4RBBwP8o8B/+PBwWOkEP/l/BwP4+JCB44OCj+Ih/+n4OB+PEoP38YOB/0YkUXGgIOB8cBi9f+IOCkEI+XvBwXigFG64OEg0/t4OEuP7BwkHx/PBwWigF8voOC+Uwg/ig4OCkMgv8QsIOB+cfSoOGLIUR/E/4ljBwPxx/B/0kO4UI/0P+J3C/HHVQOISoWEn+D/iPBBwIwC8IOCwcP84IBBwU4TAMHBwfAv+AcARBBgD3CBwX8gDnBBwfwewIODAgIABBwYHDB3oAEBwIHFByyDBABg"))
+}
+ }
+ else if (img == "w1"){//cube dispenser
+ return {
+ width : 60, height : 60, bpp : 1,
+ buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOI/3/+fvBwYEBnwO/By3APgN/O6IeBh4OF8AOcwADCBwX8g4dM/8fBwt774OE+/9Bwt/BxodH3oOcFgyVG8BhCBwX8hRwCBwXA0C6BBwc/w4OE41MBwtEo6VF84sE/1/54OLDo4sHHYxKHLIxoGO44AD/kAABo"))
+ }
+ }
+ else if (img == "w2"){//acid
+ return {
+ width : 60, height : 60, bpp : 1,
+ buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOF+IOGngOF8/D8YGD/wdBB4nv4fzAwf4BwOfGQd/4f7/+//+f74OB4PwHIJKDx8P/4BBBwP8BwIBBBwXvh+Hw5ZD+Pwnl/NAcegJOBBwfgj0fBwvhBxcPgYEBBwXw/F+FghIB84OC/BfBOYQOBk/w/0f4f4nkGgFgh0hwED4H4jOBuF8hk/v/Hzlnx/zFgQZBGYLCD4EHaIn8gAOF8EDBwn+dgQOK/8AN4IOD+EABww0BBwqGEBwIWBBwk8CwIODg/gv4OEv4OD+4OBBAIOBRYIFBh+PcAQdC+gOCDoN+h/vBwPP/wOB/wOBwJCBBwP2oa3BLALgBiA7BOwIvB/+DQoV/d4hPBBwQsB/wJB8ZoEAAZoDAAQOPRQIAM"))
+ }
+ }
+ else if (img == "w3"){//turret
+ return {
+ width : 60, height : 60, bpp : 1,
+ buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOi+IOGh4OF8AOF/UNBwthx4OE+0YBwtBh4OE6mQBwn7rEfBwl22IOE99gBwn99UzBwUc/+90YsC8HH+++n98n/+g0++2Z+4OB4Fz73T74OCg877d8/YdC+d7u/v3gsBjEvt/+O4X+gvtIgI7CwG934OD8E326kD/0A+yzEwEO74OD/EArYOEgEDv4OD+PAl4OEnkBaInz0EPBwk3iAdE+XwSIYDBj2Oj4OD/fYvIOEvdHz4OD99unIOD/vt44OE3u4Dou3h4OE+3x/IOE70/Bwn78/9Bwl4LAQ7Dx75DBwP4Awb+EBwgAEBz0AABo="))
+ }
+ }
+ else if (img == "w4"){//falling cube
+ return {
+ width : 60, height : 60, bpp : 1,
+ buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOC+YOF/0PBwvgv4OE/kFBwvAyIdFnYeBBwYeDDofng4OE8vYDonx7uPBwkf/+/Bwfh+czBwf+g/5z4OD+FevIdEhMDDon/0E3BwgeBJQgeB+5ZFvAEBBwfzgYOEw/XLInwn3BBwf8gH4LYIOCwUHDonwmE4HYkHwKkE8P4XYQOCv7dCYQkBWYsAWYvAiAsE/EDJQn/wF+CwJZDg/gBwgrBXYIOC8D+FNAL+F4eDBwn4nh2BBweHFYJ3EFYQOC/0P/AOECgIOE/E/BwsHBwvACAIODWAQOEJAIOFAgIOEQ4QsEAAOfBwoACBwgACBw8AABo"))
+}
+ }
+ else if (img == "w5"){//ball
+ return {
+ width : 60, height : 60, bpp : 1,
+ buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOiv4OF8YOFAgQOEyYdGBw3zBw0BBwv4j4OB+EAgOD84OE+/ev4dD/3+BwvcugsE/u7t0f4aRC7e2sF8Bwlxg4dEu8YBwYsB/HDHYsMngOB8EDweHDon//PADoYABz0PBwfwnJKE/0OjZZC/kB4Hxz4OCwEYh+wBwXwgeA/+HBwUP8EP/0/BwPj/0DCQIOB/l/4DQBw4OBDIMPUoJKB+H/wY+B44OBj/4CoJKC+P/g7+FBAL+Fj4OFbwIOEI4IOF8YO6JQwAEaIgORgAANA"))
+ }
+ }
+ else if (img == "w6"){//ball recviver
+ return {
+ width : 60, height : 60, bpp : 1,
+ buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOR/YOG34Ob/e7Bwu7CwQOhGgQOD34OF/0LBwvfv4dMuPfBwn29oOFtwONDowsHHY3+h7CNj4OF+IOc4A7NDo7gGJQ4ACBwX+//vBwnvBAIOK8EH/kBBwd+v/PSwIOB/fnjiWBBwXesHPLQIOB/2AgEvBwfgh0AFgf8gAuBLKQObgAANA=="))
+ }
+ }
+ else if (img == "w7"){//falling portals
+ return {
+ width : 60, height : 60, bpp : 1,
+ buffer : require("heatshrink").decompress(atob("AB0//4AE4YFE/H8BwtvBwvvvgOE/33Bwvf3gOE/v7Bxn5Bw2fHYv7/oOF3/cB118JQQOC4ODJQn8jEfLInBjBoE/0jO4pjD953CwCVF/EH5//+ykCwA8Cp4OB/MDz4DBEQUYjPzaIfn5k/74xC/l44f+BwePz1595ADDYPvv7vDMAN3Bwf4CAIOE4//BYIOB/0On47E8AFCBwcPTwYOCAgPAgE8Bwf8gEDBwOAGIJZDBwX9DofhUYRKDKIIOEAAQOD8EABwgcB+IODnoKB84OD37tCBwUzZ4QODZ4QdDnIFB/YODZwP+v47DJIIBBJQcAAwZyBABoA=="))
+ }
+ }
+ else if (img == "w8"){//flying portals
+ return {
+ width : 60, height : 60, bpp : 1,
+ buffer : require("heatshrink").decompress(atob("AB0//4AE4YFE/H8BwtvBwvvvgOE/33Bwvf3gdF/YOF/4OF/IOGgA7F8ENBwn8gHcBw/5AoOAg4OCh4sD/vD+AFB45KBBwfwv//BwMJgEIFAXcnvggF4kEBBwPMSIIYBz/8nAEBw5ZD4IhBO48AhpoG953FSo/2Ugv/p4OF/LCGaIyIBB34OH4EAngODbAMDBwnfDoqeCBy7RBBwnh//xBwc9BQPnBwe/AYO/BwUzFYQODGgYOCnIFB/YOD57WBv47Dj//AIJKDgAGDOQIANA"))
+ }
+ }
+ else if (img == "w9"){//cake
+ return {
+ width : 60, height : 60, bpp : 1,
+ buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOY/oOG94OF/1/Bwv3FgwKCBwfnFhn8HY0LAQPwvgOB8EP/5uBBwP2gF4j+PBwP+sEEj/x44OB90Ao/8Dodwg8/nkH4ZXBgHnx8ABwPv/k98+ABwZEB+EAJQPj/3+nkAv4OB5+fz0Aj4OB98Ag+Ah/nBwJXB4EDHYSTB/EA/wsCSoJfBwAODNIPgBwgcBHYQOCC4QODn8Ah4ODGgMH+47D8EB/A7KTYMf4A7Eg/wHYgcBHZx3DcAPggbRBFgQcBcAQOB/iUBBwgcBBwgcCd4V/HYL+D/YOBDgIOC8/+DgIOC/+HfwIOD/4cCBwYAEBwQADBz0AABoA="))
+ }
+ }
+ else if (img == "butPress"){
+ return {
+ width : 176, height : 176, bpp : 4,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("iIA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AFEc5kM5hD/ACXMAAJXB5nBI35WSK4ZY/AB8cK4/MJP5WRK4pY/ABhPD5e7he7A4fBJn5XNKwJXCLAZX/ABUcKwhXDLAZN/VxhSCK4m8WH5XNVwZXEWARX/ABEcK4sAgBYDXYRP/K5RQC2ACE3e8K/5XPVgYDCK/4AKJIPLVYoEEBoPBIWPd6ICPK46uDAohXzjvd7oCMCAJX/K7cAAAZXFBQkBK/6v/ABPd6ICPK/4AaK4mwKwYEDV+4ARjhKBVQoDD3gMBK2MdAIRXXVYSuDK/5XN5ZRCgEAWQYLBK/4AJK4u7KwZXC4JXxiPd6JXV5hXH3hX1ACscWApXDMQRN/WBpYCK4QICV35XOLARXBA4ZX/ABccKAfMhgFEJf5YRK4hJ/ABxXH4JI/LCRXCK34ASjhXCIf4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/ACIA=="))
+ }
+ }
+ else if (img == "butUnpress"){
+ return {
+ width : 176, height : 176, bpp : 4,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("iIA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AFEc5kM5hD/ACXMAAJXB5nBI35WSK4ZY/AB8cK4/MJP5WRK4pY/ABhQEK43BJn5X/AEMcK5fMJv6uPK46w/K/4AgjhPFgEALAxP/K5vAAQhX/K6KsDWApP/AA6uHWA/BIWOIwICPK46qFAohXxjGIxACMCAJX/K7cAAAZXFBQkBK/6v/ABOIwICPK/4AaKInAAhCv2ACMcVRC0FK2MYAIRXXVYSuFK/5XO5kAgCuFK/4AJJwvMKw3BK+MRxGBK/4ArjhXMJv6wQK4qu/K/4AjjhXKJf5YRK4hJ/ABxXH4JI/LCRXCK34ASjhXCIf4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/ACI="))
+ }
+ }
+
+ else if (img == "apetureLaboratories"){
+ return {
+ width : 173, height : 43, bpp : 4,
+ transparent : 0,
+ buffer : require("heatshrink").decompress(atob("AA+IAAeAIv5UTK34APhABBKwpeBJX5VLJgJWGAwKv/ABL8EKomABIYA/KpJWHAoRW/KpZQCfwJSCAgRW/KphWFBgZW/KphNCAgSxEKP4ADJAatFKwWAL4oA/KpALGXQhS/Ko4MIwAOMAHREOKv5ZVVgcAh///4LDAwQAB+AIHCQYHMDQQYBDwQEGDIoaGTx4MCwAiCJgYhFBIYIHLw4HFBARjGESJUDehZVFVhJNLRI5VGDIIdEAgwiL+DzDJAJVJB4IMBCoKsHAYpFCDgr2IApYEIBpJFCKpqsCHgQbFIhBVHBhJoGTwyBRKp5mBDoY6GKoYqEXYg7JApKeHQJysEKpRmBDoasHQBAACM4qMGEAr0JAgZjGFYhVPgGAEIpVEAAaAEA4oyEW4qyKFZBoFKoisDJIJWLfIp3IQBJeJfgysMERpVCwEIVpijFBA6ZJdA4JHJYgEKQIx1EVgRVBVhj5IEIyZHHYoUGNw4MCQIwIKAARVDxBVRQo6ZJHZZoGU4yBGBAiBGVgRZBVhYlIfJBoFHZZoHfJRwGBAQrEVISvCKpwrEUZAqFVhzYKB4yKGQIpVDAYJVKEoYFDBIpVIb4ysMIgoPHOgoRFhGAVwKsLAFzQHACBVCVhQA/KpawBCJa76IhRWDVxIODAwTaFAocP///dhALGBQYJBCIYQBDYgKEBBAEDVgeIAgKgFBYZVEEYY+CH4YDBBgYFBBYoFEOYhmEDYgFFLIwEDKw2AKwhTFBoSsHIgglFAQo/HKIoDECBBZGc46eEAYa2EKox2Ga5LjLDoq8FKAgVDBAv/EghWGVgRWJKoaOFD4IgCViAWFVjKTGJIZOFKpKOHAQj3JAogCFPApcGKQziGLopKCU4hWFKojsEHYrXFCAJFId45CEDYh4EB4Y1DCYSzFJQT+FgAGCKooA/AAZMBfopWDKv5WLVgxT/AB+AKopW/ACBU/ABwA=="))
+ }
+ }
+
+else if (img == "apetureLaboratoriesLight"){
+ return {
+ width : 173, height : 43, bpp : 4,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("iIAGxAADwINHAH5ULK34APjABBKwpeBJX5VLJgJWGAwKv/ABL8EKomBBIYA/KpJWHAoRW/KpZQCfwJSCAgRW/KphWFBgZW/KphNCAgSxEKP4ADJAatFKwWBL4oA/KpALGXQhS/Ko4MIwIOMAHREOKv5ZVVgcRiEAgALDAwQABgIIHCQYHMDQQYBDwQEGDIoaGTx4MCwIiCJgYhFBIYIHLw4HFBARjGESJUDehZVFVhJNLRI5VGDIIdEAgwiLgLzDJAJVJB4IMBCoKsHAYpFCDgr2IApYEIBpJFCKpqsCHgQbFIhBVHBhJoGTwyBRKp5mBDoY6GKoYqEXYg7JApKeHQJysEKpRmBDoasHQBAACM4qMGEAr0JAgZjGFYhVPiOBEIpVEAAaAEA4oyEW4qyKFZBoFKoisDJIJWLfIp3IQBJeJfgysMERpVCwMYVpijFBA6ZJdA4JHJYgEKQIx1EVgRVBVhj5IEIyZHHYoUGNw4MCQIwIKAARVDxBVRQo6ZJHZZoGU4yBGBAiBGVgRZBVhYlIfJBoFHZZoHfJRwGBAQrEVISvCKpwrEUZAqFVhzYKB4yKGQIpVDAYJVKEoYFDBIpVIb4ysMIgoPHOgoRFjGBVwKsLAFzQHACBVCVhQA/KpawBCJa76IhRWDVxIODAwTaFAocQgEAdhALGBQYJBCIYQBDYgKEBBAEDVgeIAgKgFBYZVEEYY+CH4YDBBgYFBBYoFEOYhmEDYgFFLIwEDKw2BKwhTFBoSsHIgglFAQo/HKIoDECBBZGc46eEAYa2EKox2Ga5LjLDoq8FKAgVDBAsAEghWGVgRWJKoaOFD4IgCViAWFVjKTGJIZOFKpKOHAQj3JAogCFPApcGKQziGLopKCU4hWFKojsEHYrXFCAJFId45CEDYh4EB4Y1DCYSzFJQT+FiIGCKooA/AAZMBfopWDKv5WLVgxT/AB+BKopW/ACBU/ABsQA="))
+ }
+ }
+
+ else if (img == "potato"){
+ return {
+ width : 54, height : 55, bpp : 4,
+ transparent : 6,
+ buffer : require("heatshrink").decompress(atob("swAEsEGA4oASEQ4ARGgNgDa8QsA3BKStowxTDDalikxTCRKsSNwY2BDSYvDGoI2TsoTDgwEBGx5IBs1VHIgaBGx4qCDQZOBUiUGstQDQ4FBKYwHGDQNWDRA3BCYsABotgJ4dkogABowcGAAUGOwogDDAVCAYScKOooFBooWCAAjqNAoQYHKYQ8ELQZzFsAMCiIeJAAdFMwiCEoIaNoICBoAaMiMUDA0ikMiigaIAAgaGDIIaBkcyoCHEMxqIBmdEkMzmcxDSVBkYXBGoMzmUQDSMSDIURiIbBkDBCDRtBiYwBDIMREAMiDR6qBiI0BkQEBKQKkBUJQAEoCfBC4MVgMSDYMkGwQaBeBMUiC3BGYNN8kSHgM0DQrsGAAMQQAMyCwIaBgK/BmIaKM4IGCiMTDQXd9waCmlENZIaEgESKAMhpxQEDQSiEoJTGiEimaGCqIaBmKABUQwyBUIzRBkIXBDoI8BkAaDGwgaFEIMCeQUSYAKNBmLYDGwJ/DDYlFqAaBGwILBGgLyBUIRSFQghrCgEjDYMiiTdCggaFDYgaFKIKIBmcTkciiA1GNwpsFgI4BmZsBkVEDRAbJiBTCNQMABIIZHfBCPBDQKSCoFEGhA2IKIMQgJ1CoCFHGxVBeASQDgAZJDQ8RQIMyDQkGDZQ1GDILtBAwNGsAaLs1hokECYNCaQMxDIVmDRoOBDQifCBggaNKgMRqUhiovFGxoMEsCADAQQaMsUWJBi+LsMRqtWDSwUBqvFqpVFQ54UCstVqEGsDbBQx4lFgAABotRAgQABT54ADqtRM5jjQAAQ"))
+ }
+ }
+
+ else if (img == "apetureWatch"){
+ return {
+ width : 176, height : 176, bpp : 4,
+ transparent : 2,
+ buffer : require("heatshrink").decompress(atob("kQA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A+/4A/AH4A/AH4A/AD0RgAASgMQCqgrqiJX/K/5X/K/5XR7vQK/8IxBX/K/5X/K/5X/K/5X/K/5X/K/5X/K/5Xah////wK/5XTKwIABK4YAG//x7vRBhAA2jGIAYMfK4fxCRAOCK/5XFKwYABBgTBEh4LC7vQbabxLFYoVPFZMIxADBK4sAK4wLDK/5XSBYhX/K6MPBIQDBK/5XD+BXMBAQQCK/5XDKAJXKVwRWBAoJX/K4j3CUohXDL4YACK/5XF+CuEK4yuCK/5XHWAZOCK4cPKwhX/K45MFK4YAGK/5XGLApX/K6X/K5sPK/5XIWAZXJ/5X/K6sPK/5XPAoauDK/5XJ/5XDj5eEVwRX/K5y2FVwRX/K4/wK44GDVwRX/K50fWAi9DK/5XGUYRQCiKwCAwKuDK/5XGJgZXGMQJWDK/5XGAoKkCK4arEK/5XIVQRQDK/5XQAwZLC+BXBAwYACLwJX/K4auCWApXHCAJX/K5JYFAoi/Ch5X/K4ZHCAAZXEAoZnCK/5XLVQRXFBgZX/K4igCLAnxFYRdBBohX/K5cAiIrJK/5XEfIhX/K/5Xr+BX/K/5Xu/5X/K/5WKFhRXWl5XB+BX/K6ywFK9XxK4SMFK7JWCK58PK7sP/5XDGoxXr/5Xc//4xBXEHIKyPK54fFK5CPBK7cPxAyBK4oHBK7wKFK5AQBK7UP/GPD4JWCiMfK4IJBK7jOGFgYwEK4XRBg4AQ/CtFAAZXCBQ4AQjBXCCRxXcUoJLDjnMhnMBgTqCK7RzJYYxXC6DbTAAf4UYInB5gABK4PM4KBDdYwrQhBXBBQ5XIAAJXYh6GDKwRXDLASwCK7BxIK8auDjhXH5iwPK5gKIK8iuBKwhXFLAJX/AA0PxCuBKAhXG4KwCK/6uEx/xK5qwNK/KuBjhXL5kRWAJXrbapXEJ4pXH4JXXABRXhh+I+JXPiP//5X/K4WP+McJ4oLBLAxX/K5vAAQhX/ABBDBiJXFVgawFiMf//wK/4gBAAKuHWA/BCYQhIK8uIwACOK5CqFAohXxhGIxACMCAJX/K7YaEK4pKFK/6v/ABOIwACOK/4AMFZRXI4AEIV+wrO///iMcVRC0FiMf//wQaZXZhABCFZ0P//xK4qrCVwpXB/+PbahX15gLBVwpX/K5ERJwvMKw3BiP4K98AxGAFaH//5XPj/4+BXvFaRXCjhXMiMfxBXhGoIAcK4nxWAxXF4MR/GPK4Q4e+UiADcvK4UPWARXMj/4NwayKACUPK8KwDjhXKcQOIKYZX/eIkRLAhXEiKuBx5XEY4QAYDgJXiIAXxiJXH4KuC/4VDK/6wGLAZXCKwKuGK/6wIiMcK4QFBVw5X/WA2IwJSCAAYJBVwpX/WA2IJwJVDj///GPVwpX/LA34K4PxVoYHBKwxX/AAynCK4ZWC+BX/K5ixB/6vEVo5X/IxAABK4asHK/5XLgJXCBxRX/K/5XgLAQNLK/5X/K6r7CACxX/K/5XVfJcBiINLK/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K5o6bK/YaZK/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K6o6bK/cAABUBiINLK/5X/K/5X/K/5X/K/AMLACBX/K7DMaAAcRADA4eA=="))
+ }
+ }
+
+ else if (img == "apetureWatchLight"){
+ return {
+ width : 176, height : 176, bpp : 4,
+ transparent : 2,
+ buffer : require("heatshrink").decompress(atob("kQA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A+gAA/AH4A/AH4A/AD0R/4AS+MfCqgrqiJX/K/5X/K/5XR7vfK//4xBX/K/5X/K/5X/K/5X/K/5X/K/5X/K/5Xa+EAgEPK/5XTKwIABK4YAGgEB7vRBhAA2jGIAYMQK4cBCRAOCK/5XFKwYABBgTBE+ALC7vfbabxLFYoVPFZP4xADBK4v/K4wLDK/5XSBYhX/K6PwBIQDBK/5XDh5XMBAQQCK/5XDKAJXKVwRWBAoJX/K4j3CUohXDL4YACK/5XFh6uEK4yuCK/5XHWAZOCK4fwKwhX/K45MFK4YAGK/5XGLApX/K6UAK5vwK/5XIWAZXJgBX/K6vwK/5XPAoauDK/5XJgBXDiBeEVwRX/K5y2FVwRX/K48PK44GDVwRX/K50QWAi9DK/5XGUYRQCiKwCAwKuDK/5XGJgZXGMQJWDK/5XGAoKkCK4arEK/5XIVQRQDK/5XQAwZLCh5XBAwYACLwJX/K4auCWApXHCAJX/K5JYFAoi/C+BX/K4ZHCAAZXEAoZnCK/5XLVQRXFBgZX/K4igCLAkBFYRdBBohX/K5f/iIrJK/5XEfIhX/K/5Xrh5X/K/5XugBX/K/5WKFhRXWkBXBh5XvgJXCGgpXcWApXoF4KvD+COGK65WCK5/wK7gtCK4YmCLB5XfgBXbFgIzBK4mILCBXPDwpXIcIJXa+BPBKAJXFLARXdBQpXICAJXah/4x4qDAAMfK4IJBWBpXODgwsDAAcQK4XRBg4APgAwBVogADK4XwgInWjBXCCRxXbiD9BKwcc5kM5gNCRgXwK7JyJYYxXC77bTIwf4UYInB5gABK4PM4MRDQXwDpYrKawMABQ5XIAAJXYh6uDKwRXDLAQRDK64YIK8SuEjhXH5iwPK5gKIK8UP/APBKwhXFLAKwNK/ItBiJQEK43BDgRX/AAXw/GP+JXNGQXwK/5XDEgMcK5fMiIdBK9YAKK5cPK4RPFK4/BDoUPFagAJK8WI+JXPGYRX/IIWP+McJ4sAgBYGK/5XN4ACEK/4AH+AjCK4qsDWAsRDwIWCK/ogBAAKuHWA/BCYQhIK8uIx4COK5CqFAohXx/GIxACMCAJX/K7cAAAZXFBQkBK/6v/ABOIx4COK/4AMFZRXI4AEIV+wrN+AjCjiqIWgpUCCwRXr/ABCFZ0PBoJXFVYSuFK4P/x4VBK/5XI5kAgCuFK/5XIiJOF5hWG4MR/BXv/+Ix5XREgJXOj58BK94rR+AkCjhXMiMfxAUCK70AADpXE+KwGK4vBiP4x5XhgUiADcgEQTyCK5sf/ATDK/5DD+McK5UR/+IK/5XEeYcRLAhXEiKuBx4SDK/6wFiJXH4KuOK/SwELAZXCKwKuOK/ywBiMcK4QFBVwZX/K43/gACBxGBKQQADBIKuBh5X/K43/JAOIJwJVDIYP4x4NCK/5XHfAP4K4PxVoYHBBgRX/K5H/gCnCK4ZWDVxpX9LARABV4ZWQK/xYBgBXD+EAKx5X/AAMBK4RVQK/5ADK4RBSK/5YDIKZX/K/5XNfYQA1K/5X2eJkReKfxj4VTK/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5XvgAAYh5X8DTJX/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5XVgAAYK/orL+MRIKZX/K/5X/K/5X/K/5X/K/5XmgAAdiIA3"))
+ }
+ }
+}
+
+
+function drawStart(){
+ g.clear();
+ g.reset();
+ if (g.theme.dark){apSciLab = getImg("apetureLaboratories");}
+ else {apSciLab = getImg("apetureLaboratoriesLight");}
+ g.drawImage(apSciLab, xyCenter-apSciLab.width/2, xyCenter-apSciLab.height/2);
+}
+
+// Check settings for what type our clock should be
+var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
+
+// timeout used to update every minute
+var drawTimeout;
+
+//warnings
+var maxWarning = 9;
+var curWarning = Math.floor(Math.random() * (maxWarning+1));
+
+function unPause(delay, quote){
+ if (pause){
+ setTimeout(function() {
+ if (quote == undefined || quoteNum == quote){
+ pause = false;
+ draw();
+ }
+ }, delay);
+ }
+}
+
+var quoteNum;
+
+function quote(fontsize, width, height, specificQuote){
+ pause = true;
+ var finalString = "";
+ var quotesFile;
+ var finalFontSize;
+ quotesFile = require("Storage").read("aptsciclkquotes.txt", 0, 0); //opens the quotes file
+ //console.log(quotesFile);
+ var quotes = quotesFile.split("^");
+ var numQuotes = quotes.length;//number of quotes
+ var curQuote;
+
+ if (specificQuote == undefined){
+ quoteNum = Math.round(Math.random()*numQuotes)-1;
+ curQuote = quotes[quoteNum]; //quote to be displayed
+ }
+ else{
+ quoteNum = specificQuote;
+ curQuote = quotes[quoteNum];
+ }
+
+ unPause(10000, quoteNum);
+
+ var curWords = curQuote.split(" "); //individual words
+ //console.log(numQuotes);
+
+ var maxChar = width/6/fontsize;
+ var maxLines = height/10/fontsize;
+ var curLines = 0;
+ var curLength = 0;
+
+
+ for (var i = 0; i < curWords.length; i++){
+ //console.log(curLength+curWords[i].length);
+ if (curLength + curWords[i].length <= maxChar){
+ finalString += " "+curWords[i];
+ curLength += curWords[i].length+1;
+ //console.log("next");
+ }
+ else{
+ //console.log("break");
+ curLines++;
+ if (curLines > maxLines){
+ curLength = 0;
+ finalString = "";
+ i = -1;
+ if (fontsize > 1){fontsize--;}
+ maxChar = width/6/fontsize;
+ maxLines = height/10/fontsize;
+ console.log(maxLines);
+ console.log(maxChar);
+
+ }
+ else{
+ curLength = 0;
+ finalString += "\n";
+ i--;
+ }
+ }
+ finalFontSize = fontsize;
+ }
+
+
+ //drawing actual stuff
+ g.setColor(g.getBgColor());
+ g.fillRect(10, 10+28, g.getWidth()-10,g.getWidth()-10);
+ g.reset();
+ g.setFont(font, finalFontSize);
+ g.setFontAlign(0, 0);
+ g.drawString(finalString, xyCenter, xyCenter+14);
+ //quote length*pixels per character = pixel width
+ //height ~120 width ~160
+}
+
+function buttonPressed(){
+ if (curWarning < maxWarning) curWarning += 1;
+ else curWarning = 0;
+ g.reset();
+ buttonImg = getImg("butPress");
+ g.drawImage(buttonImg, 0, 0);
+
+ warningImg = getImg("w"+String(curWarning));
+ g.drawImage(warningImg, 1, g.getWidth()-61);
+
+ setTimeout(buttonUnpressed, 500);
+}
+function buttonUnpressed(){
+ if (!pause){
+ buttonImg = getImg("butUnpress");
+ g.drawImage(buttonImg, 0, 0);
+ }
+ else{
+ setTimeout(buttonUnpressed, 500);
+ }
+}
+
+// schedule a draw for the next minute
+function queueDraw() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ }, 60000 - (Date.now() % 60000));
+}
+
+
+function draw() {
+ if (pause){}
+ else{
+ // get date
+ var d = new Date();
+ var da = d.toString().split(" ");
+
+ g.reset(); // default draw styles
+ //draw watchface
+ if (g.theme.dark){apSciWatch = getImg("apetureWatch");}
+ else {apSciWatch = getImg("apetureWatchLight");}
+ g.drawImage(apSciWatch, xyCenter-apSciWatch.width/2, xyCenter-apSciWatch.height/2);
+
+ potato = getImg("potato");
+ g.drawImage(potato, 118, 118);
+
+ g.drawImage(warningImg, 1, g.getWidth()-61);//update warning
+
+ // drawString centered
+ g.setFontAlign(0, 0);
+
+ // draw time
+ var time = da[4].substr(0, 5).split(":");
+ var hours = time[0],
+ minutes = time[1];
+ var meridian = "";
+ if (is12Hour) {
+ hours = parseInt(hours,10);
+ meridian = "AM";
+ if (hours == 0) {
+ hours = 12;
+ meridian = "AM";
+ } else if (hours >= 12) {
+ meridian = "PM";
+ if (hours>12) hours -= 12;
+ }
+ hours = (" "+hours).substr(-2);
+ }
+
+ g.setFont(font, timeFontSize);
+ g.drawString(`${hours}:${minutes}`, xyCenter+2, yposTime, false);
+ g.setFont(font, gmtFontSize);
+ g.drawString(meridian, xyCenter + 102, yposTime + 10, true);
+
+ // draw Day, name of month, Date
+ var date = [da[0], da[1], da[2]].join(" ");
+ g.setFont(font, dateFontSize);
+ g.drawString(String(date), xyCenter, yposDate, false);
+
+
+ // draw year
+ g.setFont(font, dateFontSize);
+ g.drawString(d.getFullYear(), xyCenter+1, yposYear, true);
+ }
+ queueDraw();
+}
+
+
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower',on=>{
+ if (on) {
+ draw(); // draw immediately, queue redraw
+ } else { // stop draw timer
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ }
+});
+
+Bangle.on('touch',(n,e)=>{
+ //button is 88 104
+ if (!pause && buttonX-buttonTolerance < e.x && e.x < buttonX+buttonTolerance && buttonY-buttonTolerance < e.y && e.y < buttonY+buttonTolerance){
+ buttonPressed();
+ }
+ //Potato GLaDOS
+ else if (!pause && 117 < e.x && e.x < 172 && 117 < e.y && e.y < 172){
+ quote(2, 150, 140);
+ }
+ else{
+ unPause(0);
+ }
+});
+
+//show Apeture laboritories
+drawStart();
+
+setTimeout(function() {
+ // clean app screen
+ g.clear();
+ // Show launcher when button pressed
+ Bangle.setUI("clock");
+ Bangle.loadWidgets();
+ Bangle.drawWidgets();
+ //update warning image
+ buttonPressed();
+ // draw now
+ draw();
+ }, 500);
diff --git a/apps/aptsciclk/app.png b/apps/aptsciclk/app.png
new file mode 100644
index 000000000..b37efdaf8
Binary files /dev/null and b/apps/aptsciclk/app.png differ
diff --git a/apps/aptsciclk/metadata.json b/apps/aptsciclk/metadata.json
new file mode 100644
index 000000000..c450d926e
--- /dev/null
+++ b/apps/aptsciclk/metadata.json
@@ -0,0 +1,18 @@
+{
+ "id": "aptsciclk",
+ "name": "Apeture Science Clock",
+ "shortName":"AptSci Clock",
+ "version": "0.08",
+ "description": "A clock based on the portal series",
+ "icon": "app.png",
+ "type": "clock",
+ "tags": "clock",
+ "supports": ["BANGLEJS2"],
+ "allow_emulator": false,
+ "readme":"README.md",
+ "storage": [
+ {"name":"aptsciclkquotes.txt","url":"quotes.txt"},
+ {"name":"aptsciclk.app.js","url":"app.js"},
+ {"name":"aptsciclk.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/aptsciclk/quotes.txt b/apps/aptsciclk/quotes.txt
new file mode 100644
index 000000000..bc7a04867
--- /dev/null
+++ b/apps/aptsciclk/quotes.txt
@@ -0,0 +1 @@
+Well here we are again^You euthanized your faithful Companion Cube more quickly than any test subject on record. Congratulations.^So get comfortable while I warm up the neurotoxin emitters^This isn't brave. It's murder. What did I ever do to you?^The difference between us is that I can feel pain.^Who's gonna make the cake when I'm gone? You?^Oh... It's you.^I've been really busy being dead. You know, after you MURDERED ME.^So. How are you holding up? BECAUSE I'M A POTATO.^You really do have brain damage, don't you?^You like revenge, right? Everybody likes revenge. Well, let's go get some.^It's been fun. Don't come back.^And then you showed up. You dangerous, mute lunatic.^Unbelievable. You, [subject name here] must be the pride of [subject hometown here.]^You are not a good person. You know that, right? Good people don't get up here.^Cake, and grief counseling, will be available at the conclusion of the test.^This is your fault. I'm going to kill you. And all the cake is gone. You don't even care, do you?^Momentum, a function of mass and velocity, is conserved between portals. In layman's terms, speedy thing goes in, speedy thing comes out.
diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog
index 1e6ac71d7..bd40868a5 100644
--- a/apps/messages/ChangeLog
+++ b/apps/messages/ChangeLog
@@ -36,3 +36,4 @@
Also gave the widget a pixel more room to the right
0.23: Change message colors to match current theme instead of using green
Now attempt to use Large/Big/Medium fonts, and allow minimum font size to be configured
+0.24: Remove left-over debug statement
diff --git a/apps/messages/app.js b/apps/messages/app.js
index 6feda9494..fdcad7469 100644
--- a/apps/messages/app.js
+++ b/apps/messages/app.js
@@ -92,6 +92,7 @@ function getMessageImage(msg) {
if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA==");
if (s=="slack") return atob("GBiBAAAAAAAAAABAAAHvAAHvAADvAAAPAB/PMB/veD/veB/mcAAAABzH8B3v+B3v+B3n8AHgAAHuAAHvAAHvAADGAAAAAAAAAAAAAA==");
if (s=="sms message") return getNotificationImage();
+ if (s=="threema") return atob("GBjB/4Yx//8AAAAAAAAAAAAAfgAB/4AD/8AH/+AH/+AP//AP2/APw/APw/AHw+AH/+AH/8AH/4AH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=");
if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA");
if (s=="telegram") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA==");
if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA==");
@@ -120,6 +121,7 @@ function getMessageImageCol(msg,def) {
"outlook mail": "#0072c6",
"skype": "#00aff0",
"slack": "#e51670",
+ "threema": "#000",
"telegram": "#0088cc",
"twitter": "#1da1f2",
"whatsapp": "#4fce5d",
@@ -166,27 +168,56 @@ function showMapMessage(msg) {
}
function showMusicMessage(msg) {
+ var updateLabelsInterval;
+ var trackScrollOffset = 0;
+ var artistScrollOffset = 0;
+ var albumScrollOffset = 0;
+ var trackName = '';
+ var artistName = '';
+ var albumName = '';
+
function fmtTime(s) {
var m = Math.floor(s/60);
s = (parseInt(s%60)).toString().padStart(2,0);
return m+":"+s;
}
+ function reduceStringAndPad(text, offset, maxLen) {
+ var sliceLength = offset + maxLen > text.length ? text.length - offset : maxLen;
+ return text.substr(offset, sliceLength).padEnd(maxLen, " ");
+ }
+
function back() {
+ clearInterval(updateLabelsInterval);
msg.new = false;
saveMessages();
layout = undefined;
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1});
}
+ function updateLabels() {
+ trackName = reduceStringAndPad(msg.track, trackScrollOffset, 13);
+ artistName = reduceStringAndPad(msg.artist, artistScrollOffset, 21);
+ albumName = reduceStringAndPad(msg.album, albumScrollOffset, 21);
+
+ trackScrollOffset++;
+ artistScrollOffset++;
+ albumScrollOffset++;
+
+ if ((trackScrollOffset + 13) > msg.track.length) trackScrollOffset = 0;
+ if ((artistScrollOffset + 21) > msg.artist.length) artistScrollOffset = 0;
+ if ((albumScrollOffset + 21) > msg.album.length) albumScrollOffset = 0;
+ }
+ updateLabels();
+
layout = new Layout({ type:"v", c: [
- {type:"h", fillx:1, bgCol:g.theme.bg2, col: g.theme.fg2, c: [
+ {type:"h", fillx:1, bgCol:g.theme.bg2, col: g.theme.fg2, c: [
{ type:"btn", src:getBackImage, cb:back },
{ type:"v", fillx:1, c: [
- { type:"txt", font:fontMedium, label:msg.artist, pad:2 },
- { type:"txt", font:fontMedium, label:msg.album, pad:2 }
+ { type:"txt", font:fontMedium, bgCol:g.theme.bg2, label:artistName, pad:2, id:"artist" },
+ { type:"txt", font:fontMedium, bgCol:g.theme.bg2, label:albumName, pad:2, id:"album" }
]}
]},
- {type:"txt", font:fontLarge, label:msg.track, fillx:1, filly:1, pad:2 },
+ {type:"txt", font:fontLarge, bgCol:g.theme.bg, label:trackName, fillx:1, filly:1, pad:2, id:"track" },
Bangle.musicControl?{type:"h",fillx:1, c: [
{type:"btn", pad:8, label:"\0"+atob("FhgBwAADwAAPwAA/wAD/gAP/gA//gD//gP//g///j///P//////////P//4//+D//gP/4A/+AD/gAP8AA/AADwAAMAAA"), cb:()=>Bangle.musicControl("play")}, // play
{type:"btn", pad:8, label:"\0"+atob("EhaBAHgHvwP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP3gHg"), cb:()=>Bangle.musicControl("pause")}, // pause
@@ -196,6 +227,14 @@ function showMusicMessage(msg) {
]});
g.clearRect(Bangle.appRect);
layout.render();
+
+ updateLabelsInterval = setInterval(function() {
+ updateLabels();
+ layout.artist.label = artistName;
+ layout.album.label = albumName;
+ layout.track.label = trackName;
+ layout.render();
+ }, 400);
}
function showMessageScroller(msg) {
diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json
index e8ed83099..0387c1972 100644
--- a/apps/messages/metadata.json
+++ b/apps/messages/metadata.json
@@ -1,7 +1,7 @@
{
"id": "messages",
"name": "Messages",
- "version": "0.23",
+ "version": "0.24",
"description": "App to display notifications from iOS and Gadgetbridge/Android",
"icon": "app.png",
"type": "app",
diff --git a/apps/messages/widget.js b/apps/messages/widget.js
index 4a247b917..d9363573a 100644
--- a/apps/messages/widget.js
+++ b/apps/messages/widget.js
@@ -6,7 +6,6 @@ draw:function() {
g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+this.iconwidth);
g.drawImage((c&1) ? atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+DAADDAADDAADDwAPD8A/DOBzDDn/DA//DAHvDAPvjAPvjAPvjAPvh///gf/vAAD+AAB8AAAAA==") : atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+D///D///A//8CP/xDj/HD48DD+B8D/D+D/3vD/vvj/vvj/vvj/vvh/v/gfnvAAD+AAB8AAAAA=="), this.x, this.y);
let settings = require('Storage').readJSON("messages.settings.json", true) || {};
- console.log("dingen ", typeof(settings.repeat), settings.repeat)
if (settings.repeat===undefined) settings.repeat = 4;
if (c<120 && (Date.now()-this.l)>settings.repeat*1000) {
this.l = Date.now();
@@ -47,4 +46,4 @@ want to buzz but should still show that there are unread messages. */
if (global.MESSAGES===undefined) (function() {
var messages = require("Storage").readJSON("messages.json",1)||[];
if (messages.some(m=>m.new)) WIDGETS["messages"].show(true);
-})();
\ No newline at end of file
+})();
diff --git a/apps/ptlaunch/ChangeLog b/apps/ptlaunch/ChangeLog
index 68b7d3e1c..eec3610ed 100644
--- a/apps/ptlaunch/ChangeLog
+++ b/apps/ptlaunch/ChangeLog
@@ -4,4 +4,5 @@
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.
0.11: Respect theme colors. Fix: Do not pollute global space with internal variables ans functions in boot.js
0.12: Improve pattern detection code readability by PaddeK http://forum.espruino.com/profiles/117930/
-0.13: Improve pattern rendering by HughB http://forum.espruino.com/profiles/167235/
\ No newline at end of file
+0.13: Improve pattern rendering by HughB http://forum.espruino.com/profiles/167235/
+0.14: Update setUI to work with new Bangle.js 2v13 menu style
diff --git a/apps/ptlaunch/boot.js b/apps/ptlaunch/boot.js
index 19a8f16cb..748d564f3 100644
--- a/apps/ptlaunch/boot.js
+++ b/apps/ptlaunch/boot.js
@@ -76,6 +76,7 @@
var sui = Bangle.setUI;
Bangle.setUI = function (mode, cb) {
sui(mode, cb);
+ if ("object"==typeof mode) mode = mode.mode;
if (!mode) {
Bangle.removeListener("drag", dragHandler);
storedPatterns = {};
diff --git a/apps/ptlaunch/metadata.json b/apps/ptlaunch/metadata.json
index 6c3870d24..0b6dce3d1 100644
--- a/apps/ptlaunch/metadata.json
+++ b/apps/ptlaunch/metadata.json
@@ -2,7 +2,7 @@
"id": "ptlaunch",
"name": "Pattern Launcher",
"shortName": "Pattern Launcher",
- "version": "0.13",
+ "version": "0.14",
"description": "Directly launch apps from the clock screen with custom patterns.",
"icon": "app.png",
"screenshots": [{"url":"manage_patterns_light.png"}],
diff --git a/apps/shortcuts/ChangeLog b/apps/shortcuts/ChangeLog
index 2286a7f70..0e4a98065 100644
--- a/apps/shortcuts/ChangeLog
+++ b/apps/shortcuts/ChangeLog
@@ -1 +1,2 @@
-0.01: New App!
\ No newline at end of file
+0.01: New App!
+0.02: Update setUI to work with new Bangle.js 2v13 menu style
diff --git a/apps/shortcuts/boot.js b/apps/shortcuts/boot.js
index f71f6d6ca..58ce2d067 100644
--- a/apps/shortcuts/boot.js
+++ b/apps/shortcuts/boot.js
@@ -1,6 +1,7 @@
(function() {
var sui = Bangle.setUI;
Bangle.setUI = function(mode, cb) {
+ if ("object"==typeof mode) mode = mode.mode;
if (mode!="clock") return sui(mode,cb);
return sui("clockupdown", (dir) => {
let settings = require("Storage").readJSON("shortcuts.json", 1)||{};
@@ -12,4 +13,3 @@
});
};
})();
-
\ No newline at end of file
diff --git a/apps/shortcuts/metadata.json b/apps/shortcuts/metadata.json
index 2351a102f..922d5ae11 100644
--- a/apps/shortcuts/metadata.json
+++ b/apps/shortcuts/metadata.json
@@ -2,7 +2,7 @@
"id": "shortcuts",
"name": "Shortcuts",
"shortName": "Shortcuts",
- "version": "0.01",
+ "version": "0.02",
"description": "Quickly load your favourite apps from (almost) any watch face.",
"icon": "app.png",
"type": "bootloader",
diff --git a/apps/smclock/ChangeLog b/apps/smclock/ChangeLog
new file mode 100644
index 000000000..b029d805d
--- /dev/null
+++ b/apps/smclock/ChangeLog
@@ -0,0 +1,2 @@
+0.01: Initial version
+0.02: Add battery level
diff --git a/apps/smclock/README.md b/apps/smclock/README.md
new file mode 100644
index 000000000..7b5613147
--- /dev/null
+++ b/apps/smclock/README.md
@@ -0,0 +1,5 @@
+# Monogram Watch Face
+
+Just a simple watch face for the Banglejs2.
+
+It shows battery level in the upper left corner, date information in the upper right, and time information in the bottom.
diff --git a/apps/smclock/app-icon.js b/apps/smclock/app-icon.js
new file mode 100644
index 000000000..91494a528
--- /dev/null
+++ b/apps/smclock/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("l0uwkEogAgm4VUod3ugsUu93FigABFyQsCFyQsDFyQsEFyAsFFyAsGFxwsHFxwsIu9yCpVCmQWIkckCxMhkcnFg8yiQsJiEBFw9zkMBFxEhgEAiYuGmUQgAuHFgIWBFwwsBBQQuGBQQuHFgQABFwosDFwwsDFw4KEFwosEFwosFFwgsFFwoKGFwYsGFwYsHEYUzEJAuBoIKHBYMiEJEAilEBRESFhAABLYNAFiUERQQsUFw4sPFwwsPFwosRFwgsRFwYsTFwQsTAANBFigABFigABoQtIFhYuKCpguIFhouICpwuGFh4uGCqAuEFiIuECqQuCFiYuCCqgAMA"))
diff --git a/apps/smclock/app.js b/apps/smclock/app.js
new file mode 100644
index 000000000..6aff72a46
--- /dev/null
+++ b/apps/smclock/app.js
@@ -0,0 +1,108 @@
+const background = {
+ width : 176, height : 176, bpp : 3,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("/4A/AH4ACUb8H9MkyVJAThB/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/INP/AH4A/AAX8Yz4Afn5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/INI="))
+};
+
+const weekday = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
+var level = -1;
+
+function ISO8601_week_no(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
+ var tdt = new Date(date.valueOf());
+ var dayn = (date.getDay() + 6) % 7;
+ tdt.setDate(tdt.getDate() - dayn + 3);
+ var firstThursday = tdt.valueOf();
+ tdt.setMonth(0, 1);
+ if (tdt.getDay() !== 4) {
+ tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
+ }
+ return 1 + Math.ceil((firstThursday - tdt) / 604800000);
+}
+
+function d02(value) {
+ return ('0' + value).substr(-2);
+}
+
+function pollBattery() {
+ level = E.getBattery();
+ return level;
+}
+
+function getBatteryColor(level) {
+ var color;
+ if (level < 0) {
+ level = pollBattery();
+ }
+ if(level>80) {
+ color = [0,0,1];
+ } else if(level>60) {
+ color = [0,1,1];
+ } else if(level>40) {
+ color = [0,1,0];
+ } else if(level>20) {
+ color = [1,1,0];
+ } else {
+ color = [1,0,0];
+ }
+ return color;
+}
+
+function draw() {
+ g.drawImage(background);
+
+ const color = getBatteryColor();
+ const bat = d02(E.getBattery()) + "%";
+ const d = new Date();
+ const day = d.getDate();
+ const month = (d.getMonth() + 1);
+ const week = d02(ISO8601_week_no(d));
+ const date1 = d02(day) + "/" + d02(month);
+ const date2 = weekday[d.getDay()] + " " + d02(week);
+ const h = d.getHours();
+ const m = d.getMinutes();
+ const time = d02(h) + ":" + d02(m);
+
+ g.reset();
+
+ g.setColor(0, 0, 0);
+ g.setFont("Vector", 20);
+ g.drawString(date1, 105, 20, false);
+ g.setFont("Vector", 16);
+ g.drawString(date2, 105, 55, false);
+
+ g.setColor(1, 1, 1);
+ g.setFont("Vector", 60);
+ g.drawString(time, 10, 108, false);
+
+ g.setColor(1, 1, 1);
+ g.setFont("Vector", 16);
+ g.drawString("Bat:", 12, 22, false);
+ g.setColor(color[0], color[1], color[2]);
+ g.drawString(bat, 52, 22, false);
+}
+
+g.clear();
+
+pollBattery();
+draw();
+
+var batInterval = setInterval(pollBattery, 60000);
+var drawInterval = setInterval(draw, 10000);
+
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower',on=>{
+ if (batInterval) clearInterval(batInterval);
+ batInterval = undefined;
+ if (drawInterval) clearInterval(drawInterval);
+ drawInterval = undefined;
+ if (on) {
+ batInterval = setInterval(pollBattery, 60000);
+ drawInterval = setInterval(draw, 10000);
+
+ pollBattery();
+ draw(); // draw immediately
+ }
+});
+
+// Show launcher when middle button pressed
+Bangle.setUI("clock");
diff --git a/apps/smclock/app.png b/apps/smclock/app.png
new file mode 100644
index 000000000..16712c63b
Binary files /dev/null and b/apps/smclock/app.png differ
diff --git a/apps/smclock/metadata.json b/apps/smclock/metadata.json
new file mode 100644
index 000000000..1783ca7bf
--- /dev/null
+++ b/apps/smclock/metadata.json
@@ -0,0 +1,16 @@
+{
+ "id":"smclock",
+ "name":"Monogram Watch Face",
+ "shortName":"MonoClock",
+ "icon":"app.png",
+ "version":"0.02",
+ "description": "A simple watchface based on my stylised monogram.",
+ "tags":"clock",
+ "readme":"README.md",
+ "supports" : ["BANGLEJS2"],
+ "allow_emulator": true,
+ "storage": [
+ {"name":"smclock.app.js","url":"app.js"},
+ {"name":"smclock.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/swiperclocklaunch/ChangeLog b/apps/swiperclocklaunch/ChangeLog
index c1b4a5fbb..244b602b5 100644
--- a/apps/swiperclocklaunch/ChangeLog
+++ b/apps/swiperclocklaunch/ChangeLog
@@ -1,2 +1,3 @@
0.01: New App!
0.02: Fix issue with mode being undefined
+0.03: Update setUI to work with new Bangle.js 2v13 menu style
diff --git a/apps/swiperclocklaunch/boot.js b/apps/swiperclocklaunch/boot.js
index e9b203eee..bb285ea94 100644
--- a/apps/swiperclocklaunch/boot.js
+++ b/apps/swiperclocklaunch/boot.js
@@ -4,6 +4,7 @@
Bangle.setUI = function(mode, cb) {
sui(mode,cb);
if(!mode) return;
+ if ("object"==typeof mode) mode = mode.mode;
if (!mode.startsWith("clock")) return;
Bangle.swipeHandler = dir => { if (dir<0) Bangle.showLauncher(); };
Bangle.on("swipe", Bangle.swipeHandler);
diff --git a/apps/swiperclocklaunch/metadata.json b/apps/swiperclocklaunch/metadata.json
index 733aaa032..5e4a0d648 100644
--- a/apps/swiperclocklaunch/metadata.json
+++ b/apps/swiperclocklaunch/metadata.json
@@ -1,7 +1,7 @@
{
"id": "swiperclocklaunch",
"name": "Swiper Clock Launch",
- "version": "0.02",
+ "version": "0.03",
"description": "Navigate between clock and launcher with Swipe action",
"icon": "swiperclocklaunch.png",
"type": "bootloader",
diff --git a/apps/widcw/ChangeLog b/apps/widcw/ChangeLog
new file mode 100644
index 000000000..a4bc24d1a
--- /dev/null
+++ b/apps/widcw/ChangeLog
@@ -0,0 +1 @@
+0.01: First version
\ No newline at end of file
diff --git a/apps/widcw/logo.svg b/apps/widcw/logo.svg
new file mode 100644
index 000000000..e093414d5
--- /dev/null
+++ b/apps/widcw/logo.svg
@@ -0,0 +1,62 @@
+
+
+
+
diff --git a/apps/widcw/metadata.json b/apps/widcw/metadata.json
new file mode 100644
index 000000000..653b093ec
--- /dev/null
+++ b/apps/widcw/metadata.json
@@ -0,0 +1,13 @@
+{
+ "id": "widcw",
+ "name": "Calendar Week Widget",
+ "version": "0.01",
+ "description": "Widget which shows the current calendar week",
+ "icon": "widget.png",
+ "type": "widget",
+ "tags": "widget,calendar",
+ "supports": ["BANGLEJS","BANGLEJS2"],
+ "storage": [
+ {"name":"widcw.wid.js","url":"widget.js"}
+ ]
+}
diff --git a/apps/widcw/widget.js b/apps/widcw/widget.js
new file mode 100644
index 000000000..ef43a4551
--- /dev/null
+++ b/apps/widcw/widget.js
@@ -0,0 +1,48 @@
+(function() {
+ var width = 22; // width of the widget
+
+ function draw() {
+ const x = this.x, y = this.y, x2 = x+21, y2 = y+23;
+
+ var date = new Date();
+
+ // Calculate calendar week (https://stackoverflow.com/a/6117889)
+ getCW= function(date){
+ var d=new Date(date.getFullYear(), date.getMonth(), date.getDate());
+ var dayNum = d.getDay() || 7;
+ d.setDate(d.getDate() + 4 - dayNum);
+ var yearStart = new Date(d.getFullYear(),0,1);
+ return Math.ceil((((d - yearStart) / 86400000) + 1)/7);
+ };
+
+ g.reset().setFontAlign(0, 0) // center all text
+ // header
+ .setBgColor("#f00").setColor("#fff")
+ .clearRect(x, y, x2, y+8).setFont("4x6").drawString("CW", (x+x2)/2+1, y+5)
+ // date
+ .setBgColor("#fff").setColor("#000")
+ .clearRect(x, y+9, x2, y2).setFont("Vector:16").drawString(getCW(date), (x+x2)/2+2, y+17);
+
+ if (!g.theme.dark) {
+ // black border around date for light themes
+ g.setColor("#000").drawPoly([
+ x, y+9,
+ x, y2,
+ x2, y2,
+ x2, y+9
+ ]);
+ }
+
+ // redraw when date changes
+ setTimeout(()=>WIDGETS["widcw"].draw(), (86401 - Math.floor(date/1000) % 86400)*1000);
+
+ }
+
+ // add your widget
+ WIDGETS["widcw"]={
+ area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
+ width: width, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
+ draw:draw // called to draw the widget
+ };
+
+})();
diff --git a/apps/widcw/widget.png b/apps/widcw/widget.png
new file mode 100644
index 000000000..c73d40c5a
Binary files /dev/null and b/apps/widcw/widget.png differ
diff --git a/apps/widcw/widget.svg b/apps/widcw/widget.svg
new file mode 100644
index 000000000..d3e567286
--- /dev/null
+++ b/apps/widcw/widget.svg
@@ -0,0 +1,55 @@
+
+
+
+
diff --git a/apps/widgps/ChangeLog b/apps/widgps/ChangeLog
index 3d47b8a0c..f68fc701c 100644
--- a/apps/widgps/ChangeLog
+++ b/apps/widgps/ChangeLog
@@ -2,3 +2,4 @@
0.02: Don't break if running on 2v08 firmware (just don't display anything)
0.03: Fix positioning
0.04: Show GPS fix status
+0.05: Don't poll for GPS status, override setGPSPower handler (fix #1456)
diff --git a/apps/widgps/metadata.json b/apps/widgps/metadata.json
index 2f59aa82a..39bff2fad 100644
--- a/apps/widgps/metadata.json
+++ b/apps/widgps/metadata.json
@@ -1,7 +1,7 @@
{
"id": "widgps",
"name": "GPS Widget",
- "version": "0.04",
+ "version": "0.05",
"description": "Tiny widget to show the power and fix status of the GPS",
"icon": "widget.png",
"type": "widget",
diff --git a/apps/widgps/widget.js b/apps/widgps/widget.js
index 25df2178a..bfdb89d33 100644
--- a/apps/widgps/widget.js
+++ b/apps/widgps/widget.js
@@ -1,7 +1,13 @@
(function(){
- if (!Bangle.isGPSOn) return; // old firmware
+ // override setGPSPower so we know if GPS is on or off
+ var oldSetGPSPower = Bangle.setGPSPower;
+ Bangle.setGPSPower = function(on,id) {
+ var isGPSon = oldSetGPSPower(on,id);
+ WIDGETS.gps.draw();
+ return isGPSon;
+ }
- function draw() {
+ WIDGETS.gps={area:"tr",width:24,draw:function() {
g.reset();
if (Bangle.isGPSOn()) {
const gpsObject = Bangle.getGPSFix();
@@ -14,20 +20,5 @@
g.setColor("#888"); // off = grey
}
g.drawImage(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), this.x, 2+this.y);
- }
-
- var timerInterval;
- Bangle.on('lcdPower', function(on) {
- if (on) {
- WIDGETS.gps.draw();
- if (!timerInterval) timerInterval = setInterval(()=>WIDGETS.gps.draw(), 2000);
- } else {
- if (timerInterval) {
- clearInterval(timerInterval);
- timerInterval = undefined;
- }
- }
- });
-
- WIDGETS.gps={area:"tr",width:24,draw:draw};
+ }};
})();