diff --git a/.gitmodules b/.gitmodules index fd6663d2a..c2c1104c2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "EspruinoAppLoaderCore"] path = core url = https://github.com/espruino/EspruinoAppLoaderCore.git +[submodule "webtools"] + path = webtools + url = https://github.com/espruino/EspruinoWebTools.git diff --git a/README.md b/README.md index d2f7022e9..1058787bb 100644 --- a/README.md +++ b/README.md @@ -255,8 +255,11 @@ and which gives information about the app for the Launcher. // 'app' - an application // 'clock' - a clock - required for clocks to automatically start // 'widget' - a widget + // 'module' - this provides a module that can be used with 'require'. + // 'provides_modules' should be used if type:module is specified // 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js' // 'settings' - apps that appear in Settings->Apps (with appname.settings.js) but that have no 'app.js' + // 'clkinfo' - Provides a 'myapp.clkinfo.js' file that can be used to display info in clocks - see modules/clock_info.js // 'RAM' - code that runs and doesn't upload anything to storage // 'launch' - replacement 'Launcher' // 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle @@ -266,10 +269,21 @@ and which gives information about the app for the Launcher. // 'locale' - provides 'locale' library for language-specific date/distance/etc // (a version of 'locale' is included in the firmware) "tags": "", // comma separated tag list for searching + // common types are: + // 'clock' - it's a clock + // 'widget' - it is (or provides) a widget + // 'outdoors' - useful for outdoor activities + // 'tool' - a useful utility (timer, calculator, etc) + // 'game' - a game + // 'bluetooth' - uses Bluetooth LE + // 'system' - used by the system + // 'clkinfo' - provides or uses clock_info module for data on your clock face (see modules/clock_info.js) "supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2 "dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above) "dependencies" : { "messages":"app" } // optional, depend on a specific app ID // for instance this will use notify/notifyfs is they exist, or will pull in 'notify' + "dependencies" : { "messageicons":"module" } // optional, depend on a specific library to be used with 'require' + "provides_modules" : ["messageicons"] // optional, this app provides a module that can be used with 'require' "readme": "README.md", // if supplied, a link to a markdown-style text file // that contains more information about this app (usage, etc) // A 'Read more...' link will be added under the app @@ -454,7 +468,10 @@ It should also add `myappid.json` to `data`, to make sure it is cleaned up when ## Modules You can include any of [Espruino's modules](https://www.espruino.com/Modules) as -normal with `require("modulename")`. If you want to develop your own module for your +normal with `require("modulename")`. To include [Bangle's modules](modules) for use in the Web +IDE, [upload the modules to internal storage](modules#upload-the-module-to-the-bangles-internal-storage) +or [change the IDE's search path](modules#change-the-web-ide-search-path-to-include-banglejs-modules). +If you want to develop your own module for your app(s) then you can do that too. Just add the module into the `modules` folder then you can use it from your app as normal. diff --git a/android.html b/android.html index 93999008f..8a70a46e9 100644 --- a/android.html +++ b/android.html @@ -170,10 +170,10 @@ - + + - diff --git a/apps/advcasio/ChangeLog b/apps/advcasio/ChangeLog index a1b528cf6..fd37c324e 100644 --- a/apps/advcasio/ChangeLog +++ b/apps/advcasio/ChangeLog @@ -1,3 +1,4 @@ 0.01: AdvCasio first version 0.02: Remove un-needed fonts to improve memory usage 0.03: Tell clock widgets to hide. +0.04: Swipe down to see widgets, step counter now just uses getHealthStatus diff --git a/apps/advcasio/app.js b/apps/advcasio/app.js index 8cb904f90..9d246b7ef 100644 --- a/apps/advcasio/app.js +++ b/apps/advcasio/app.js @@ -1,304 +1,160 @@ const storage = require('Storage'); +require("Font6x12").add(Graphics); require("Font8x12").add(Graphics); require("Font7x11Numeric7Seg").add(Graphics); function bigThenSmall(big, small, x, y) { - g.setFont("7x11Numeric7Seg", 2); - g.drawString(big, x, y); - x += g.stringWidth(big); - g.setFont("8x12"); - g.drawString(small, x, y); + g.setFont("7x11Numeric7Seg", 2); + g.drawString(big, x, y); + x += g.stringWidth(big); + g.setFont("8x12"); + g.drawString(small, x, y); } -function getClockBg() { - return require("heatshrink").decompress(atob("icVgf/ABv8v4DBx4CB+PH8F+nAGB48fwEHBwXjxwqBuPH//+nAGBBwIjCAwI2D/wGBgIyDI4QGDwAGBHYX/4AGBn4UFEYQpCEYYpCAAMfMhP4FIgABwJ8OEBIA==")); -} - - -// sun, cloud, rain, thunder -var iconsWeather = [ - require("heatshrink").decompress(atob("i8Ugf/ACcfA434BA/AAwsAv0/8F/BAcDwEHHIpECFI3wn4GC/gOC+PAGoXggEH/+ODQgXBGQv/wAbBBAnguEACIn4gfxI4JXFwJmG/kPBA3jSynw")), require("heatshrink").decompress(atob("i0Ugf/AEXggIGE/0A/kPBAmBCIN/A4Y8CgAICwEHBYoUE/ACCj4sDn4CBC4YyDwBrDCgYA3A")), require("heatshrink").decompress(atob("h8Rgf/AAuBAgf8h4FDCwM/AgPA/gFC/0HgEBBQPwnEfDoWAg4jC/gOCAoQmBAQXjFIV//8f//4IQP4j/+gAIB4EcHII4CAoI+DLQJXF/AA==")), require("heatshrink").decompress(atob("h0Pgf/AA8fAYX+g4EC8EBAgXADAeAgAECgAOC/wrCDQIOBBYfwgAaC/kAn4EB/EAv4aDHAeBIg38")) -]; - - function getBackgroundImage() { - return require("heatshrink").decompress(atob("2GwghC/AH4A/AH4AMl////wAwURiQECgUzmcxBQQCBiYUBBARW+LAcCAgcPBYgFBkAIFG7kQiAKIiIKBgISOAAJBD//zKQfxK4vyAoMQCgn/ERBhBBYR5BAwR1DB4Y2DgYPCGIQRCCQcP+EfGJI0FEgRSCGAQCCX4JXCkAhDn4lI+HyK4YWBFIPzJYJXHAIMSK4cwJ4I3CAYMzA4cfcRMBdwytBK4i6FK4IUCMgYAEGIITBK4cCaAPwgJXB+fzK4sAgYtCK5EfA4pXR+AmBaIZYCK6KcCAwSjDEYXx/8vK5QRCK4kPK6cDkJREBIMBfgIrDK5svUAIQBAwIaCK4w+DK4YGBK7IaBboIuCK4gFCJwYBBiBCCCgQhHHYgGDgArBK5IGDAYMgJ4Xwn53BGgLVDmBXKAAinDLpJXCAAYhHR4YODn/wJIPyTYZXDE4RXD+ECNILIDAIPwj4xIAAYNCR4fyVIYLFA4KEBBAglKAGUCmcykEAiMQBIURBYM/BgIUEgcz+bTKAH4A/AH4A/AHP/AGY1d+BWCh5X/LCpW1K74fgG/5X/AH5X/K9Bg/K63wK/5XWgBX/K6pWBK/5XU+BWBh5J/K6auCK/5XTVwRfFAH5XOKwRX/K6auDh5I/K6SuDWP5XSVwYADWX6vXK/5XQWQpW/K6auDJP5XWV35XT+Cu/K7Ku/K65H/K6hW/K7EPI35XWIv5XWAH5X/K/4A/K/5X/K/4A/K9cAAH4A/AFzz/AHRX/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/40VAH4A/AFzLb+EPDm4AdK/5X/K+PwgEAHy5X9HgMAK/5XXH6xX/H65X/K/5X/K98AK7sAgBX3DjBWFO644DSTHwGzJXED4RXaDoLqcK7weWDIQcXK8I6YK77KXK4o8DPbY6ZK7qvDDy6vdR7JXDh60EDyw5BAIRXYSwjMbAgIhUDwJZCHwJX0GwjRWNwIAEHSwBCDSpXFH4pXzDS5XIEARXVSYbQEDaYzCK+6vcKaxXNDypX9HwQkbHS40COSpXKK2A6CHgRXcPIhX0SwpXYVuQ6EgBX/K644YODBXkSDJX/K/5X/DtRX6gA3YOkRWbLDZX4KwYA/AG8F5vdABncKH4AGhpRJAYXNAgPAKP4AF5vMJwoDBAQIKE6BR/AAvc5vO9wAB7oCB9veAoPcAoPcK+kwh8AgcA98An//gH/+sD//wCISgBJ4IABAYpaC9vdK4UP/9AAQNQr/zgHwEYNQFYQAh+EP+FegH+A4QBCMQIKBAAPNK4yxBA4RXCV4YZBE4IjChwCDmApCK8VdmHggHgFYf0SQJXE5nMK4anCAoYHC5pXCaQJXBop+BqAGEK7f/AAQeEKwQrBqCtDAILjBCQfNK4JTCAYZXF7qvD//gV4S2DgEFFIYAECgIACMC8PKoIBB8n1K4ivF5vc5xOCWYZbBAYavHU4RXCr4pEAEMDfoNQGoMEgEwYQPwAoIBBAAPM5ipC7oDCVIIAE7hXCD4SdBiEP+gGBgihCFYIAz5pXBAAnN7oIB7nc5gOBK4QA/K4pNCWgSpCBInNK/4AGhncKIStC7gCBA4QAC4BR/AAysCABZW/AHwA=")); + return require("heatshrink").decompress(atob("2GwwkGIf4AfgMRkUiiIHCiMRiAMDAwYCCBAYVDAHMv/4ACkBIBAgPxBgM/BYXyAoICBCowA5gRADKQUDKAYMCmYCBiBXBCo4A5J4MxiMSKQUf+YBBBgSiBgc/kBXBBAMyCoK2CK/btCiUhfAJLCkBkDiMQgBXDCoUvNAJX+AAU/+MB/8wAQIAC+cQK5hoDgIEBBIQFEAYIPHBIgBBAQQIDBwZXSKIMxgJaBgEjmZYCmBXLgLBBkkAgUhiMxBIM0iMSCoMRkZECkQJEichBINDiETAgISBiQTDK6MvJAXzVIQrBBYMCK5E/K4kwGIJXFgdAMgQQBiYiCDgU0HQSlCgMikIEBEAMTDYJXQ+UikYDBj6nCAAMTWoJ6BK4oVEK4c0oQ+BK4MjAgMDJoJXHNYJXHBwa0BohcDY4QAKgJQE+LzBNwJVBkQMEkBXBCoyvFJAVAKISaBiMiHQRIDkVBoSyCK5CvBAgavNDAJAC+cQn5DCgSpBl4MDgBXBgCsBCoYoMLAKREgIKDBJIdKK5oA/AH4A/AH4A/ADUBIH4APiAFEi1mAGUADrkRKwUGK2ZXes1gK2xXfD8A3/K/4AWgxX/ACtga2AwIHLkAgCwvJw6RcDgIABK+w4cK/I4dsEGP5BXtSAQ6BV/5XSG4RX/K6Y3fK+42CK/5XTGwcGK/5XSVwY5cK+o1DAAayYsAhDsCv4K7BTBK4YeYK7CyFVzJXFFIpXtVwYiYK/rmZKYYDDELJXXG4YiaK/Y0aKgQAEK+gkdKt5XGKzqv5GTpX6ETlgK4xWrKTyxKVthXmAGRX/K/5X/AH5X/K/4gBAH4A/AFz/uAH4A/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/AHNggEGHfEAgAEHKyQXVK0qTCAggbUK+6SDAApzXK/5BRDYZX3KxBBSYqxXngyvaV25XEd4ZCSsAcBAoRZ2dQZXBLwgaQCIYeCAGirCS4YGCDSJXCC6ZaodYICBZzSw4S4I+XDgSv4K4rzCK/47RAQTMaWHI9YV3TscV3aVagByBK3SwCSqyt8AAQ+XK/4A/AH4A/AH4A3gAA/AH4AuZbdggwc3ADpX/K/5XxsEAgA+XK/o8BgBX/K64/WK/4/XK/5X/K/5XvgBX/K64cYHrw4CSTFggCuXK4oDCEQJXYDS6ScDgg4CPKyRCAAZX0HAgBDK+LlYK4oeBAwZ9aK+lgAoQGBgyvzDIIDBK66sCG4JXYCwIBDK7ADCK+xZCHwJXzGoQ8BK7DpBAAaSXSgRXZO4okCK+IaXV4oABEILSWSYjRCHSo3BDSxXEAAIcBAISvyKawcIAYIGCK/4cUH4YlaHS0AHgI1XOg5YBPrY6WHgRXfAGRXDHzBX8VoJX/K68ADjRX6sBX/K/5X/K8wdcK/UAG7B0iKzZYbK/BWDAH4A/hWpzWhIf4ASgOpzIAB0EAhhH/AB8ZzGJ1WazMA4pH/AB+pxOZxOpzVMqA2ugUzmcgD7cKVYOqzGqpnRFw8ykchK8kviEBmQFBgMiFocSCAcSkUQAgMikRsHhWqxOq0Ut4mqBw0DC4IxBD4wpBHAQMCA4cCGJIAFj8hDIQuBkMTCwU/AYQJBiUxFoPxiIVDK4kyxUz4cxl+KK5MfDQXyD4UCmMSmAEBAQQHDgMTmIxHAAqpBmaqCFwMDEYZRBgEjCQQBB+USK5E/ns/0Uzwc6K48ykYkCK4IfCc4I4CK4QHEBAYAMiICBmYuDmQEBh8iAgRXCLISvJO4MqwcklEiK5CADV4oaBV4oHEK6Eve4JNCbwRfCiMTFoMDkMRSAJXCD49azWp0UqzWayJXIQwcAO4cCkMCFIJOCA4XxK6KPBkR6DTwYyBAwYPEAggfFzORpWK1OZyAOHJ4QfERAUSEgQxIIIgAr1URWIOZzOgGtwAhgMZzWq1OaIv4ASKgOqzTkvAEmq1WgFtQA==")); } +function getRocketSequences() { + return { + 1: require("heatshrink").decompress(atob("qFGwkCkQAiiEBEkUgKQhPhE8ogCE8YhCiQoEE7pKEPIgncTQ4neEwpQCPoh1eJYYwCJ7QmHKAh1hZIpOjPAUBJ0ZQCTzEhExZ1lPAZ1kKDQmOJ65O2E65OPOy5O2E64mPOyxO/J2wnPJyx2QJ35O/J2khE0p2POq52PEy4nOiQnlOrEhiSfMJrEggQnLJzB1CPBQmZkInMEzBQDPBImbPBR1ZEoRMCZYImhgQgEE0BzFKAgmaDwLDFKAbqdYQwHBOrcgDgLBFJrsiiRNGYbpLBY4Ymhd4omkkUhE0pQEEwUBJjrHBd4QmCdzoiBDwYrCPLyZHF4QnagQeCE8UgJwYniJwgnIOzwfFO0wJCJzMQE4gyFEzR2FBQombkInDQI4AakAnBTYS+ZE5BMDE0LEES7YnLE0R3FAEQA=")), + 2: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYon/AA0gEAQniEwIhCAgYndEIjqBE8CaGKogmgKAp1fKAgncExBQBBQR1gKAp7BJ0IndExR4CE0idaOpYnbExqeYJxxPYEx0BJ0x2XExx2XJ20QE6xONJi5OPGwJOlBwLFkLoLFlBwJOkOwJOlE4JOkTjBOOE/52Pdi5OPEy7FnE5wmXE5xOZT5gmYEoMiiB1lgR4KTLAkDPBJ1WIAYDDKA4mWJwchDwYEDTjQiDJQh4GYLAhHFosSJy6OCTIxaEEywbBKYwjEEzMgUQxQFBogAURwZOGOjTKJdTYnOEryfHE0JQEfIpQgYQMAgJLeAgrtfTI4ndgSaFE4h0bdQkSZQpOfEAgIBO0AnEdrh2FJAb1EdbInEBIpObOwhOEEzYnFXzZ2HE4QlhE4QlDFMKcDYooniO0QnDT0YnCE0ciA")), + 3: require("heatshrink").decompress(atob("qFGwkEogAjiMUEkVAKYgnhPYolgOQIniOYZ4FOcLqBE8CaGKojpgKAomhEYUQE7gmHKAIxCE0QkCPYR1gZIgnZExR4CJ0idmE7ZONYzImNgEUJ0p3YJRh2ZJJwnXOpQhBdkpaETsMEGQhOhE7jFLUYpOfTzgmKE4hOiE4hOigEUJ0rvCEywnPEqx2OTjBOOE7ImOTsqeZE5zFYoJOmT5kBJzEAih4LdK5mBAQInKOqoYDEgR4JEypHDEYbxJOq5ABdgZ7CEzZOEJQgnGihOYEIzJFTionCKYxWGEy9ADAYnGUIYmWog/EdBFAEy7KIKAwnjKwLqWE5pMeT48CVQpQfgMjKEtEiAnfEQJQCgJSCTcB6FJzkEdYcUE8FAdQghDOzonKTjh2EZAidcDoInHJzodBOwx/BE8JxcOwsAOwQmhJgSXDObwnFEwUUO0LFGE8aeiE4YmiokQE0tE")), + 4: require("heatshrink").decompress(atob("qFGwkCkQAjiMSEkRTFE/4AGkMAgQCBE8MgEIYEDE7whDdQIngTQxVEE0ChFTjxQFE7jnFKAgxCOsBQFZgJ1gE7wmKPAROkTrTEHGAwnYiBHJFAaeXOoyXBEQZPac5AsFgJOhAoh2XJwwnFKoROdE4J9GJzwnIiQmVkInPAC0QE5AJFE64mHY5DFdE4SBEYr5JDJ0hKDJ0jCZJxoACgInmKLAmOTq5OOEy5OPTsxOYE5wmXO5wlYkAnMOqshiRNCgR4LOC8CkJCCEzxHDAgYnJOqpAEDoZ4HEyodDEQpQHdCsQOwwFHEyzoCPYzJGEy0gEwaZGA4acVEQSjHKAomXkQYEYAwlZeRKYDE8gjCYa7zJEwcCkImfKAb4FAD0hdTh4LgRSBOcR0CJz0gYYrrgN4QnEYrxOEE4bEeiAnGF4J2idL6VDE8ohBE0gnFE0J0BE4QGBiROgdIQABgJ2hJoTtjYgZSEE8ScgE4omikUQTcQADA=")), + 5: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYonhiAnjkEATIIniEwIhCAgYndEIhQFYUZVEE0BQFOr5QEeQQmiKAL1DOr5QEE7ROCDgZVEAoInZDwchFQQoDPAJOdEQYrBdrZFDOYwncEJDsDVIpOXgJxEE4pObEAgGFgJOaE48BaIhOZJ5ZObY5ROcE441CE6xOGPAwtCJzpGCJ0hHDkI1DJzwoEJzInLFg52dUo5O/J35OzE54mWOx4mXJxx1XE54mXkUhExkSJzCfMOrAlBPBiZXgQDBAQQmgJgh4JOqoYEFYwmaDoZzEFgh1YDgkiiAFEKAroXJJAGFiQmVkCNDTIz5EJy57HKAomXkQYEJoqaYeRadEJrAnJEQUAgJPiAoYmeT4cCkAnBE0BKCJkT1EkDCeJYYiDOkLDFFL5wBE4guCPDhEBEwQiDY70CkInDiQnCJzkhOwhKDdzp2Idb4nEE0B0Bdo4niE0J0CeYhOhgESUYYnidsgnEE0KeCE0gnDE0ciA")), + 6: require("heatshrink").decompress(atob("qFGwkCkQA/ABEgKQZPhEwgABEsAoGJkBxBE8JKEAowAbJIhQEgLDiPooAdKA4ncTZAndSwhQEFoInaJQkSKAwlZdgwnfSgYADE4h1ZDwInlcggnIOzAdCE8i7EY5J3XDgYhGd4pOZEI52bSYwGCOAJ2bYIodEOzZOFFAjFcEwwAIE6xOHABBO/J34ndEyx2PJ00BJ00SJ0p1XE54mXOxxO/J5wmYgQnMOrB2BPBgkWiJ1CPBbBYAYR4KiTAXRwIrFTjgZDJYZ4IEyoiEIwrDcEJJQFOqwiBDARxFFwgmXkAYDEogsBF4QmXEQJ7GUYYkBEzDKJAgYmdEQbKFEzonEKYgngJwgmfZggmjKQghgiBRGkBzeTgUikJRgc47LDErTnDEAkQJzkCJwYnEJzonEJIaddOwhJEJzgdBE4hYEJzieJADgnEE0KUCXzoAGkJLEiB2hOgQDBT0TsDT0YmlE4YmjkQ=")), + 7: require("heatshrink").decompress(atob("qFGwkCkQAhkIpBiQlhkBSEJ8InlEIIoFE7whEE8pQFE7giBJQoneI4MCTYhQDE7YdCYYondEQYnEPwZ1bE5BQCJzonHkR2ZEAkBE4pNBE7zHFYrYhFUgonaXAQeEEwruZEYcgiROHJ7AfDAwxOeAAURiAmHE65HIOzwmOJ35OPE6xOPO35O/J35O/J1gnPEyx2PEy5OOOq5OnE5xOYO5omZgJQMJrQnLiQnagR4JOq5nCDgZ1fEYRLDE5DoZkUQNoZ4GOrJKGAoomXOw7lCAwYmYDgJSEAAUBA4QDBJzB6FOQrDXJwTJFdLjJKE9jDYZRAmkKAwmhKAgmiKAYmBkApdJIgjCKYIncOQYvJYTovGE84lagR2DE4xOakBOEgJXFOjYnEJAbtdOwggEkAmbDgInDE0B0BE4QgcE5AkiXYbpCOLonGYo4nhPMYnCUEgnBY0kiA==")), + 8: require("heatshrink").decompress(atob("qFGwkCkQA/ABBSEJ8MgE4kBEsBPFE7xMCOIJ3hOYgFEE7rCGE70gE4pQBiAndYQwjBUohOZD4ZQFE7YkBE5AICYbZ2GE7sggJRCAA8iYzZOITroALE7EhExh4CAC0QExpPXOponZExx2XJ24nWdh52XdhzF/Yu5O/J35O0E55OXOx5O/J2omXE5x1XO54mYgQnMJrR4LOrciiAmiJgR4KEzIjDPBAlYiAiEeI51YkEBE4J5CD4KceTQQcBJgRQFdTZDCJIjDcNIqhGdTQmCkByFTTInDKgoAEE7ZEEJwhPdE1R1FE0InEE0R3DEwTGcDwomEE7hKFPYqafE8ROCE5DJbE5B/IEqh2ED4gnCJrMCJwgnEiB2bE4qeFEzUggQmIBQLEaEQImHLIImaE4YfcOw4lEFMLECS7onJO8wmkE4QljAAIA==")), + }; +} +let rocketSequence = 1; +let settings = storage.readJSON("cassioWatch.settings.json", true) || {}; +let rocketSpeed = settings.rocketSpeed || 700; +delete settings; // schedule a draw for the next minute let rocketInterval; var drawTimeout; function queueDraw() { - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = setTimeout(function() { - drawTimeout = undefined; - draw(); - }, 60000 - (Date.now() % 60000)); + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); } function clearIntervals() { - if (rocketInterval) clearInterval(rocketInterval); - rocketInterval = undefined; - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = undefined; + if (rocketInterval) clearInterval(rocketInterval); + rocketInterval = undefined; + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; } -//////////////////////////////////////////// -// TIMER FUNC -// -var timer_time = 0; -var alreadyListenTouch = false; -function initTouchTimer () { - if (alreadyListenTouch) return; - alreadyListenTouch = true; - - Bangle.on('swipe', function(dirX,dirY) { - if (canTouch === false) return; - var njson = getDataJson(); - if (!njson) return; - - if (dirX === -1) { - timer_time = 0; - delete njson.timer; - setDataJson(njson); - } - else if (dirX === 1) { - var now = new Date().getTime(); - njson.timer = now + (timer_time * 1000 * 60); - Bangle.setLocked(true); - setDataJson(njson); - Bangle.buzz(200, 0); - timer_time = 0; - } - else if (dirY === -1) { - if (canTouch === false || njson.timer) return; - timer_time = timer_time + 5; - } - else if (dirY === 1) { - if (canTouch === false || njson.timer) return; - timer_time = timer_time - 5; - } - draw(); - }); -} -setTimeout(() => { - initTouchTimer (); -}); - -function getTimerTime() { - // if timer_time !== -1, take it - if (timer_time !== 0) { - return timer_time + "m"; - } else { - // else, show diff between njsontime and now - var njson = getDataJson(); - if (!njson) return false; - var now = new Date().getTime(); - var diff = Math.round((njson.timer - now) / (1000 * 60)); - //console.log(123, njson, diff, now, njson.timer - now); - if (diff > 0) return diff + "m"; - else if (njson.timer) { - Bangle.buzz(1000, 1); - console.log("END OF TIMER"); - delete njson.timer; - setDataJson(njson); - return false; - } else { - return false; - } - // if diff is <0, delete timer from json - } -} -function drawTimer() { - //g.drawString(getTimerTime(), 100, 100); - g.setFont("8x12", 2); - var t = 97; - var l = 105; - var time = getTimerTime(); - if (time || timer_time !== 0) g.drawString(time, l+5, t+0); - if (time && timer_time === 0) g.drawImage(getClockBg(), l-20, t+2, { scale: 1 }); -} - - -//////////////////////////////////////////// -// DATA READING -// -function getDataJson(){ - var res = {"tasks":"", "weather":[]}; - try { - res = storage.readJSON('advcasio.data.json'); - } catch(ex) { - return res; - } - return res; -} -function setDataJson(resJson){ - try { - res = storage.writeJSON('advcasio.data.json', resJson); - } catch(ex) { - return res; - } - return res; -} -var dataJson = getDataJson(); - -//////////////////////////////////////////// -// WEATHER! -// -function drawWeather(arr) { - g.setFont("6x8", 1); - var p = {l: 8, tText: 40, tIcon:20, decal:25}; - var today = new Date().getTime(); - var yesterday = today - (1000 * 60 * 60 * 24); - var testday = today + (1000 * 60 * 60 * 24 * 2); - //12h auj > 12h hier qui est sup a 0h auj - //23h59 hier est sup a 0h auj - var j = 0; - for(var i = 0; i yesterday && j < 4) { - g.drawString(arr[i][0], p.l + p.decal*j + 4, p.tText); - g.drawImage(iconsWeather[arr[i][1]], p.l + p.decal*j, p.tIcon, { scale: 1 }); - j++ - } - } -} - - -//////////////////////////////////////////// -// DRAWING FUNCS -// -function drawTasks(str) { - g.setFont("6x8", 1); - var t = 57; - var l = 0; - g.drawString(str, l+5, t+0); -} - -function drawSteps() { - g.setFont("8x12", 2); - var t = 132; - var l = 150; - g.drawString(getSteps(), l+5, t+0); -} - - function drawClock() { - g.setFont("7x11Numeric7Seg", 3); - g.clearRect(80, 57, 170, 96); - g.setColor(255, 255, 255); - var l = 77; - var t = 57; - var w = 170; - var h = 116; - g.drawRect(l, t, w, h); - g.fillRect(l, t, w, h); - g.setColor(0, 0, 0); - g.drawString(require("locale").time(new Date(), 1), 76, 60); - - // day - //g.setFont("8x12", 1); - //g.setFont("9x18", 1); - //g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 25, 136); - g.setFont("8x12", 2); - g.drawString(require("locale").dow(new Date(), 2), 18, 130); - - // month - g.setFont("8x12"); - g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 127); - - // day nb - g.setFont("8x12", 2); - const time = new Date().getDate(); - g.drawString(time < 10 ? "0" + time : time, 78, 137); + g.setFont("7x11Numeric7Seg", 3); + g.clearRect(80, 57, 170, 96); + g.setColor(0, 255, 255); + g.drawRect(80, 57, 170, 96); + g.fillRect(80, 57, 170, 96); + g.setColor(0, 0, 0); + g.drawString(require("locale").time(new Date(), 1), 70, 60); + g.setFont("8x12", 2); + g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 18, 130); + g.setFont("8x12"); + g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 126); + g.setFont("8x12", 2); + const time = new Date().getDate(); + g.drawString(time < 10 ? "0" + time : time, 78, 137); } function drawBattery() { - bigThenSmall(E.getBattery(), "%", 140, 23); + bigThenSmall(E.getBattery(), "%", 135, 21); } +function drawRocket() { + let Rocket = getRocketSequences(); + g.clearRect(5, 62, 63, 115); + g.setColor(0, 255, 255); + g.drawRect(5, 62, 63, 115); + g.fillRect(5, 62, 63, 115); + g.drawImage(Rocket[rocketSequence], 5, 65, { scale: 0.7 }); + g.setColor(0, 0, 0); + rocketSequence = rocketSequence + 1; + if(rocketSequence > 8) rocketSequence = 1; +} + +function getTemperature(){ + try { + var weatherJson = storage.readJSON('weather.json'); + var weather = weatherJson.weather; + return Math.round(weather.temp-273.15); + + } catch(ex) { + print(ex) + return "?" + } +} function getSteps() { - var steps = 0; - try{ - if (WIDGETS.wpedom !== undefined) { - steps = WIDGETS.wpedom.getSteps(); - } else if (WIDGETS.activepedom !== undefined) { - steps = WIDGETS.activepedom.getSteps(); - } else { - steps = Bangle.getHealthStatus("day").steps; - } - } catch(ex) { - // In case we failed, we can only show 0 steps. - return "? k"; - } - - steps = Math.round(steps/1000); - return steps + "k"; + var steps = Bangle.getHealthStatus("day").steps; + steps = Math.round(steps/1000); + return steps + "k"; } - function draw() { - - queueDraw(); + queueDraw(); - g.reset(); - g.clear(); - g.setColor(255, 255, 255); - g.fillRect(0, 0, g.getWidth(), g.getHeight()); - let background = getBackgroundImage(); - g.drawImage(background, 0, 0, { scale: 1 }); - - - g.setColor(0, 0, 0); - if(dataJson && dataJson.weather) drawWeather(dataJson.weather); - if(dataJson && dataJson.tasks) drawTasks(dataJson.tasks); - + g.clear(1); + g.setColor(0, 255, 255); + g.fillRect(0, 0, g.getWidth(), g.getHeight()); + let background = getBackgroundImage(); + g.drawImage(background, 0, 0, { scale: 1 }); + g.setColor(0, 0, 0); + g.setFont("6x12"); + g.drawString("Launching Process", 30, 20); + g.setFont("8x12"); + g.drawString("ACTIVATE", 40, 35); - g.setFontAlign(0,-1); - g.setFont("8x12", 2); + g.setFontAlign(0,-1); + g.setFont("8x12", 2); + g.drawString(getTemperature(), 155, 132); + g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), 109, 98); + g.drawString(getSteps(), 158, 98); - drawSteps(); - g.setFontAlign(-1,-1); - drawClock(); - drawBattery(); - drawTimer(); - // Hide widgets - for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} + g.setFontAlign(-1,-1); + drawClock(); + drawRocket(); + drawBattery(); + + // Hide widgets + for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} } -// save batt power, does not seem to work although... -var canTouch = true; Bangle.on("lcdPower", (on) => { - if (on) { - draw(); - } else { - canTouch = false; - clearIntervals(); - } + if (on) { + draw(); + } else { + clearIntervals(); + } }); Bangle.on("lock", (locked) => { - clearIntervals(); - draw(); - if (!locked) { - canTouch = true; - } else { - canTouch = false; - } + clearIntervals(); + draw(); + if (!locked) { + rocketInterval = setInterval(drawRocket, rocketSpeed); + } }); - Bangle.setUI("clock"); // Load widgets, but don't show them Bangle.loadWidgets(); - -g.reset(); -g.clear(); +require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe +g.clear(1); draw(); diff --git a/apps/advcasio/metadata.json b/apps/advcasio/metadata.json index 152f47132..25dc1243a 100644 --- a/apps/advcasio/metadata.json +++ b/apps/advcasio/metadata.json @@ -1,7 +1,7 @@ { "id": "advcasio", "name": "Advanced Casio Clock", "shortName":"advcasio", - "version":"0.03", + "version":"0.04", "description": "An over-engineered clock inspired by Casio watches. It has a 4 days weather, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.", "icon": "app.png", "tags": "clock", @@ -12,7 +12,7 @@ { "url": "screenshot-clock-3.jpg" }, { "url": "screenshot-webapp.jpg" } ], - "supports" : ["BANGLEJS", "BANGLEJS2"], + "supports" : ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "allow_emulator":true, "storage": [ diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json index 6d91455f0..4436e143c 100644 --- a/apps/agenda/metadata.json +++ b/apps/agenda/metadata.json @@ -5,7 +5,7 @@ "description": "Simple agenda", "icon": "agenda.png", "screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}], - "tags": "agenda", + "tags": "agenda,clkinfo", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "allow_emulator": true, diff --git a/apps/agpsdata/ChangeLog b/apps/agpsdata/ChangeLog index 89844a132..8ada244d7 100644 --- a/apps/agpsdata/ChangeLog +++ b/apps/agpsdata/ChangeLog @@ -2,3 +2,4 @@ 0.02: Load AGPS data on app start and automatically in background 0.03: Do not load AGPS data on boot Increase minimum interval to 6 hours +0.04: Write AGPS data chunks with delay to improve reliability diff --git a/apps/agpsdata/app.js b/apps/agpsdata/app.js index 647723bb4..4a6d2ba5c 100644 --- a/apps/agpsdata/app.js +++ b/apps/agpsdata/app.js @@ -36,7 +36,7 @@ function updateAgps() { g.clear(); if (!waiting) { waiting = true; - display("Updating A-GPS..."); + display("Updating A-GPS...", "takes ~ 10 seconds"); require("agpsdata").pull(function() { waiting = false; display("A-GPS updated.", "touch to close"); diff --git a/apps/agpsdata/lib.js b/apps/agpsdata/lib.js index 7d9758c0a..34608a5c6 100644 --- a/apps/agpsdata/lib.js +++ b/apps/agpsdata/lib.js @@ -8,41 +8,52 @@ var FILE = "agpsdata.settings.json"; var settings; readSettings(); -function setAGPS(data) { - var js = jsFromBase64(data); - try { - eval(js); - return true; - } - catch(e) { - console.log("error:", e); - } - return false; +function setAGPS(b64) { + return new Promise(function(resolve, reject) { + var initCommands = "Bangle.setGPSPower(1);\n"; // turn GPS on + const gnsstype = settings.gnsstype || 1; // default GPS + initCommands += `Serial1.println("${CASIC_CHECKSUM("$PCAS04," + gnsstype)}")\n`; // set GNSS mode + // What about: + // NAV-TIMEUTC (0x01 0x10) + // NAV-PV (0x01 0x03) + // or AGPS.zip uses AID-INI (0x0B 0x01) + + eval(initCommands); + + try { + writeChunks(atob(b64), resolve); + } catch (e) { + console.log("error:", e); + reject(); + } + }); } -function jsFromBase64(b64) { - var bin = atob(b64); - var chunkSize = 128; - var js = "Bangle.setGPSPower(1);\n"; // turn GPS on - var gnsstype = settings.gnsstype || 1; // default GPS - js += `Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnsstype)}")\n`; // set GNSS mode - // What about: - // NAV-TIMEUTC (0x01 0x10) - // NAV-PV (0x01 0x03) - // or AGPS.zip uses AID-INI (0x0B 0x01) +var chunkI = 0; +function writeChunks(bin, resolve) { + return new Promise(function(resolve2) { + const chunkSize = 128; + setTimeout(function() { + if (chunkI < bin.length) { + var chunk = bin.substr(chunkI, chunkSize); + js = `Serial1.write(atob("${btoa(chunk)}"))\n`; + eval(js); - for (var i=0;i { - let result = setAGPS(event.resp); - if (result) { - updateLastUpdate(); - if (successCallback) successCallback(); - } else { - console.log("error applying AGPS data"); - if (failureCallback) failureCallback("Error applying AGPS data"); - } - }).catch((e)=>{ - console.log("error", e); - if (failureCallback) failureCallback(e); - }); + const uri = "https://www.espruino.com/agps/casic.base64"; + if (Bangle.http) { + Bangle.http(uri, {timeout : 10000}) + .then(event => { + setAGPS(event.resp) + .then(r => { + updateLastUpdate(); + if (successCallback) + successCallback(); + }) + .catch((e) => { + console.log("error", e); + if (failureCallback) + failureCallback(e); + }); + }) + .catch((e) => { + console.log("error", e); + if (failureCallback) + failureCallback(e); + }); } else { console.log("error: No http method found"); - if (failureCallback) failureCallback(/*LANG*/"No http method"); + if (failureCallback) + failureCallback(/*LANG*/ "No http method"); } }; diff --git a/apps/agpsdata/metadata.json b/apps/agpsdata/metadata.json index 1ce299532..203a00f72 100644 --- a/apps/agpsdata/metadata.json +++ b/apps/agpsdata/metadata.json @@ -2,7 +2,7 @@ "name": "A-GPS Data Downloader App", "shortName":"A-GPS Data", "icon": "agpsdata.png", - "version":"0.03", + "version":"0.04", "description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.", "tags": "boot,tool,assisted,gps,agps,http", "allow_emulator":true, diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index a65326941..fcb139c94 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -14,3 +14,4 @@ 0.14: Fix timeout of http function not being cleaned up 0.15: Allow method/body/headers to be specified for `http` (needs Gadgetbridge 0.68.0b or later) 0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152) +0.17: Now kick off Calendar sync as soon as connected to Gadgetbridge diff --git a/apps/android/boot.js b/apps/android/boot.js index 0d1edae99..d4f50d501 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -91,10 +91,6 @@ sched.reload(); }, //TODO perhaps move those in a library (like messages), used also for viewing events? - //simple package with events all together - "calendarevents" : function() { - require("Storage").writeJSON("android.calendar.json", event.events); - }, //add and remove events based on activity on phone (pebble-like) "calendar" : function() { var cal = require("Storage").readJSON("android.calendar.json",true); @@ -109,7 +105,7 @@ "calendar-" : function() { var cal = require("Storage").readJSON("android.calendar.json",true); //if any of those happen we are out of sync! - if (!cal || !Array.isArray(cal)) return; + if (!cal || !Array.isArray(cal)) cal = []; cal = cal.filter(e=>e.id!=event.id); require("Storage").writeJSON("android.calendar.json", cal); }, @@ -170,7 +166,10 @@ // Battery monitor function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); } - NRF.on("connect", () => setTimeout(sendBattery, 2000)); + NRF.on("connect", () => setTimeout(function() { + sendBattery(); + GB({t:"force_calendar_sync_start"}); // send a list of our calendar entries to start off the sync process + }, 2000)); Bangle.on("charging", sendBattery); if (!settings.keep) NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect diff --git a/apps/android/metadata.json b/apps/android/metadata.json index ab340340c..ac47449d6 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.16", + "version": "0.17", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", diff --git a/apps/aptsciclk/metadata.json b/apps/aptsciclk/metadata.json index c450d926e..77e40f843 100644 --- a/apps/aptsciclk/metadata.json +++ b/apps/aptsciclk/metadata.json @@ -5,6 +5,7 @@ "version": "0.08", "description": "A clock based on the portal series", "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS2"], diff --git a/apps/aptsciclk/screenshot.png b/apps/aptsciclk/screenshot.png new file mode 100644 index 000000000..4803e4b13 Binary files /dev/null and b/apps/aptsciclk/screenshot.png differ diff --git a/apps/barwatch/ChangeLog b/apps/barwatch/ChangeLog new file mode 100644 index 000000000..7f837e50e --- /dev/null +++ b/apps/barwatch/ChangeLog @@ -0,0 +1 @@ +0.01: First version diff --git a/apps/barwatch/README.md b/apps/barwatch/README.md new file mode 100644 index 000000000..c37caa6e4 --- /dev/null +++ b/apps/barwatch/README.md @@ -0,0 +1,5 @@ +# BarWatch - an experimental watch + +For too long the watches have shown the time with digits or hands. No more! +With this stylish watch the time is represented by bars. Up to 24 as the day goes by. +Practical? Not really, but a different look! \ No newline at end of file diff --git a/apps/barwatch/app-icon.js b/apps/barwatch/app-icon.js new file mode 100644 index 000000000..82416ee28 --- /dev/null +++ b/apps/barwatch/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("l0uwkE/4A/AH4A/AB0gicQmUB+EPgEigExh8gj8A+ECAgMQn4WCgcACyotWC34W/C34W/CycACw0wgYWFBYIWCAAc/+YGHCAgNFACkxl8hGYwAMLYUvCykQC34WycoIW/C34W0gAWTmUjkUzkbmSAFY=")) \ No newline at end of file diff --git a/apps/barwatch/app.js b/apps/barwatch/app.js new file mode 100644 index 000000000..e0ed15ce6 --- /dev/null +++ b/apps/barwatch/app.js @@ -0,0 +1,76 @@ +// timeout used to update every minute +var drawTimeout; + +// 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() { + g.reset(); + + if(g.theme.dark){ + g.setColor(1,1,1); + }else{ + g.setColor(0,0,0); + } + + // work out how to display the current time + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(); + + // hour bars + var bx_offset = 10, by_offset = 35; + var b_width = 8, b_height = 60; + var b_space = 5; + + for(var i=0; i 11){ + by_offset = 105; + } + var iter = i % 12; + //console.log(iter); + g.fillRect(bx_offset+(b_width*(iter+1))+(b_space*iter), + by_offset, + bx_offset+(b_width*iter)+(b_space*iter), + by_offset+b_height); + } + + // minute bar + if(h > 11){ + by_offset = 105; + } + var m_bar = h % 12; + if(m != 0){ + g.fillRect(bx_offset+(b_width*(m_bar+1))+(b_space*m_bar), + by_offset+b_height-m, + bx_offset+(b_width*m_bar)+(b_space*m_bar), + by_offset+b_height); + } + + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first +draw(); +// 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.setUI("clock"); +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/barwatch/app.png b/apps/barwatch/app.png new file mode 100644 index 000000000..134de9424 Binary files /dev/null and b/apps/barwatch/app.png differ diff --git a/apps/barwatch/metadata.json b/apps/barwatch/metadata.json new file mode 100644 index 000000000..adcd44107 --- /dev/null +++ b/apps/barwatch/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "barwatch", + "name": "BarWatch", + "shortName":"BarWatch", + "version":"0.01", + "description": "A watch that displays the time using bars. One bar for each hour.", + "readme": "README.md", + "icon": "screenshot.png", + "tags": "clock", + "type": "clock", + "allow_emulator":true, + "screenshots" : [ { "url": "screenshot.png" } ], + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"barwatch.app.js","url":"app.js"}, + {"name":"barwatch.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/barwatch/screenshot.png b/apps/barwatch/screenshot.png new file mode 100644 index 000000000..305138252 Binary files /dev/null and b/apps/barwatch/screenshot.png differ diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 7b95d8686..0eaf517d9 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -59,3 +59,5 @@ Add patch to ensure that compass heading is corrected on pre-2v15.68 firmware Ensure clock is only fast-loaded if it doesn't contain widgets 0.52: Ensure heading patch for pre-2v15.68 firmware applies to getCompass +0.53: Add polyfills for pre-2v15.135 firmware for Bangle.load and Bangle.showClock +0.54: Fix for invalid version comparison in polyfill diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index e9a24f5f5..ad58437ec 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -4,6 +4,7 @@ of the time. */ E.showMessage(/*LANG*/"Updating boot0..."); var s = require('Storage').readJSON('setting.json',1)||{}; var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2 +var FWVERSION = parseFloat(process.env.VERSION.replace("v","").replace(/\.(\d\d)$/,".0$1")); var boot = "", bootPost = ""; if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT); @@ -77,11 +78,17 @@ if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passke if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`; if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation // ================================================== FIXING OLDER FIRMWARES -// 2v15.68 and before had compass heading inverted. -if (process.version.replace("v","")<215.68) +if (FWVERSION<215.068) // 2v15.68 and before had compass heading inverted. boot += `Bangle.on('mag',e=>{if(!isNaN(e.heading))e.heading=360-e.heading;}); Bangle.getCompass=(c=>(()=>{e=c();if(!isNaN(e.heading))e.heading=360-e.heading;return e;}))(Bangle.getCompass);`; +// deleting stops us getting confused by our own decl. builtins can't be deleted +// this is a polyfill without fastloading capability +delete Bangle.showClock; +if (!Bangle.showClock) boot += `Bangle.showClock = ()=>{load(".bootcde")};\n`; +delete Bangle.load; +if (!Bangle.load) boot += `Bangle.load = load;\n`; + // ================================================== BOOT.JS // Append *.boot.js files // These could change bleServices/bleServiceOptions if needed diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json index 339f8503e..bd39beb7f 100644 --- a/apps/boot/metadata.json +++ b/apps/boot/metadata.json @@ -1,7 +1,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.52", + "version": "0.54", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog index a70ae3f8a..99cf0c670 100644 --- a/apps/bthrm/ChangeLog +++ b/apps/bthrm/ChangeLog @@ -30,3 +30,13 @@ Allow recording unmodified internal HR Better connection retry handling 0.13: Less time used during boot if disabled +0.14: Allow bonding (Debug menu) + Prevent mixing of BT and internal HRM events if both are enabled + Always use a grace period (default 0 ms) to decouple some connection steps + Device not found errors now utilize increasing timeouts +0.15: Fix recording internal sensor + Handle fallback to internal sensor consistently if BT bpm is 0 + Power internal sensor down if not needed for fallback +0.16: Set powerdownRequested correctly on BTHRM power on + Additional logging on errors + Add debug option for disabling active scanning diff --git a/apps/bthrm/default.json b/apps/bthrm/default.json index fb284bcd2..79605b412 100644 --- a/apps/bthrm/default.json +++ b/apps/bthrm/default.json @@ -16,5 +16,7 @@ "gracePeriodNotification": 0, "gracePeriodConnect": 0, "gracePeriodService": 0, - "gracePeriodRequest": 0 + "gracePeriodRequest": 0, + "bonding": false, + "active": true } diff --git a/apps/bthrm/lib.js b/apps/bthrm/lib.js index 9e2f0fe63..f5e0e1e5b 100644 --- a/apps/bthrm/lib.js +++ b/apps/bthrm/lib.js @@ -106,9 +106,10 @@ exports.enable = () => { var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit supportedCharacteristics["0x2a37"].active = bpm > 0; log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active); - if (supportedCharacteristics["0x2a37"].active) stopFallback(); + switchFallback(); if (bpmTimeout) clearTimeout(bpmTimeout); bpmTimeout = setTimeout(()=>{ + bpmTimeout = undefined; supportedCharacteristics["0x2a37"].active = false; startFallback(); }, 3000); @@ -147,15 +148,15 @@ exports.enable = () => { battery = lastReceivedData["0x180f"]["0x2a19"]; } - if (settings.replace){ + if (settings.replace && bpm > 0){ var repEvent = { bpm: bpm, confidence: (sensorContact || sensorContact === undefined)? 100 : 0, src: "bthrm" }; - log("Emitting HRM", repEvent); - Bangle.emit("HRM_int", repEvent); + log("Emitting HRM_R(bt)", repEvent); + Bangle.emit("HRM_R", repEvent); } var newEvent = { @@ -254,7 +255,7 @@ exports.enable = () => { var retry = function() { log("Retry"); - if (!currentRetryTimeout){ + if (!currentRetryTimeout && !powerdownRequested){ var clampedTime = retryTime < 100 ? 100 : retryTime; @@ -280,9 +281,13 @@ exports.enable = () => { log("Disconnect: " + reason); log("GATT", gatt); log("Characteristics", characteristics); - clearRetryTimeout(reason != "Connection Timeout"); + + var retryTimeResetNeeded = true; + retryTimeResetNeeded &= reason != "Connection Timeout"; + retryTimeResetNeeded &= reason != "No device found matching filters"; + clearRetryTimeout(retryTimeResetNeeded); supportedCharacteristics["0x2a37"].active = false; - startFallback(); + if (!powerdownRequested) startFallback(); blockInit = false; if (settings.warnDisconnect && !buzzing){ buzzing = true; @@ -312,13 +317,13 @@ exports.enable = () => { result = result.then(()=>{ log("Starting notifications", newCharacteristic); var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic)); - if (settings.gracePeriodNotification > 0){ - log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications"); - startPromise = startPromise.then(()=>{ - log("Wait after connect"); - return waitingPromise(settings.gracePeriodNotification); - }); - } + + log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications"); + startPromise = startPromise.then(()=>{ + log("Wait after connect"); + return waitingPromise(settings.gracePeriodNotification); + }); + return startPromise; }); } @@ -364,7 +369,7 @@ exports.enable = () => { var initBt = function () { log("initBt with blockInit: " + blockInit); - if (blockInit){ + if (blockInit && !powerdownRequested){ retry(); return; } @@ -382,8 +387,14 @@ exports.enable = () => { return; } log("Requesting device with filters", filters); - promise = NRF.requestDevice({ filters: filters, active: true }); - + try { + promise = NRF.requestDevice({ filters: filters, active: settings.active }); + } catch (e){ + log("Error during initial request:", e); + onDisconnect(e); + return; + } + if (settings.gracePeriodRequest){ log("Add " + settings.gracePeriodRequest + "ms grace period after request"); } @@ -429,30 +440,30 @@ exports.enable = () => { var connectPromise = gatt.connect(connectSettings).then(function() { log("Connected."); }); - if (settings.gracePeriodConnect > 0){ - log("Add " + settings.gracePeriodConnect + "ms grace period after connecting"); - connectPromise = connectPromise.then(()=>{ - log("Wait after connect"); - return waitingPromise(settings.gracePeriodConnect); - }); - } + log("Add " + settings.gracePeriodConnect + "ms grace period after connecting"); + connectPromise = connectPromise.then(()=>{ + log("Wait after connect"); + return waitingPromise(settings.gracePeriodConnect); + }); return connectPromise; } else { return Promise.resolve(); } }); - -/* promise = promise.then(() => { - log(JSON.stringify(gatt.getSecurityStatus())); - if (gatt.getSecurityStatus()['bonded']) { - log("Already bonded"); - return Promise.resolve(); - } else { - log("Start bonding"); - return gatt.startBonding() - .then(() => console.log(gatt.getSecurityStatus())); - } - });*/ + + if (settings.bonding){ + promise = promise.then(() => { + log(JSON.stringify(gatt.getSecurityStatus())); + if (gatt.getSecurityStatus()['bonded']) { + log("Already bonded"); + return Promise.resolve(); + } else { + log("Start bonding"); + return gatt.startBonding() + .then(() => log("Security status" + gatt.getSecurityStatus())); + } + }); + } promise = promise.then(()=>{ if (!characteristics || characteristics.length === 0){ @@ -476,13 +487,11 @@ exports.enable = () => { log("Supporting service", service.uuid); result = attachServicePromise(result, service); } - if (settings.gracePeriodService > 0) { - log("Add " + settings.gracePeriodService + "ms grace period after services"); - result = result.then(()=>{ - log("Wait after services"); - return waitingPromise(settings.gracePeriodService); - }); - } + log("Add " + settings.gracePeriodService + "ms grace period after services"); + result = result.then(()=>{ + log("Wait after services"); + return waitingPromise(settings.gracePeriodService); + }); return result; }); } else { @@ -505,6 +514,8 @@ exports.enable = () => { }); }; + var powerdownRequested = false; + Bangle.setBTHRMPower = function(isOn, app) { // Do app power handling if (!app) app="?"; @@ -515,11 +526,14 @@ exports.enable = () => { isOn = Bangle._PWR.BTHRM.length; // so now we know if we're really on if (isOn) { + powerdownRequested = false; switchFallback(); if (!Bangle.isBTHRMConnected()) initBt(); } else { // not on log("Power off for " + app); + powerdownRequested = true; clearRetryTimeout(true); + stopFallback(); if (gatt) { if (gatt.connected){ log("Disconnect with gatt", gatt); @@ -538,39 +552,44 @@ exports.enable = () => { }; if (settings.replace){ + // register a listener for original HRM events and emit as HRM_int Bangle.on("HRM", (e) => { e.modified = true; + log("Emitting HRM_int", e); Bangle.emit("HRM_int", e); + if (fallbackActive){ + // if fallback to internal HRM is active, emit as HRM_R to which everyone listens + log("Emitting HRM_R(int)", e); + Bangle.emit("HRM_R", e); + } }); - Bangle.origOn = Bangle.on; - Bangle.on = function(name, callback) { - if (name == "HRM") { - Bangle.origOn("HRM_int", callback); - } else { - Bangle.origOn(name, callback); - } - }; - - Bangle.origRemoveListener = Bangle.removeListener; - Bangle.removeListener = function(name, callback) { - if (name == "HRM") { - Bangle.origRemoveListener("HRM_int", callback); - } else { - Bangle.origRemoveListener(name, callback); - } - }; + // force all apps wanting to listen to HRM to actually get events for HRM_R + Bangle.on = ( o => (name, cb) => { + o = o.bind(Bangle); + if (name == "HRM") o("HRM_R", cb); + else o(name, cb); + })(Bangle.on); + Bangle.removeListener = ( o => (name, cb) => { + o = o.bind(Bangle); + if (name == "HRM") o("HRM_R", cb); + else o(name, cb); + })(Bangle.removeListener); } Bangle.origSetHRMPower = Bangle.setHRMPower; if (settings.startWithHrm){ - Bangle.setHRMPower = function(isOn, app) { log("setHRMPower for " + app + ": " + (isOn?"on":"off")); if (settings.enabled){ Bangle.setBTHRMPower(isOn, app); + if (Bangle._PWR && Bangle._PWR.HRM && Object.keys(Bangle._PWR.HRM).length == 0) { + Bangle._PWR.BTHRM = []; + Bangle.setBTHRMPower(0); + if (!isOn) stopFallback(); + } } if ((settings.enabled && !settings.replace) || !settings.enabled){ Bangle.origSetHRMPower(isOn, app); @@ -626,7 +645,11 @@ exports.enable = () => { E.on("kill", ()=>{ if (gatt && gatt.connected){ log("Got killed, trying to disconnect"); - gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e)); + try { + gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect promise on kill", e)); + } catch (e) { + log("Error during disconnnect on kill", e) + } } }); } diff --git a/apps/bthrm/metadata.json b/apps/bthrm/metadata.json index 7eedd223c..18c34ea33 100644 --- a/apps/bthrm/metadata.json +++ b/apps/bthrm/metadata.json @@ -2,7 +2,7 @@ "id": "bthrm", "name": "Bluetooth Heart Rate Monitor", "shortName": "BT HRM", - "version": "0.13", + "version": "0.16", "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", "icon": "app.png", "type": "app", diff --git a/apps/bthrm/recorder.js b/apps/bthrm/recorder.js index ed36b5aef..fcfed47c3 100644 --- a/apps/bthrm/recorder.js +++ b/apps/bthrm/recorder.js @@ -38,35 +38,32 @@ recorders.hrmint = function() { var active = false; var bpmTimeout; - var bpm = "", bpmConfidence = "", src=""; + var bpm = "", bpmConfidence = ""; function onHRM(h) { bpmConfidence = h.confidence; bpm = h.bpm; - srv = h.src; if (h.bpm > 0){ active = true; - print("active" + h.bpm); if (bpmTimeout) clearTimeout(bpmTimeout); bpmTimeout = setTimeout(()=>{ - print("inactive"); active = false; },3000); } } return { name : "HR int", - fields : ["Heartrate", "Confidence"], + fields : ["Int Heartrate", "Int Confidence"], getValues : () => { - var r = [bpm,bpmConfidence,src]; - bpm = ""; bpmConfidence = ""; src=""; + var r = [bpm,bpmConfidence]; + bpm = ""; bpmConfidence = ""; return r; }, start : () => { - Bangle.origOn('HRM', onHRM); + Bangle.on('HRM_int', onHRM); if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(1,"recorder"); }, stop : () => { - Bangle.removeListener('HRM', onHRM); + Bangle.removeListener('HRM_int', onHRM); if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(0,"recorder"); }, draw : (x,y) => g.setColor(( Bangle.origIsHRMOn && Bangle.origIsHRMOn() && active)?"#0f0":"#8f8").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y) diff --git a/apps/bthrm/settings.js b/apps/bthrm/settings.js index 2b19ea46a..459ed29fc 100644 --- a/apps/bthrm/settings.js +++ b/apps/bthrm/settings.js @@ -96,6 +96,18 @@ writeSettings("debuglog",v); } }, + 'Use bonding': { + value: !!settings.bonding, + onchange: v => { + writeSettings("bonding",v); + } + }, + 'Use active scanning': { + value: !!settings.active, + onchange: v => { + writeSettings("active",v); + } + }, 'Grace periods': function() { E.showMenu(submenu_grace); } }; diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json index fbae0e1e7..fa0f7b01f 100644 --- a/apps/bwclk/metadata.json +++ b/apps/bwclk/metadata.json @@ -7,7 +7,7 @@ "icon": "app.png", "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}], "type": "clock", - "tags": "clock", + "tags": "clock,clkinfo", "supports": ["BANGLEJS2"], "allow_emulator": true, "storage": [ diff --git a/apps/calculator/ChangeLog b/apps/calculator/ChangeLog index f3ce3a928..2e1ace7bf 100644 --- a/apps/calculator/ChangeLog +++ b/apps/calculator/ChangeLog @@ -4,3 +4,4 @@ 0.04: Display current operation on LHS 0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2) 0.06: Bangle.js 2: Exit with a short press of the physical button +0.07: Bangle.js 2: Exit by pressing upper left corner of the screen diff --git a/apps/calculator/README.md b/apps/calculator/README.md index b25d355bf..62f6cef24 100644 --- a/apps/calculator/README.md +++ b/apps/calculator/README.md @@ -12,12 +12,20 @@ Basic calculator reminiscent of MacOs's one. Handy for small calculus. ## Controls +Bangle.js 1 - UP: BTN1 - DOWN: BTN3 - LEFT: BTN4 - RIGHT: BTN5 - SELECT: BTN2 +Bangle.js 2 +- Swipes to change visible buttons +- Click physical button to exit +- Press upper left corner of screen to exit (where the red back button would be) ## Creator + +## Contributors +[thyttan](https://github.com/thyttan) diff --git a/apps/calculator/app.js b/apps/calculator/app.js index 571a5b27f..d9a89a989 100644 --- a/apps/calculator/app.js +++ b/apps/calculator/app.js @@ -3,6 +3,8 @@ * * Original Author: Frederic Rousseau https://github.com/fredericrous * Created: April 2020 + * + * Contributors: thyttan https://github.com/thyttan */ g.clear(); @@ -402,38 +404,42 @@ if (process.env.HWVERSION==1) { swipeEnabled = false; drawGlobal(); } else { // touchscreen? - setWatch(_ => {load();}, BTN1, {edge:'falling'}); // Exit with a short press to physical button - selected = "NONE"; + selected = "NONE"; swipeEnabled = true; prepareScreen(numbers, numbersGrid, COLORS.DEFAULT); prepareScreen(operators, operatorsGrid, COLORS.OPERATOR); prepareScreen(specials, specialsGrid, COLORS.SPECIAL); drawNumbers(); - Bangle.on('touch',(n,e)=>{ - for (var key in screen) { - if (typeof screen[key] == "undefined") break; - var r = screen[key].xy; - if (e.x>=r[0] && e.y>=r[1] && - e.x{ + for (var key in screen) { + if (typeof screen[key] == "undefined") break; + var r = screen[key].xy; + if (e.x>=r[0] && e.y>=r[1] && e.x { + if (LR == 1) { // right + drawSpecials(); + } + if (LR == -1) { // left + drawOperators(); + } + if (UD == 1) { // down + drawNumbers(); + } + if (UD == -1) { // up + drawNumbers(); } } }); - Bangle.on('swipe', (LR, UD) => { - if (LR == 1) { // right - drawSpecials(); - } - if (LR == -1) { // left - drawOperators(); - } - if (UD == 1) { // down - drawNumbers(); - } - if (UD == -1) { // up - drawNumbers(); - } - }); + } displayOutput(0); diff --git a/apps/calculator/metadata.json b/apps/calculator/metadata.json index 536a6955e..1674b7843 100644 --- a/apps/calculator/metadata.json +++ b/apps/calculator/metadata.json @@ -2,7 +2,7 @@ "id": "calculator", "name": "Calculator", "shortName": "Calculator", - "version": "0.06", + "version": "0.07", "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.", "icon": "calculator.png", "screenshots": [{"url":"screenshot_calculator.png"}], diff --git a/apps/cassioWatch/ChangeLog b/apps/cassioWatch/ChangeLog index 883bd2585..1180554ff 100644 --- a/apps/cassioWatch/ChangeLog +++ b/apps/cassioWatch/ChangeLog @@ -10,3 +10,4 @@ 0.9: Remove ESLint spaces 0.10: Show daily steps, heartrate and the temperature if weather information is available. 0.11: Tell clock widgets to hide. +0.12: Swipe down to see widgets, step counter now just uses getHealthStatus diff --git a/apps/cassioWatch/README.md b/apps/cassioWatch/README.md index aaeb3f122..6c13cdcac 100644 --- a/apps/cassioWatch/README.md +++ b/apps/cassioWatch/README.md @@ -8,4 +8,5 @@ It displays current temperature,day,steps,battery.heartbeat and weather. **To-do**: -Align and change size of some elements. + +* Align and change size of some elements diff --git a/apps/cassioWatch/app.js b/apps/cassioWatch/app.js index 91f6737ad..19dd883d2 100644 --- a/apps/cassioWatch/app.js +++ b/apps/cassioWatch/app.js @@ -91,7 +91,6 @@ function getTemperature(){ var weatherJson = storage.readJSON('weather.json'); var weather = weatherJson.weather; return Math.round(weather.temp-273.15); - } catch(ex) { print(ex) return "?" @@ -99,20 +98,7 @@ function getTemperature(){ } function getSteps() { - var steps = 0; - try{ - if (WIDGETS.wpedom !== undefined) { - steps = WIDGETS.wpedom.getSteps(); - } else if (WIDGETS.activepedom !== undefined) { - steps = WIDGETS.activepedom.getSteps(); - } else { - steps = Bangle.getHealthStatus("day").steps; - } - } catch(ex) { - // In case we failed, we can only show 0 steps. - return "? k"; - } - + var steps = Bangle.getHealthStatus("day").steps; steps = Math.round(steps/1000); return steps + "k"; } @@ -121,8 +107,7 @@ function getSteps() { function draw() { queueDraw(); - g.reset(); - g.clear(); + g.clear(1); g.setColor(0, 255, 255); g.fillRect(0, 0, g.getWidth(), g.getHeight()); let background = getBackgroundImage(); @@ -143,9 +128,6 @@ function draw() { drawClock(); drawRocket(); drawBattery(); - - // Hide widgets - for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} } Bangle.on("lcdPower", (on) => { @@ -169,7 +151,6 @@ Bangle.setUI("clock"); // Load widgets, but don't show them Bangle.loadWidgets(); - -g.reset(); -g.clear(); +require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe +g.clear(1); draw(); diff --git a/apps/cassioWatch/metadata.json b/apps/cassioWatch/metadata.json index abfee9b93..5ac4502fd 100644 --- a/apps/cassioWatch/metadata.json +++ b/apps/cassioWatch/metadata.json @@ -4,7 +4,7 @@ "description": "Animated Clock with Space Cassio Watch Style", "screenshots": [{ "url": "screens/screen_night.png" },{ "url": "screens/screen_day.png" }], "icon": "app.png", - "version": "0.11", + "version": "0.12", "type": "clock", "tags": "clock, weather, cassio, retro", "supports": ["BANGLEJS2"], diff --git a/apps/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog index 26e531be7..ea7266442 100644 --- a/apps/circlesclock/ChangeLog +++ b/apps/circlesclock/ChangeLog @@ -30,3 +30,4 @@ 0.15: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16 0.16: Fix const error Use widget_utils if available +0.17: Load circles from clkinfo diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js index 25e34cce0..d4a170ce8 100644 --- a/apps/circlesclock/app.js +++ b/apps/circlesclock/app.js @@ -1,3 +1,4 @@ +let clock_info = require("clock_info"); let locale = require("locale"); let storage = require("Storage"); Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) { @@ -18,6 +19,7 @@ let settings = Object.assign( storage.readJSON(SETTINGS_FILE, true) || {} ); + //TODO deprecate this (and perhaps use in the clkinfo module) // Load step goal from health app and pedometer widget as fallback if (settings.stepGoal == undefined) { let d = storage.readJSON("health.json", true) || {}; @@ -29,7 +31,7 @@ if (settings.stepGoal == undefined) { } } -let timerHrm; +let timerHrm; //TODO deprecate this let drawTimeout; /* @@ -44,10 +46,9 @@ let showWidgets = settings.showWidgets || false; let circleCount = settings.circleCount || 3; let showBigWeather = settings.showBigWeather || false; -let hrtValue; +let hrtValue; //TODO deprecate this let now = Math.round(new Date().getTime() / 1000); - // layout values: let colorFg = g.theme.dark ? '#fff' : '#000'; let colorBg = g.theme.dark ? '#000' : '#fff'; @@ -91,8 +92,20 @@ let circleFontSmall = circleCount == 3 ? "Vector:14" : "Vector:10"; let circleFont = circleCount == 3 ? "Vector:15" : "Vector:11"; let circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12"; let iconOffset = circleCount == 3 ? 6 : 8; -let defaultCircleTypes = ["steps", "hr", "battery", "weather"]; +let defaultCircleTypes = ["Bangle/Steps", "Bangle/HRM", "Bangle/Battery", "weather"]; +let circleInfoNum = [ + 0, // circle1 + 0, // circle2 + 0, // circle3 + 0, // circle4 +]; +let circleItemNum = [ + 0, // circle1 + 1, // circle2 + 2, // circle3 + 3, // circle4 +]; function hideWidgets() { /* @@ -111,7 +124,7 @@ function hideWidgets() { function draw() { g.clear(true); let widgetUtils; - + try { widgetUtils = require("widget_utils"); } catch (e) { @@ -177,6 +190,15 @@ function drawCircle(index) { let w = getCircleXPosition(type); switch (type) { + case "weather": + drawWeather(w); + break; + case "sunprogress": + case "sunProgress": + drawSunProgress(w); + break; + //TODO those are going to be deprecated, keep for backwards compatibility for now + //ideally all data should come from some clkinfo case "steps": drawSteps(w); break; @@ -189,13 +211,6 @@ function drawCircle(index) { case "battery": drawBattery(w); break; - case "weather": - drawWeather(w); - break; - case "sunprogress": - case "sunProgress": - drawSunProgress(w); - break; case "temperature": drawTemperature(w); break; @@ -205,9 +220,12 @@ function drawCircle(index) { case "altitude": drawAltitude(w); break; + //end deprecated case "empty": // we draw nothing here return; + default: + drawClkInfo(index, w); } } @@ -304,6 +322,102 @@ function getImage(graphic, color) { } } +function drawWeather(w) { + if (!w) w = getCircleXPosition("weather"); + let weather = getWeather(); + let tempString = weather ? locale.temp(weather.temp - 273.15) : undefined; + let code = weather ? weather.code : -1; + + drawCircleBackground(w); + + let color = getCircleColor("weather"); + let percent; + let data = settings.weatherCircleData; + switch (data) { + case "humidity": + let humidity = weather ? weather.hum : undefined; + if (humidity >= 0) { + percent = humidity / 100; + drawGauge(w, h3, percent, color); + } + break; + case "wind": + if (weather) { + let wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/); + if (wind[1] >= 0) { + if (wind[2] == "kmh") { + wind[1] = windAsBeaufort(wind[1]); + } + // wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale) + percent = wind[1] / 12; + drawGauge(w, h3, percent, color); + } + } + break; + case "empty": + break; + } + + drawInnerCircleAndTriangle(w); + + writeCircleText(w, tempString ? tempString : "?"); + + if (code > 0) { + let icon = getWeatherIconByCode(code); + if (icon) g.drawImage(getImage(icon, getCircleIconColor("weather", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); + } else { + g.drawString("?", w, h3 + radiusOuter); + } +} + +function drawSunProgress(w) { + if (!w) w = getCircleXPosition("sunprogress"); + let percent = getSunProgress(); + + // sunset icons: + let sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA"); + let sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA"); + + drawCircleBackground(w); + + let color = getCircleColor("sunprogress"); + + drawGauge(w, h3, percent, color); + + drawInnerCircleAndTriangle(w); + + let icon = sunSetDown; + let text = "?"; + let times = getSunData(); + if (times != undefined) { + let sunRise = Math.round(times.sunrise.getTime() / 1000); + let sunSet = Math.round(times.sunset.getTime() / 1000); + if (!isDay()) { + // night + if (now > sunRise) { + // after sunRise + let upcomingSunRise = sunRise + 60 * 60 * 24; + text = formatSeconds(upcomingSunRise - now); + } else { + text = formatSeconds(sunRise - now); + } + icon = sunSetUp; + } else { + // day, approx sunrise tomorrow: + text = formatSeconds(sunSet - now); + icon = sunSetDown; + } + } + + writeCircleText(w, text); + + g.drawImage(getImage(icon, getCircleIconColor("sunprogress", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); +} + +/* + * Deprecated but nice as references for clkinfo + */ + function drawSteps(w) { if (!w) w = getCircleXPosition("steps"); let steps = getSteps(); @@ -406,99 +520,6 @@ function drawBattery(w) { g.drawImage(getImage(powerIcon, getCircleIconColor("battery", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); } - -function drawWeather(w) { - if (!w) w = getCircleXPosition("weather"); - let weather = getWeather(); - let tempString = weather ? locale.temp(weather.temp - 273.15) : undefined; - let code = weather ? weather.code : -1; - - drawCircleBackground(w); - - let color = getCircleColor("weather"); - let percent; - let data = settings.weatherCircleData; - switch (data) { - case "humidity": - let humidity = weather ? weather.hum : undefined; - if (humidity >= 0) { - percent = humidity / 100; - drawGauge(w, h3, percent, color); - } - break; - case "wind": - if (weather) { - let wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/); - if (wind[1] >= 0) { - if (wind[2] == "kmh") { - wind[1] = windAsBeaufort(wind[1]); - } - // wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale) - percent = wind[1] / 12; - drawGauge(w, h3, percent, color); - } - } - break; - case "empty": - break; - } - - drawInnerCircleAndTriangle(w); - - writeCircleText(w, tempString ? tempString : "?"); - - if (code > 0) { - let icon = getWeatherIconByCode(code); - if (icon) g.drawImage(getImage(icon, getCircleIconColor("weather", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); - } else { - g.drawString("?", w, h3 + radiusOuter); - } -} - -function drawSunProgress(w) { - if (!w) w = getCircleXPosition("sunprogress"); - let percent = getSunProgress(); - - // sunset icons: - let sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA"); - let sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA"); - - drawCircleBackground(w); - - let color = getCircleColor("sunprogress"); - - drawGauge(w, h3, percent, color); - - drawInnerCircleAndTriangle(w); - - let icon = sunSetDown; - let text = "?"; - let times = getSunData(); - if (times != undefined) { - let sunRise = Math.round(times.sunrise.getTime() / 1000); - let sunSet = Math.round(times.sunset.getTime() / 1000); - if (!isDay()) { - // night - if (now > sunRise) { - // after sunRise - let upcomingSunRise = sunRise + 60 * 60 * 24; - text = formatSeconds(upcomingSunRise - now); - } else { - text = formatSeconds(sunRise - now); - } - icon = sunSetUp; - } else { - // day, approx sunrise tomorrow: - text = formatSeconds(sunSet - now); - icon = sunSetDown; - } - } - - writeCircleText(w, text); - - g.drawImage(getImage(icon, getCircleIconColor("sunprogress", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); -} - function drawTemperature(w) { if (!w) w = getCircleXPosition("temperature"); @@ -577,6 +598,113 @@ function drawAltitude(w) { }); } +function shortValue(v) { + if (isNaN(v)) return '-'; + if (v <= 999) return v; + if (v >= 1000 && v < 10000) { + v = Math.floor(v / 100) * 100; + return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; + } + if (v >= 10000) { + v = Math.floor(v / 1000) * 1000; + return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; + } +} + +function getSteps() { + if (Bangle.getHealthStatus) { + return Bangle.getHealthStatus("day").steps; + } + if (WIDGETS && WIDGETS.wpedom !== undefined) { + return WIDGETS.wpedom.getSteps(); + } + return 0; +} + +function getPressureValue(type) { + return new Promise((resolve) => { + if (Bangle.getPressure) { + if (!pressureLocked) { + pressureLocked = true; + if (pressureCache && pressureCache[type]) { + resolve(pressureCache[type]); + } + Bangle.getPressure().then(function(d) { + pressureLocked = false; + if (d) { + pressureCache = d; + if (d[type]) { + resolve(d[type]); + } + } + }).catch(() => {}); + } else { + if (pressureCache && pressureCache[type]) { + resolve(pressureCache[type]); + } + } + } + }); +} + +/* + * end deprecated + */ + +var menu = null; +function reloadMenu() { + menu = clock_info.load(); + for(var i=1; i<5; i++) + if(settings['circle'+i].includes("/")) { + let parts = settings['circle'+i].split("/"); + let infoName = parts[0], itemName = parts[1]; + let infoNum = menu.findIndex(e=>e.name==infoName); + let itemNum = 0; + //suppose unnamed are varying (like timers or events), pick the first + if(itemName) + itemNum = menu[infoNum].items.findIndex(it=>it.name==itemName); + circleInfoNum[i-1] = infoNum; + circleItemNum[i-1] = itemNum; + } +} +//reload periodically for changes? +reloadMenu(); + +function drawEmpty(img, w, color) { + drawGauge(w, h3, 0, color); + drawInnerCircleAndTriangle(w); + writeCircleText(w, "?"); + if(img) + g.setColor(getGradientColor(color, 0)) + .drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24}); +} + +function drawClkInfo(index, w) { + var info = menu[circleInfoNum[index-1]]; + var type = settings['circle'+index]; + if (!w) w = getCircleXPosition(type); + drawCircleBackground(w); + const color = getCircleColor(type); + if(!info || !info.items.length) { + drawEmpty(info? info.img : null, w, color); + return; + } + var item = info.items[circleItemNum[index-1]]; + //TODO do hide()+get() here + item.show(); + item.hide(); + item=item.get(); + var img = item.img; + if(!img) img = info.img; + let percent = (item.v-item.min) / item.max; + if(isNaN(percent)) percent = 1; //fill it up + drawGauge(w, h3, percent, color); + drawInnerCircleAndTriangle(w); + writeCircleText(w, item.text); + g.setColor(getCircleIconColor(type, color, percent)) + .drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24}); +} + /* * wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale) */ @@ -770,125 +898,15 @@ function writeCircleText(w, content) { g.drawString(content, w, h3); } -function shortValue(v) { - if (isNaN(v)) return '-'; - if (v <= 999) return v; - if (v >= 1000 && v < 10000) { - v = Math.floor(v / 100) * 100; - return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; - } - if (v >= 10000) { - v = Math.floor(v / 1000) * 1000; - return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; - } -} - -function getSteps() { - if (Bangle.getHealthStatus) { - return Bangle.getHealthStatus("day").steps; - } - if (WIDGETS && WIDGETS.wpedom !== undefined) { - return WIDGETS.wpedom.getSteps(); - } - return 0; -} - function getWeather() { let jsonWeather = storage.readJSON('weather.json'); return jsonWeather && jsonWeather.weather ? jsonWeather.weather : undefined; } -function enableHRMSensor() { - Bangle.setHRMPower(1, "circleclock"); - if (hrtValue == undefined) { - hrtValue = '...'; - drawHeartRate(); - } -} - -let pressureLocked = false; -let pressureCache; - -function getPressureValue(type) { - return new Promise((resolve) => { - if (Bangle.getPressure) { - if (!pressureLocked) { - pressureLocked = true; - if (pressureCache && pressureCache[type]) { - resolve(pressureCache[type]); - } - Bangle.getPressure().then(function(d) { - pressureLocked = false; - if (d) { - pressureCache = d; - if (d[type]) { - resolve(d[type]); - } - } - }).catch(() => {}); - } else { - if (pressureCache && pressureCache[type]) { - resolve(pressureCache[type]); - } - } - } - }); -} - -function onLock(isLocked) { - if (!isLocked) { - draw(); - if (isCircleEnabled("hr")) { - enableHRMSensor(); - } - } else { - Bangle.setHRMPower(0, "circleclock"); - } -} -Bangle.on('lock', onLock); - -function onHRM(hrm) { - if (isCircleEnabled("hr")) { - if (hrm.confidence >= (settings.confidence)) { - hrtValue = hrm.bpm; - if (Bangle.isLCDOn()) { - drawHeartRate(); - } - } - // Let us wait before we overwrite "good" HRM values: - if (Bangle.isLCDOn()) { - if (timerHrm) clearTimeout(timerHrm); - timerHrm = setTimeout(() => { - hrtValue = '...'; - drawHeartRate(); - }, settings.hrmValidity * 1000); - } - } -} -Bangle.on('HRM', onHRM); - -function onCharging(charging) { - if (isCircleEnabled("battery")) drawBattery(); -} -Bangle.on('charging', onCharging); - - -if (isCircleEnabled("hr")) { - enableHRMSensor(); -} - Bangle.setUI({ mode : "clock", remove : function() { // Called to unload all of the clock app - Bangle.removeListener('charging', onCharging); - Bangle.removeListener('lock', onLock); - Bangle.removeListener('HRM', onHRM); - - Bangle.setHRMPower(0, "circleclock"); - - if (timerHrm) clearTimeout(timerHrm); - timerHrm = undefined; if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; diff --git a/apps/circlesclock/default.json b/apps/circlesclock/default.json index 6247c058c..0a41bf9e0 100644 --- a/apps/circlesclock/default.json +++ b/apps/circlesclock/default.json @@ -1,17 +1,11 @@ { - "minHR": 40, - "maxHR": 200, - "confidence": 0, - "stepGoal": 10000, - "stepDistanceGoal": 8000, - "stepLength": 0.8, "batteryWarn": 30, "showWidgets": false, "weatherCircleData": "humidity", "circleCount": 3, - "circle1": "hr", - "circle2": "steps", - "circle3": "battery", + "circle1": "Bangle/HRM", + "circle2": "Bangle/Steps", + "circle3": "Bangle/Battery", "circle4": "weather", "circle1color": "green-red", "circle2color": "#0000ff", @@ -21,7 +15,15 @@ "circle2colorizeIcon": true, "circle3colorizeIcon": true, "circle4colorizeIcon": false, - "hrmValidity": 60, "updateInterval": 60, - "showBigWeather": false + "showBigWeather": false, + + "minHR": 40, + "maxHR": 200, + "confidence": 0, + "stepGoal": 10000, + "stepDistanceGoal": 8000, + "stepLength": 0.8, + "hrmValidity": 60 + } diff --git a/apps/circlesclock/metadata.json b/apps/circlesclock/metadata.json index c2d3b3364..490d6c4fb 100644 --- a/apps/circlesclock/metadata.json +++ b/apps/circlesclock/metadata.json @@ -1,7 +1,7 @@ { "id": "circlesclock", "name": "Circles clock", "shortName":"Circles clock", - "version":"0.16", + "version":"0.17", "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"}], diff --git a/apps/circlesclock/settings.js b/apps/circlesclock/settings.js index 0aa8dc826..ca26cb295 100644 --- a/apps/circlesclock/settings.js +++ b/apps/circlesclock/settings.js @@ -1,6 +1,7 @@ (function(back) { const SETTINGS_FILE = "circlesclock.json"; const storage = require('Storage'); + const clock_info = require("clock_info"); let settings = Object.assign( storage.readJSON("circlesclock.default.json", true) || {}, storage.readJSON(SETTINGS_FILE, true) || {} @@ -11,8 +12,25 @@ storage.write(SETTINGS_FILE, settings); } - const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude"]; - const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude"]; + //const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude", "timer"]; + //const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude", "timer"]; + var valuesCircleTypes = ["empty","weather", "sunprogress"]; + var namesCircleTypes = ["empty","weather", "sun"]; + clock_info.load().forEach(e=>{ + //TODO filter for hasRange and other + if(!e.items.length || !e.items[0].name) { + //suppose unnamed are varying (like timers or events), pick the first + item = e.items[0]; + valuesCircleTypes = valuesCircleTypes.concat([e.name+"/"]); + namesCircleTypes = namesCircleTypes.concat([e.name]); + } else { + let values = e.items.map(i=>e.name+"/"+i.name); + let names =e.name=="Bangle" ? e.items.map(i=>i.name) : values; + valuesCircleTypes = valuesCircleTypes.concat(values); + namesCircleTypes = namesCircleTypes.concat(names); + } + }) + const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#fff", "#000", "green-red", "red-green", "fg"]; @@ -36,8 +54,6 @@ /*LANG*/'circle 2': ()=>showCircleMenu(2), /*LANG*/'circle 3': ()=>showCircleMenu(3), /*LANG*/'circle 4': ()=>showCircleMenu(4), - /*LANG*/'heartrate': ()=>showHRMenu(), - /*LANG*/'steps': ()=>showStepMenu(), /*LANG*/'battery warn': { value: settings.batteryWarn, min: 10, @@ -78,91 +94,6 @@ E.showMenu(menu); } - function showHRMenu() { - let menu = { - '': { 'title': /*LANG*/'Heartrate' }, - /*LANG*/'< Back': ()=>showMainMenu(), - /*LANG*/'minimum': { - value: settings.minHR, - min: 0, - max : 250, - step: 5, - format: x => { - return x + " bpm"; - }, - onchange: x => save('minHR', x), - }, - /*LANG*/'maximum': { - value: settings.maxHR, - min: 20, - max : 250, - step: 5, - format: x => { - return x + " bpm"; - }, - onchange: x => save('maxHR', x), - }, - /*LANG*/'min. confidence': { - value: settings.confidence, - min: 0, - max : 100, - step: 10, - format: x => { - return x + "%"; - }, - onchange: x => save('confidence', x), - }, - /*LANG*/'valid period': { - value: settings.hrmValidity, - min: 10, - max : 1800, - step: 10, - format: x => { - return x + "s"; - }, - onchange: x => save('hrmValidity', x), - }, - }; - E.showMenu(menu); - } - - function showStepMenu() { - let menu = { - '': { 'title': /*LANG*/'Steps' }, - /*LANG*/'< Back': ()=>showMainMenu(), - /*LANG*/'goal': { - value: settings.stepGoal, - min: 1000, - max : 50000, - step: 500, - format: x => { - return x; - }, - onchange: x => save('stepGoal', x), - }, - /*LANG*/'distance goal': { - value: settings.stepDistanceGoal, - min: 1000, - max : 50000, - step: 500, - format: x => { - return x; - }, - onchange: x => save('stepDistanceGoal', x), - }, - /*LANG*/'step length': { - value: settings.stepLength, - min: 0.1, - max : 1.5, - step: 0.01, - format: x => { - return x; - }, - onchange: x => save('stepLength', x), - } - }; - E.showMenu(menu); - } function showCircleMenu(circleId) { const circleName = "circle" + circleId; const colorKey = circleName + "color"; @@ -192,6 +123,5 @@ E.showMenu(menu); } - showMainMenu(); }); diff --git a/apps/clkinfosunrise/ChangeLog b/apps/clkinfosunrise/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/clkinfosunrise/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/clkinfosunrise/app.png b/apps/clkinfosunrise/app.png new file mode 100644 index 000000000..a1d53946d Binary files /dev/null and b/apps/clkinfosunrise/app.png differ diff --git a/apps/clkinfosunrise/clkinfo.js b/apps/clkinfosunrise/clkinfo.js new file mode 100644 index 000000000..1454a83f3 --- /dev/null +++ b/apps/clkinfosunrise/clkinfo.js @@ -0,0 +1,33 @@ +(function() { + // get today's sunlight times for lat/lon + var sunrise, sunset; + + function calculate() { + var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); + const locale = require("locale"); + var location = require("Storage").readJSON("mylocation.json",1)||{}; + location.lat = location.lat||51.5072; + location.lon = location.lon||0.1276; + location.location = location.location||"London"; + var times = SunCalc.getTimes(new Date(), location.lat, location.lon); + sunrise = locale.time(times.sunrise,1); + sunset = locale.time(times.sunset,1); + /* do we want to re-calculate this every day? Or we just assume + that 'show' will get called once a day? */ + } + + return { + name: "Bangle", + items: [ + { name : "Sunrise", + get : () => ({ text : sunrise, + img : atob("GBiBAAAAAAAAAAAAAAAYAAA8AAB+AAD/AAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }), + show : calculate, hide : () => {} + }, { name : "Sunset", + get : () => ({ text : sunset, + img : atob("GBiBAAAAAAAAAAAAAAB+AAA8AAAYAAAYAAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }), + show : calculate, hide : () => {} + } + ] + }; +}) diff --git a/apps/clkinfosunrise/metadata.json b/apps/clkinfosunrise/metadata.json new file mode 100644 index 000000000..f8b68e11f --- /dev/null +++ b/apps/clkinfosunrise/metadata.json @@ -0,0 +1,12 @@ +{ "id": "clkinfosunrise", + "name": "Sunrise Clockinfo", + "version":"0.01", + "description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays sunrise and sunset based on the location from the 'My Location' app", + "icon": "app.png", + "type": "clkinfo", + "tags": "clkinfo,sunrise", + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"sunrise.clkinfo.js","url":"clkinfo.js"} + ] +} diff --git a/apps/demoapp/metadata.json b/apps/demoapp/metadata.json index df6554ef5..2fb30f718 100644 --- a/apps/demoapp/metadata.json +++ b/apps/demoapp/metadata.json @@ -12,6 +12,5 @@ "storage": [ {"name":"demoapp.app.js","url":"app.js"}, {"name":"demoapp.img","url":"app-icon.js","evaluate":true} - ], - "sortorder": -9 + ] } diff --git a/apps/dragboard/ChangeLog b/apps/dragboard/ChangeLog index 48a1ffb03..265094e87 100644 --- a/apps/dragboard/ChangeLog +++ b/apps/dragboard/ChangeLog @@ -3,3 +3,4 @@ 0.03: Made the code shorter and somewhat more readable by writing some functions. Also made it work as a library where it returns the text once finished. The keyboard is now made to exit correctly when the 'back' event is called. The keyboard now uses theme colors correctly, although it still looks best with dark theme. The numbers row is now solidly green - except for highlights. 0.04: Now displays the opened text string at launch. 0.05: Now scrolls text when string gets longer than screen width. +0.06: The code is now more reliable and the input snappier. Widgets will be drawn if present. diff --git a/apps/dragboard/lib.js b/apps/dragboard/lib.js index b9b19f982..220f075d7 100644 --- a/apps/dragboard/lib.js +++ b/apps/dragboard/lib.js @@ -1,12 +1,9 @@ -//Keep banglejs screen on for 100 sec at 0.55 power level for development purposes -//Bangle.setLCDTimeout(30); -//Bangle.setLCDPower(1); - exports.input = function(options) { options = options||{}; var text = options.text; if ("string"!=typeof text) text=""; - + + var R = Bangle.appRect; var BGCOLOR = g.theme.bg; var HLCOLOR = g.theme.fg; var ABCCOLOR = g.toColor(1,0,0);//'#FF0000'; @@ -17,35 +14,38 @@ exports.input = function(options) { var SMALLFONTWIDTH = parseInt(SMALLFONT.charAt(0)*parseInt(SMALLFONT.charAt(-1))); var ABC = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase(); - var ABCPADDING = (g.getWidth()-6*ABC.length)/2; + var ABCPADDING = ((R.x+R.w)-6*ABC.length)/2; var NUM = ' 1234567890!?,.- '; var NUMHIDDEN = ' 1234567890!?,.- '; - var NUMPADDING = (g.getWidth()-6*NUM.length)/2; + var NUMPADDING = ((R.x+R.w)-6*NUM.length)/2; var rectHeight = 40; - var delSpaceLast; function drawAbcRow() { g.clear(); + try { // Draw widgets if they are present in the current app. + if (WIDGETS) Bangle.drawWidgets(); + } catch (_) {} g.setFont(SMALLFONT); g.setColor(ABCCOLOR); - g.drawString(ABC, ABCPADDING, g.getHeight()/2); - g.fillRect(0, g.getHeight()-26, g.getWidth(), g.getHeight()); + g.setFontAlign(-1, -1, 0); + g.drawString(ABC, ABCPADDING, (R.y+R.h)/2); + g.fillRect(0, (R.y+R.h)-26, (R.x+R.w), (R.y+R.h)); } function drawNumRow() { g.setFont(SMALLFONT); g.setColor(NUMCOLOR); - g.drawString(NUM, NUMPADDING, g.getHeight()/4); + g.setFontAlign(-1, -1, 0); + g.drawString(NUM, NUMPADDING, (R.y+R.h)/4); - g.fillRect(NUMPADDING, g.getHeight()-rectHeight*4/3, g.getWidth()-NUMPADDING, g.getHeight()-rectHeight*2/3); + g.fillRect(NUMPADDING, (R.y+R.h)-rectHeight*4/3, (R.x+R.w)-NUMPADDING, (R.y+R.h)-rectHeight*2/3); } function updateTopString() { - "ram" g.setColor(BGCOLOR); g.fillRect(0,4+20,176,13+20); g.setColor(0.2,0,0); @@ -54,13 +54,10 @@ exports.input = function(options) { g.setColor(0.7,0,0); g.fillRect(rectLen+5,4+20,rectLen+10,13+20); g.setColor(1,1,1); + g.setFontAlign(-1, -1, 0); g.drawString(text.length<=27? text.substr(-27, 27) : '<- '+text.substr(-24,24), 5, 5+20); } - drawAbcRow(); - drawNumRow(); - updateTopString(); - var abcHL; var abcHLPrev = -10; var numHL; @@ -68,194 +65,182 @@ exports.input = function(options) { var type = ''; var typePrev = ''; var largeCharOffset = 6; - + function resetChars(char, HLPrev, typePadding, heightDivisor, rowColor) { - "ram" + "ram"; // Small character in list g.setColor(rowColor); g.setFont(SMALLFONT); - g.drawString(char, typePadding + HLPrev*6, g.getHeight()/heightDivisor); + g.setFontAlign(-1, -1, 0); + g.drawString(char, typePadding + HLPrev*6, (R.y+R.h)/heightDivisor); // Large character g.setColor(BGCOLOR); - g.fillRect(0,g.getHeight()/3,176,g.getHeight()/3+24); - //g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, g.getHeight()/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle. + g.fillRect(0,(R.y+R.h)/3,176,(R.y+R.h)/3+24); + //g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, (R.y+R.h)/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle. // mark in the list } function showChars(char, HL, typePadding, heightDivisor) { - "ram" + "ram"; // mark in the list g.setColor(HLCOLOR); g.setFont(SMALLFONT); - if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, g.getHeight()/heightDivisor); + g.setFontAlign(-1, -1, 0); + if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, (R.y+R.h)/heightDivisor); // show new large character g.setFont(BIGFONT); - g.drawString(char, typePadding + HL*6 -largeCharOffset, g.getHeight()/3); + g.drawString(char, typePadding + HL*6 -largeCharOffset, (R.y+R.h)/3); g.setFont(SMALLFONT); } - + + function initDraw() { + //var R = Bangle.appRect; // To make sure it's properly updated. Not sure if this is needed. + drawAbcRow(); + drawNumRow(); + updateTopString(); + } + initDraw(); + //setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise. + function changeCase(abcHL) { g.setColor(BGCOLOR); - g.drawString(ABC, ABCPADDING, g.getHeight()/2); + g.setFontAlign(-1, -1, 0); + g.drawString(ABC, ABCPADDING, (R.y+R.h)/2); if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) ABC = ABC.toLowerCase(); else ABC = ABC.toUpperCase(); g.setColor(ABCCOLOR); - g.drawString(ABC, ABCPADDING, g.getHeight()/2); + g.drawString(ABC, ABCPADDING, (R.y+R.h)/2); } return new Promise((resolve,reject) => { - // Interpret touch input + // Interpret touch input Bangle.setUI({ - mode: 'custom', - back: ()=>{ - Bangle.setUI(); - g.clearRect(Bangle.appRect); - resolve(text); - }, - drag: function(event) { + mode: 'custom', + back: ()=>{ + Bangle.setUI(); + g.clearRect(Bangle.appRect); + resolve(text); + }, + drag: function(event) { + "ram"; + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + // Choose character by draging along red rectangle at bottom of screen + if (event.y >= ( (R.y+R.h) - 12 )) { + // Translate x-position to character + if (event.x < ABCPADDING) { abcHL = 0; } + else if (event.x >= 176-ABCPADDING) { abcHL = 25; } + else { abcHL = Math.floor((event.x-ABCPADDING)/6); } - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - // Choose character by draging along red rectangle at bottom of screen - if (event.y >= ( g.getHeight() - 12 )) { - // Translate x-position to character - if (event.x < ABCPADDING) { abcHL = 0; } - else if (event.x >= 176-ABCPADDING) { abcHL = 25; } - else { abcHL = Math.floor((event.x-ABCPADDING)/6); } + // Datastream for development purposes + //print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev)); - // Datastream for development purposes - //print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev)); + // Unmark previous character and mark the current one... + // Handling switching between letters and numbers/punctuation + if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - // Unmark previous character and mark the current one... - // Handling switching between letters and numbers/punctuation - if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - - if (abcHL != abcHLPrev) { - resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); - showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2); + if (abcHL != abcHLPrev) { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2); } - // Print string at top of screen - if (event.b == 0) { - text = text + ABC.charAt(abcHL); - updateTopString(); - - // Autoswitching letter case - if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL); - } - // Update previous character to current one - abcHLPrev = abcHL; - typePrev = 'abc'; - } - - - - - - - - - - // 12345678901234567890 - // Choose number or puctuation by draging on green rectangle - else if ((event.y < ( g.getHeight() - 12 )) && (event.y > ( g.getHeight() - 52 ))) { - // Translate x-position to character - if (event.x < NUMPADDING) { numHL = 0; } - else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; } - else { numHL = Math.floor((event.x-NUMPADDING)/6); } - - // Datastream for development purposes - //print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev)); - - // Unmark previous character and mark the current one... - // Handling switching between letters and numbers/punctuation - if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); - - if (numHL != numHLPrev) { - resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4); - } - // Print string at top of screen - if (event.b == 0) { - g.setColor(HLCOLOR); - // Backspace if releasing before list of numbers/punctuation - if (event.x < NUMPADDING) { - // show delete sign - showChars('del', 0, g.getWidth()/2 +6 -27 , 4); - delSpaceLast = 1; - text = text.slice(0, -1); - updateTopString(); - //print(text); - } - // Append space if releasing after list of numbers/punctuation - else if (event.x > g.getWidth()-NUMPADDING) { - //show space sign - showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4); - delSpaceLast = 1; - text = text + ' '; - updateTopString(); - //print(text); - } - // Append selected number/punctuation - else { - text = text + NUMHIDDEN.charAt(numHL); + // Print string at top of screen + if (event.b == 0) { + text = text + ABC.charAt(abcHL); updateTopString(); // Autoswitching letter case - if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase(); + if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL); } + // Update previous character to current one + abcHLPrev = abcHL; + typePrev = 'abc'; } - // Update previous character to current one - numHLPrev = numHL; - typePrev = 'num'; - } - - - - - - - - - // Make a space or backspace by swiping right or left on screen above green rectangle - else if (event.y > 20+4) { - if (event.b == 0) { - g.setColor(HLCOLOR); - if (event.x < g.getWidth()/2) { - resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + + // 12345678901234567890 + // Choose number or puctuation by draging on green rectangle + else if ((event.y < ( (R.y+R.h) - 12 )) && (event.y > ( (R.y+R.h) - 52 ))) { + // Translate x-position to character + if (event.x < NUMPADDING) { numHL = 0; } + else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; } + else { numHL = Math.floor((event.x-NUMPADDING)/6); } + + // Datastream for development purposes + //print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev)); + + // Unmark previous character and mark the current one... + // Handling switching between letters and numbers/punctuation + if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + + if (numHL != numHLPrev) { resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - - // show delete sign - showChars('del', 0, g.getWidth()/2 +6 -27 , 4); - delSpaceLast = 1; - - // Backspace and draw string upper right corner - text = text.slice(0, -1); - updateTopString(); - if (text.length==0) changeCase(abcHL); - //print(text, 'undid'); + showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4); } - else { - resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); - resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + // Print string at top of screen + if (event.b == 0) { + g.setColor(HLCOLOR); + // Backspace if releasing before list of numbers/punctuation + if (event.x < NUMPADDING) { + // show delete sign + showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4); + delSpaceLast = 1; + text = text.slice(0, -1); + updateTopString(); + //print(text); + } + // Append space if releasing after list of numbers/punctuation + else if (event.x > (R.x+R.w)-NUMPADDING) { + //show space sign + showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4); + delSpaceLast = 1; + text = text + ' '; + updateTopString(); + //print(text); + } + // Append selected number/punctuation + else { + text = text + NUMHIDDEN.charAt(numHL); + updateTopString(); - //show space sign - showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4); - delSpaceLast = 1; + // Autoswitching letter case + if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase(); + } + } + // Update previous character to current one + numHLPrev = numHL; + typePrev = 'num'; + } - // Append space and draw string upper right corner - text = text + NUMHIDDEN.charAt(0); - updateTopString(); - //print(text, 'made space'); + // Make a space or backspace by swiping right or left on screen above green rectangle + else if (event.y > 20+4) { + if (event.b == 0) { + g.setColor(HLCOLOR); + if (event.x < (R.x+R.w)/2) { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + + // show delete sign + showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4); + delSpaceLast = 1; + + // Backspace and draw string upper right corner + text = text.slice(0, -1); + updateTopString(); + if (text.length==0) changeCase(abcHL); + //print(text, 'undid'); + } + else { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + + //show space sign + showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4); + delSpaceLast = 1; + + // Append space and draw string upper right corner + text = text + NUMHIDDEN.charAt(0); + updateTopString(); + //print(text, 'made space'); + } } } } - } - }); -}); -/* return new Promise((resolve,reject) => { - Bangle.setUI({mode:"custom", back:()=>{ - Bangle.setUI(); - g.clearRect(Bangle.appRect); - Bangle.setUI(); - resolve(text); - }}); - }); */ - + }); + }); }; diff --git a/apps/dragboard/metadata.json b/apps/dragboard/metadata.json index f9c73ddde..64b6dbe18 100644 --- a/apps/dragboard/metadata.json +++ b/apps/dragboard/metadata.json @@ -1,6 +1,6 @@ { "id": "dragboard", "name": "Dragboard", - "version":"0.05", + "version":"0.06", "description": "A library for text input via swiping keyboard", "icon": "app.png", "type":"textinput", diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog index 16c550334..044b8c35f 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -14,3 +14,13 @@ 0.14: Don't move pages when doing exit swipe - Bangle 2. 0.15: 'Swipe to exit'-code is slightly altered to be more reliable - Bangle 2. 0.16: Use default Bangle formatter for booleans +0.17: Bangle 2: Fast loading on exit to clock face. Added option for exit to +clock face by timeout. +0.18: Bangle 2: Move interactions inside setUI. Replace "one click exit" with +back-functionality through setUI, adding the red back button as well. Hardware +button to exit is no longer an option. +0.19: Bangle 2: Utilize new Bangle.load(), Bangle.showClock() functions to +facilitate 'fast switching' of apps where available. +0.20: Bangle 2: Revert use of Bangle.load() to classic load() calls since +widgets would still be loaded when they weren't supposed to. + diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js index 8cd5790bb..a7a318c18 100644 --- a/apps/dtlaunch/app-b2.js +++ b/apps/dtlaunch/app-b2.js @@ -1,61 +1,59 @@ -/* Desktop launcher -* -*/ +{ // must be inside our own scope here so that when we are unloaded everything disappears -var settings = Object.assign({ - showClocks: true, - showLaunchers: true, - direct: false, - oneClickExit:false, - swipeExit: false -}, require('Storage').readJSON("dtlaunch.json", true) || {}); + /* Desktop launcher + * + */ -if( settings.oneClickExit) - setWatch(_=> load(), BTN1); - -var s = require("Storage"); -var apps = s.list(/\.info$/).map(app=>{ - var a=s.readJSON(app,1); - return a && { - name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src - };}).filter( - app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type)); + let settings = Object.assign({ + showClocks: true, + showLaunchers: true, + direct: false, + swipeExit: false, + timeOut: "Off" + }, require('Storage').readJSON("dtlaunch.json", true) || {}); -apps.sort((a,b)=>{ - var n=(0|a.sortorder)-(0|b.sortorder); - if (n) return n; // do sortorder first - if (a.nameb.name) return 1; - return 0; -}); -apps.forEach(app=>{ + let s = require("Storage"); + var apps = s.list(/\.info$/).map(app=>{ + let a=s.readJSON(app,1); + return a && { + name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src + };}).filter( + app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type)); + + apps.sort((a,b)=>{ + let n=(0|a.sortorder)-(0|b.sortorder); + if (n) return n; // do sortorder first + if (a.nameb.name) return 1; + return 0; + }); + apps.forEach(app=>{ if (app.icon) app.icon = s.read(app.icon); // should just be a link to a memory area }); -var Napps = apps.length; -var Npages = Math.ceil(Napps/4); -var maxPage = Npages-1; -var selected = -1; -var oldselected = -1; -var page = 0; -const XOFF = 24; -const YOFF = 30; + let Napps = apps.length; + let Npages = Math.ceil(Napps/4); + let maxPage = Npages-1; + let selected = -1; + let oldselected = -1; + let page = 0; + const XOFF = 24; + const YOFF = 30; -function draw_icon(p,n,selected) { - var x = (n%2)*72+XOFF; - var y = n>1?72+YOFF:YOFF; + let drawIcon= function(p,n,selected) { + let x = (n%2)*72+XOFF; + let y = n>1?72+YOFF:YOFF; (selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52); g.clearRect(x+12,y+4,x+59,y+51); g.setColor(g.theme.fg); try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){} g.setFontAlign(0,-1,0).setFont("6x8",1); - var txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" "); - var lineY = 0; - var line = ""; - while (txt.length > 0){ - var c = txt.shift(); - + let txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" "); + let lineY = 0; + let line = ""; + while (txt.length > 0){ + let c = txt.shift(); if (c.length + 1 + line.length > 13){ if (line.length > 0){ g.drawString(line.trim(),x+36,y+54+lineY*8); @@ -67,70 +65,91 @@ function draw_icon(p,n,selected) { } } g.drawString(line.trim(),x+36,y+54+lineY*8); -} + }; -function drawPage(p){ + let drawPage = function(p){ g.reset(); g.clearRect(0,24,175,175); - var O = 88+YOFF/2-12*(Npages/2); - for (var j=0;j{ + Bangle.loadWidgets(); + drawPage(0); + + let swipeListenerDt = function(dirLeftRight, dirUpDown){ + updateTimeoutToClock(); selected = 0; oldselected=-1; - if(settings.swipeExit && dirLeftRight==1) load(); + if(settings.swipeExit && dirLeftRight==1) Bangle.showClock(); if (dirUpDown==-1||dirLeftRight==-1){ - ++page; if (page>maxPage) page=0; - drawPage(page); + ++page; if (page>maxPage) page=0; + drawPage(page); } else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){ - --page; if (page<0) page=maxPage; - drawPage(page); + --page; if (page<0) page=maxPage; + drawPage(page); } -}); + }; -function isTouched(p,n){ + let isTouched = function(p,n){ if (n<0 || n>3) return false; - var x1 = (n%2)*72+XOFF; var y1 = n>1?72+YOFF:YOFF; - var x2 = x1+71; var y2 = y1+81; + let x1 = (n%2)*72+XOFF; let y1 = n>1?72+YOFF:YOFF; + let x2 = x1+71; let y2 = y1+81; return (p.x>x1 && p.y>y1 && p.x{ - var i; + let touchListenerDt = function(_,p){ + updateTimeoutToClock(); + let i; for (i=0;i<4;i++){ - if((page*4+i)=0 || settings.direct) { - if (selected!=i && !settings.direct){ - draw_icon(page,selected,false); - } else { - load(apps[page*4+i].src); - } - } - selected=i; - break; + if((page*4+i)=0 || settings.direct) { + if (selected!=i && !settings.direct){ + drawIcon(page,selected,false); + } else { + load(apps[page*4+i].src); } + } + selected=i; + break; } + } } if ((i==4 || (page*4+i)>Napps) && selected>=0) { - draw_icon(page,selected,false); - selected=-1; + drawIcon(page,selected,false); + selected=-1; } -}); + }; -Bangle.loadWidgets(); -g.clear(); -Bangle.drawWidgets(); -drawPage(0); + Bangle.setUI({ + mode : 'custom', + back : Bangle.showClock, + swipe : swipeListenerDt, + touch : touchListenerDt, + remove : ()=>{if (timeoutToClock) clearTimeout(timeoutToClock);} + }); + + // taken from Icon Launcher with minor alterations + let timeoutToClock; + const updateTimeoutToClock = function(){ + if (settings.timeOut!="Off"){ + let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt + if (timeoutToClock) clearTimeout(timeoutToClock); + timeoutToClock = setTimeout(Bangle.showClock,time*1000); + } + }; + updateTimeoutToClock(); + +} // end of app scope diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json index 36728f342..b69a1a5e6 100644 --- a/apps/dtlaunch/metadata.json +++ b/apps/dtlaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "dtlaunch", "name": "Desktop Launcher", - "version": "0.16", + "version": "0.20", "description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.", "screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}], "icon": "icon.png", diff --git a/apps/dtlaunch/settings-b2.js b/apps/dtlaunch/settings-b2.js index fac9c0fff..24959df8c 100644 --- a/apps/dtlaunch/settings-b2.js +++ b/apps/dtlaunch/settings-b2.js @@ -5,51 +5,56 @@ showClocks: true, showLaunchers: true, direct: false, - oneClickExit:false, - swipeExit: false + swipeExit: false, + timeOut: "Off" }, require('Storage').readJSON(FILE, true) || {}); function writeSettings() { require('Storage').writeJSON(FILE, settings); } + const timeOutChoices = [/*LANG*/"Off", "10s", "15s", "20s", "30s"]; + E.showMenu({ "" : { "title" : "Desktop launcher" }, - "< Back" : () => back(), - 'Show clocks': { + /*LANG*/"< Back" : () => back(), + /*LANG*/'Show clocks': { value: settings.showClocks, onchange: v => { settings.showClocks = v; writeSettings(); } }, - 'Show launchers': { + /*LANG*/'Show launchers': { value: settings.showLaunchers, onchange: v => { settings.showLaunchers = v; writeSettings(); } }, - 'Direct launch': { + /*LANG*/'Direct launch': { value: settings.direct, onchange: v => { settings.direct = v; writeSettings(); } }, - 'Swipe Exit': { + /*LANG*/'Swipe Exit': { value: settings.swipeExit, onchange: v => { settings.swipeExit = v; writeSettings(); } }, - 'One click exit': { - value: settings.oneClickExit, + /*LANG*/'Time Out': { // Adapted from Icon Launcher + value: timeOutChoices.indexOf(settings.timeOut), + min: 0, + max: timeOutChoices.length-1, + format: v => timeOutChoices[v], onchange: v => { - settings.oneClickExit = v; + settings.timeOut = timeOutChoices[v]; writeSettings(); } } }); -}) +}); diff --git a/apps/fwupdate/ChangeLog b/apps/fwupdate/ChangeLog index 06f84a11a..ea0b48eb9 100644 --- a/apps/fwupdate/ChangeLog +++ b/apps/fwupdate/ChangeLog @@ -5,3 +5,4 @@ 0.03: Improve bootloader update safety. Now sets unsafeFlash:1 to allow flash with 2v11 and later Add CRC checks for common bootloaders that we know don't work 0.04: Include a precompiled bootloader for easy bootloader updates +0.05: Rename Bootloader->DFU and add explanation to avoid confusion with Bootloader app diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html index 8ef117933..31eb4a256 100644 --- a/apps/fwupdate/custom.html +++ b/apps/fwupdate/custom.html @@ -3,7 +3,7 @@ -

This tool allows you to update the bootloader on Bangle.js 2 devices +

This tool allows you to update the firmware on Bangle.js 2 devices from within the App Loader.

@@ -12,27 +12,41 @@ see the Bangle.js 1 instructions

    -

    Your current firmware version is unknown and bootloader is unknown

    +

    Your current firmware version is unknown and DFU is unknown

- + + + + diff --git a/apps/medicalinfo/lib.js b/apps/medicalinfo/lib.js new file mode 100644 index 000000000..683005359 --- /dev/null +++ b/apps/medicalinfo/lib.js @@ -0,0 +1,21 @@ +const storage = require('Storage'); + +exports.load = function () { + const medicalinfo = storage.readJSON('medicalinfo.json') || { + bloodType: "", + height: "", + weight: "", + medicalAlert: [""] + }; + + // Don't return anything unexpected + const expectedMedicalinfo = [ + "bloodType", + "height", + "weight", + "medicalAlert" + ].filter(key => key in medicalinfo) + .reduce((obj, key) => (obj[key] = medicalinfo[key], obj), {}); + + return expectedMedicalinfo; +}; diff --git a/apps/medicalinfo/medicalinfo.json b/apps/medicalinfo/medicalinfo.json new file mode 100644 index 000000000..8b49725cb --- /dev/null +++ b/apps/medicalinfo/medicalinfo.json @@ -0,0 +1,6 @@ +{ + "bloodType": "", + "height": "", + "weight": "", + "medicalAlert": [ "" ] +} diff --git a/apps/medicalinfo/metadata.json b/apps/medicalinfo/metadata.json new file mode 100644 index 000000000..f1a0c145f --- /dev/null +++ b/apps/medicalinfo/metadata.json @@ -0,0 +1,20 @@ +{ "id": "medicalinfo", + "name": "Medical Information", + "version":"0.01", + "description": "Provides 'medicalinfo.json' used by various health apps, as well as a way to edit it from the App Loader", + "icon": "app.png", + "tags": "health,medical", + "type": "app", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "screenshots": [{"url":"screenshot_light.png"}], + "interface": "interface.html", + "storage": [ + {"name":"medicalinfo.app.js","url":"app.js"}, + {"name":"medicalinfo.img","url":"app-icon.js","evaluate":true}, + {"name":"medicalinfo","url":"lib.js"} + ], + "data": [ + {"name":"medicalinfo.json","url":"medicalinfo.json"} + ] +} diff --git a/apps/medicalinfo/screenshot_light.png b/apps/medicalinfo/screenshot_light.png new file mode 100644 index 000000000..42970f9fc Binary files /dev/null and b/apps/medicalinfo/screenshot_light.png differ diff --git a/apps/messageicons/ChangeLog b/apps/messageicons/ChangeLog new file mode 100644 index 000000000..52a4b35a7 --- /dev/null +++ b/apps/messageicons/ChangeLog @@ -0,0 +1 @@ +0.01: Moved message icons from messages into standalone library diff --git a/apps/messageicons/app.png b/apps/messageicons/app.png new file mode 100644 index 000000000..1e47a39c6 Binary files /dev/null and b/apps/messageicons/app.png differ diff --git a/apps/messageicons/icons/1password.png b/apps/messageicons/icons/1password.png new file mode 100644 index 000000000..7e28c0c93 Binary files /dev/null and b/apps/messageicons/icons/1password.png differ diff --git a/apps/messageicons/icons/airbnb.png b/apps/messageicons/icons/airbnb.png new file mode 100644 index 000000000..f691469bc Binary files /dev/null and b/apps/messageicons/icons/airbnb.png differ diff --git a/apps/messageicons/icons/alarm.png b/apps/messageicons/icons/alarm.png new file mode 100644 index 000000000..22a5b6cc4 Binary files /dev/null and b/apps/messageicons/icons/alarm.png differ diff --git a/apps/messageicons/icons/amazon.png b/apps/messageicons/icons/amazon.png new file mode 100644 index 000000000..9d446cb6a Binary files /dev/null and b/apps/messageicons/icons/amazon.png differ diff --git a/apps/messageicons/icons/bag.png b/apps/messageicons/icons/bag.png new file mode 100644 index 000000000..70dab4221 Binary files /dev/null and b/apps/messageicons/icons/bag.png differ diff --git a/apps/messageicons/icons/bank.png b/apps/messageicons/icons/bank.png new file mode 100644 index 000000000..fa1500a41 Binary files /dev/null and b/apps/messageicons/icons/bank.png differ diff --git a/apps/messageicons/icons/beeper.png b/apps/messageicons/icons/beeper.png new file mode 100644 index 000000000..bea9138ec Binary files /dev/null and b/apps/messageicons/icons/beeper.png differ diff --git a/apps/messageicons/icons/bitcoin.png b/apps/messageicons/icons/bitcoin.png new file mode 100644 index 000000000..85deecc36 Binary files /dev/null and b/apps/messageicons/icons/bitcoin.png differ diff --git a/apps/messageicons/icons/bolt.png b/apps/messageicons/icons/bolt.png new file mode 100644 index 000000000..215b9d052 Binary files /dev/null and b/apps/messageicons/icons/bolt.png differ diff --git a/apps/messageicons/icons/cafe.png b/apps/messageicons/icons/cafe.png new file mode 100644 index 000000000..26a3bb114 Binary files /dev/null and b/apps/messageicons/icons/cafe.png differ diff --git a/apps/messageicons/icons/calendar.png b/apps/messageicons/icons/calendar.png new file mode 100644 index 000000000..286952af5 Binary files /dev/null and b/apps/messageicons/icons/calendar.png differ diff --git a/apps/messageicons/icons/cart.png b/apps/messageicons/icons/cart.png new file mode 100644 index 000000000..dec53ef00 Binary files /dev/null and b/apps/messageicons/icons/cart.png differ diff --git a/apps/messageicons/icons/cashapp.png b/apps/messageicons/icons/cashapp.png new file mode 100644 index 000000000..23e897c82 Binary files /dev/null and b/apps/messageicons/icons/cashapp.png differ diff --git a/apps/messageicons/icons/cbc.png b/apps/messageicons/icons/cbc.png new file mode 100644 index 000000000..96e3ddd1b Binary files /dev/null and b/apps/messageicons/icons/cbc.png differ diff --git a/apps/messageicons/icons/chrome.png b/apps/messageicons/icons/chrome.png new file mode 100644 index 000000000..b477c57ff Binary files /dev/null and b/apps/messageicons/icons/chrome.png differ diff --git a/apps/messageicons/icons/coronavirus.png b/apps/messageicons/icons/coronavirus.png new file mode 100644 index 000000000..98b967954 Binary files /dev/null and b/apps/messageicons/icons/coronavirus.png differ diff --git a/apps/messageicons/icons/crave.png b/apps/messageicons/icons/crave.png new file mode 100644 index 000000000..ee6f0778a Binary files /dev/null and b/apps/messageicons/icons/crave.png differ diff --git a/apps/messageicons/icons/delivery.png b/apps/messageicons/icons/delivery.png new file mode 100644 index 000000000..78ca0e190 Binary files /dev/null and b/apps/messageicons/icons/delivery.png differ diff --git a/apps/messageicons/icons/desjardins.png b/apps/messageicons/icons/desjardins.png new file mode 100644 index 000000000..c54899aab Binary files /dev/null and b/apps/messageicons/icons/desjardins.png differ diff --git a/apps/messageicons/icons/discord.png b/apps/messageicons/icons/discord.png new file mode 100644 index 000000000..a8c4e2d39 Binary files /dev/null and b/apps/messageicons/icons/discord.png differ diff --git a/apps/messageicons/icons/dollars.png b/apps/messageicons/icons/dollars.png new file mode 100644 index 000000000..e5c1d2e68 Binary files /dev/null and b/apps/messageicons/icons/dollars.png differ diff --git a/apps/messageicons/icons/dropbox.png b/apps/messageicons/icons/dropbox.png new file mode 100644 index 000000000..ad6dd84a8 Binary files /dev/null and b/apps/messageicons/icons/dropbox.png differ diff --git a/apps/messageicons/icons/facebook messenger.png b/apps/messageicons/icons/facebook messenger.png new file mode 100644 index 000000000..286b5bc29 Binary files /dev/null and b/apps/messageicons/icons/facebook messenger.png differ diff --git a/apps/messageicons/icons/facebook.png b/apps/messageicons/icons/facebook.png new file mode 100644 index 000000000..5ba18eca3 Binary files /dev/null and b/apps/messageicons/icons/facebook.png differ diff --git a/apps/messageicons/icons/fdroid.png b/apps/messageicons/icons/fdroid.png new file mode 100644 index 000000000..4b5c6761e Binary files /dev/null and b/apps/messageicons/icons/fdroid.png differ diff --git a/apps/messageicons/icons/firefox.png b/apps/messageicons/icons/firefox.png new file mode 100644 index 000000000..2dcae9270 Binary files /dev/null and b/apps/messageicons/icons/firefox.png differ diff --git a/apps/messageicons/icons/github.png b/apps/messageicons/icons/github.png new file mode 100644 index 000000000..813cbb2c9 Binary files /dev/null and b/apps/messageicons/icons/github.png differ diff --git a/apps/messageicons/icons/gitlab.png b/apps/messageicons/icons/gitlab.png new file mode 100644 index 000000000..3e7280f59 Binary files /dev/null and b/apps/messageicons/icons/gitlab.png differ diff --git a/apps/messageicons/icons/google chat.png b/apps/messageicons/icons/google chat.png new file mode 100644 index 000000000..6d8eb7741 Binary files /dev/null and b/apps/messageicons/icons/google chat.png differ diff --git a/apps/messageicons/icons/google drive.png b/apps/messageicons/icons/google drive.png new file mode 100644 index 000000000..97da419a8 Binary files /dev/null and b/apps/messageicons/icons/google drive.png differ diff --git a/apps/messageicons/icons/google home.png b/apps/messageicons/icons/google home.png new file mode 100644 index 000000000..f6ebaa77f Binary files /dev/null and b/apps/messageicons/icons/google home.png differ diff --git a/apps/messageicons/icons/google keep.png b/apps/messageicons/icons/google keep.png new file mode 100644 index 000000000..f7d1f97c6 Binary files /dev/null and b/apps/messageicons/icons/google keep.png differ diff --git a/apps/messageicons/icons/google opinion rewards.png b/apps/messageicons/icons/google opinion rewards.png new file mode 100644 index 000000000..479ec0c5f Binary files /dev/null and b/apps/messageicons/icons/google opinion rewards.png differ diff --git a/apps/messageicons/icons/google photos.png b/apps/messageicons/icons/google photos.png new file mode 100644 index 000000000..aecf00dbe Binary files /dev/null and b/apps/messageicons/icons/google photos.png differ diff --git a/apps/messageicons/icons/google play store.png b/apps/messageicons/icons/google play store.png new file mode 100644 index 000000000..166094907 Binary files /dev/null and b/apps/messageicons/icons/google play store.png differ diff --git a/apps/messageicons/icons/google.png b/apps/messageicons/icons/google.png new file mode 100644 index 000000000..62797fefb Binary files /dev/null and b/apps/messageicons/icons/google.png differ diff --git a/apps/messageicons/icons/instagram.png b/apps/messageicons/icons/instagram.png new file mode 100644 index 000000000..9bccd20af Binary files /dev/null and b/apps/messageicons/icons/instagram.png differ diff --git a/apps/messageicons/icons/kde connect.png b/apps/messageicons/icons/kde connect.png new file mode 100644 index 000000000..f13298b1d Binary files /dev/null and b/apps/messageicons/icons/kde connect.png differ diff --git a/apps/messageicons/icons/lieferando.png b/apps/messageicons/icons/lieferando.png new file mode 100644 index 000000000..7a31bc9e1 Binary files /dev/null and b/apps/messageicons/icons/lieferando.png differ diff --git a/apps/messageicons/icons/linkedin.png b/apps/messageicons/icons/linkedin.png new file mode 100644 index 000000000..016e29ca8 Binary files /dev/null and b/apps/messageicons/icons/linkedin.png differ diff --git a/apps/messageicons/icons/mail.png b/apps/messageicons/icons/mail.png new file mode 100644 index 000000000..8c29a4895 Binary files /dev/null and b/apps/messageicons/icons/mail.png differ diff --git a/apps/messageicons/icons/map.png b/apps/messageicons/icons/map.png new file mode 100644 index 000000000..215f3f971 Binary files /dev/null and b/apps/messageicons/icons/map.png differ diff --git a/apps/messageicons/icons/mastodon.png b/apps/messageicons/icons/mastodon.png new file mode 100644 index 000000000..82fe737a3 Binary files /dev/null and b/apps/messageicons/icons/mastodon.png differ diff --git a/apps/messageicons/icons/matrix element.png b/apps/messageicons/icons/matrix element.png new file mode 100644 index 000000000..9ae89dc37 Binary files /dev/null and b/apps/messageicons/icons/matrix element.png differ diff --git a/apps/messageicons/icons/mattermost.png b/apps/messageicons/icons/mattermost.png new file mode 100644 index 000000000..2d5f168ca Binary files /dev/null and b/apps/messageicons/icons/mattermost.png differ diff --git a/apps/messageicons/icons/mcdonalds.png b/apps/messageicons/icons/mcdonalds.png new file mode 100644 index 000000000..efe4088a4 Binary files /dev/null and b/apps/messageicons/icons/mcdonalds.png differ diff --git a/apps/messageicons/icons/message.png b/apps/messageicons/icons/message.png new file mode 100644 index 000000000..a93cb3f4c Binary files /dev/null and b/apps/messageicons/icons/message.png differ diff --git a/apps/messageicons/icons/netflix.png b/apps/messageicons/icons/netflix.png new file mode 100644 index 000000000..d956a103b Binary files /dev/null and b/apps/messageicons/icons/netflix.png differ diff --git a/apps/messageicons/icons/news.png b/apps/messageicons/icons/news.png new file mode 100644 index 000000000..8e75513e9 Binary files /dev/null and b/apps/messageicons/icons/news.png differ diff --git a/apps/messageicons/icons/notification.png b/apps/messageicons/icons/notification.png new file mode 100644 index 000000000..c29a6025c Binary files /dev/null and b/apps/messageicons/icons/notification.png differ diff --git a/apps/messageicons/icons/onedrive.png b/apps/messageicons/icons/onedrive.png new file mode 100644 index 000000000..ff2b08304 Binary files /dev/null and b/apps/messageicons/icons/onedrive.png differ diff --git a/apps/messageicons/icons/outlook.png b/apps/messageicons/icons/outlook.png new file mode 100644 index 000000000..5519ccd4c Binary files /dev/null and b/apps/messageicons/icons/outlook.png differ diff --git a/apps/messageicons/icons/paypal.png b/apps/messageicons/icons/paypal.png new file mode 100644 index 000000000..cd76aae90 Binary files /dev/null and b/apps/messageicons/icons/paypal.png differ diff --git a/apps/messageicons/icons/phone.png b/apps/messageicons/icons/phone.png new file mode 100644 index 000000000..376170f7c Binary files /dev/null and b/apps/messageicons/icons/phone.png differ diff --git a/apps/messageicons/icons/playstation.png b/apps/messageicons/icons/playstation.png new file mode 100644 index 000000000..a97a38964 Binary files /dev/null and b/apps/messageicons/icons/playstation.png differ diff --git a/apps/messageicons/icons/plex.png b/apps/messageicons/icons/plex.png new file mode 100644 index 000000000..a0840b751 Binary files /dev/null and b/apps/messageicons/icons/plex.png differ diff --git a/apps/messageicons/icons/pocket.png b/apps/messageicons/icons/pocket.png new file mode 100644 index 000000000..d34f2e399 Binary files /dev/null and b/apps/messageicons/icons/pocket.png differ diff --git a/apps/messageicons/icons/podcast.png b/apps/messageicons/icons/podcast.png new file mode 100644 index 000000000..1be0f22b6 Binary files /dev/null and b/apps/messageicons/icons/podcast.png differ diff --git a/apps/messageicons/icons/pokeball.png b/apps/messageicons/icons/pokeball.png new file mode 100644 index 000000000..d023761eb Binary files /dev/null and b/apps/messageicons/icons/pokeball.png differ diff --git a/apps/messageicons/icons/protonmail.png b/apps/messageicons/icons/protonmail.png new file mode 100644 index 000000000..065607c47 Binary files /dev/null and b/apps/messageicons/icons/protonmail.png differ diff --git a/apps/messageicons/icons/protonvpn.png b/apps/messageicons/icons/protonvpn.png new file mode 100644 index 000000000..6d837a3e2 Binary files /dev/null and b/apps/messageicons/icons/protonvpn.png differ diff --git a/apps/messageicons/icons/radio.png b/apps/messageicons/icons/radio.png new file mode 100644 index 000000000..ea1cbffe1 Binary files /dev/null and b/apps/messageicons/icons/radio.png differ diff --git a/apps/messageicons/icons/reddit.png b/apps/messageicons/icons/reddit.png new file mode 100644 index 000000000..96fcce901 Binary files /dev/null and b/apps/messageicons/icons/reddit.png differ diff --git a/apps/messageicons/icons/restaurant.png b/apps/messageicons/icons/restaurant.png new file mode 100644 index 000000000..9bf1fcea9 Binary files /dev/null and b/apps/messageicons/icons/restaurant.png differ diff --git a/apps/messageicons/icons/router.png b/apps/messageicons/icons/router.png new file mode 100644 index 000000000..4446342f6 Binary files /dev/null and b/apps/messageicons/icons/router.png differ diff --git a/apps/messageicons/icons/rss.png b/apps/messageicons/icons/rss.png new file mode 100644 index 000000000..b248b70e9 Binary files /dev/null and b/apps/messageicons/icons/rss.png differ diff --git a/apps/messageicons/icons/rust.png b/apps/messageicons/icons/rust.png new file mode 100644 index 000000000..b74eb6ec4 Binary files /dev/null and b/apps/messageicons/icons/rust.png differ diff --git a/apps/messageicons/icons/security.png b/apps/messageicons/icons/security.png new file mode 100644 index 000000000..b8cc5c77e Binary files /dev/null and b/apps/messageicons/icons/security.png differ diff --git a/apps/messageicons/icons/shopping.png b/apps/messageicons/icons/shopping.png new file mode 100644 index 000000000..f966188b8 Binary files /dev/null and b/apps/messageicons/icons/shopping.png differ diff --git a/apps/messageicons/icons/signal.png b/apps/messageicons/icons/signal.png new file mode 100644 index 000000000..e8508706f Binary files /dev/null and b/apps/messageicons/icons/signal.png differ diff --git a/apps/messageicons/icons/skype.png b/apps/messageicons/icons/skype.png new file mode 100644 index 000000000..867a8feb6 Binary files /dev/null and b/apps/messageicons/icons/skype.png differ diff --git a/apps/messageicons/icons/slack.png b/apps/messageicons/icons/slack.png new file mode 100644 index 000000000..7a5a5a71c Binary files /dev/null and b/apps/messageicons/icons/slack.png differ diff --git a/apps/messageicons/icons/snapchat.png b/apps/messageicons/icons/snapchat.png new file mode 100644 index 000000000..42daddbaf Binary files /dev/null and b/apps/messageicons/icons/snapchat.png differ diff --git a/apps/messageicons/icons/steam.png b/apps/messageicons/icons/steam.png new file mode 100644 index 000000000..f6212cdfb Binary files /dev/null and b/apps/messageicons/icons/steam.png differ diff --git a/apps/messageicons/icons/syncthing.png b/apps/messageicons/icons/syncthing.png new file mode 100644 index 000000000..174384aba Binary files /dev/null and b/apps/messageicons/icons/syncthing.png differ diff --git a/apps/messageicons/icons/task.png b/apps/messageicons/icons/task.png new file mode 100644 index 000000000..c43d355c4 Binary files /dev/null and b/apps/messageicons/icons/task.png differ diff --git a/apps/messageicons/icons/taxi.png b/apps/messageicons/icons/taxi.png new file mode 100644 index 000000000..b577eef0e Binary files /dev/null and b/apps/messageicons/icons/taxi.png differ diff --git a/apps/messageicons/icons/teams.png b/apps/messageicons/icons/teams.png new file mode 100644 index 000000000..5160b007e Binary files /dev/null and b/apps/messageicons/icons/teams.png differ diff --git a/apps/messageicons/icons/telegram.png b/apps/messageicons/icons/telegram.png new file mode 100644 index 000000000..fe8051006 Binary files /dev/null and b/apps/messageicons/icons/telegram.png differ diff --git a/apps/messageicons/icons/terminal.png b/apps/messageicons/icons/terminal.png new file mode 100644 index 000000000..10a35e71f Binary files /dev/null and b/apps/messageicons/icons/terminal.png differ diff --git a/apps/messageicons/icons/thermostat.png b/apps/messageicons/icons/thermostat.png new file mode 100644 index 000000000..8e96f6241 Binary files /dev/null and b/apps/messageicons/icons/thermostat.png differ diff --git a/apps/messageicons/icons/threema.png b/apps/messageicons/icons/threema.png new file mode 100644 index 000000000..401660b5c Binary files /dev/null and b/apps/messageicons/icons/threema.png differ diff --git a/apps/messageicons/icons/tiktok.png b/apps/messageicons/icons/tiktok.png new file mode 100644 index 000000000..99afd3dd9 Binary files /dev/null and b/apps/messageicons/icons/tiktok.png differ diff --git a/apps/messageicons/icons/transit.png b/apps/messageicons/icons/transit.png new file mode 100644 index 000000000..3ec108194 Binary files /dev/null and b/apps/messageicons/icons/transit.png differ diff --git a/apps/messageicons/icons/twitch.png b/apps/messageicons/icons/twitch.png new file mode 100644 index 000000000..cd7d479c1 Binary files /dev/null and b/apps/messageicons/icons/twitch.png differ diff --git a/apps/messageicons/icons/twitter.png b/apps/messageicons/icons/twitter.png new file mode 100644 index 000000000..88df293f8 Binary files /dev/null and b/apps/messageicons/icons/twitter.png differ diff --git a/apps/messageicons/icons/videoconf.png b/apps/messageicons/icons/videoconf.png new file mode 100644 index 000000000..9b420341a Binary files /dev/null and b/apps/messageicons/icons/videoconf.png differ diff --git a/apps/messageicons/icons/vlc.png b/apps/messageicons/icons/vlc.png new file mode 100644 index 000000000..74949aded Binary files /dev/null and b/apps/messageicons/icons/vlc.png differ diff --git a/apps/messageicons/icons/voicemail.png b/apps/messageicons/icons/voicemail.png new file mode 100644 index 000000000..2c1972a56 Binary files /dev/null and b/apps/messageicons/icons/voicemail.png differ diff --git a/apps/messageicons/icons/wallet.png b/apps/messageicons/icons/wallet.png new file mode 100644 index 000000000..536cae2ba Binary files /dev/null and b/apps/messageicons/icons/wallet.png differ diff --git a/apps/messageicons/icons/warning.png b/apps/messageicons/icons/warning.png new file mode 100644 index 000000000..59080713f Binary files /dev/null and b/apps/messageicons/icons/warning.png differ diff --git a/apps/messageicons/icons/webhook.png b/apps/messageicons/icons/webhook.png new file mode 100644 index 000000000..7562fd759 Binary files /dev/null and b/apps/messageicons/icons/webhook.png differ diff --git a/apps/messageicons/icons/wechat.png b/apps/messageicons/icons/wechat.png new file mode 100644 index 000000000..55f4bd6a9 Binary files /dev/null and b/apps/messageicons/icons/wechat.png differ diff --git a/apps/messageicons/icons/whatsapp.png b/apps/messageicons/icons/whatsapp.png new file mode 100644 index 000000000..d6d89bc0c Binary files /dev/null and b/apps/messageicons/icons/whatsapp.png differ diff --git a/apps/messageicons/icons/xbox.png b/apps/messageicons/icons/xbox.png new file mode 100644 index 000000000..dce76128d Binary files /dev/null and b/apps/messageicons/icons/xbox.png differ diff --git a/apps/messageicons/icons/youtube.png b/apps/messageicons/icons/youtube.png new file mode 100644 index 000000000..93e50ccad Binary files /dev/null and b/apps/messageicons/icons/youtube.png differ diff --git a/apps/messageicons/lib.js b/apps/messageicons/lib.js new file mode 100644 index 000000000..ff9f4b680 --- /dev/null +++ b/apps/messageicons/lib.js @@ -0,0 +1,104 @@ +exports.getImage = function(msg) { + /* + * icons should be 24x24px or less with 1bpp colors and 'Transparency to Color' + * http://www.espruino.com/Image+Converter + */ + if (msg.img) return atob(msg.img); + const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase(); + if (s=="airbnb") return atob("GBgBAAAAAAAAAAAAADwAAH4AAGYAAMMAAIEAAYGAAYGAAzzAA2bABmZgBmZgDGYwDDwwCDwQCBgQDDwwB+fgA8PAAAAAAAAAAAAA"); + if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA="); + if (s=="bibel") return atob("GBgBAAAAA//wD//4D//4H//4H/f4H/f4H+P4H4D4H4D4H/f4H/f4H/f4H/f4H/f4H//4H//4H//4GAAAEAAAEAAACAAAB//4AAAA"); + if (s=="bring") return atob("GBgBAAAAAAAAAAAAAAAAAHwAAFoAAf+AA/+AA/+AA/+AA/eAA+eAA0+AAx+AA7+AA/+AA//AA/+AAf8AAAIAAAAAAAAAAAAAAAAA"); + if (s=="calendar" || s=="etar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA=="); + if (s=="corona-warn") return atob("GBgBAAAAABwAAP+AAf/gA//wB/PwD/PgDzvAHzuAP8EAP8AAPAAAPMAAP8AAH8AAHzsADzuAB/PAB/PgA//wAP/gAH+AAAwAAAAA"); + if (s=="discord") return atob("GBgBAAAAAAAAAAAAAIEABwDgDP8wH//4H//4P//8P//8P//8Pjx8fhh+fzz+f//+f//+e//ePH48HwD4AgBAAAAAAAAAAAAAAAAA"); + if (s=="facebook" || s=="messenger") return atob("GBiBAAAAAAAAAAAYAAD/AAP/wAf/4A/48A/g8B/g+B/j+B/n+D/n/D8A/B8A+B+B+B/n+A/n8A/n8Afn4APnwADnAAAAAAAAAAAAAA=="); + if (s=="gmx") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEJmfmd8Zuc85v847/88Z9s8fttmHIHiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + if (s=="google") return atob("GBiBAAAAAAD/AAP/wAf/4A/D4B8AwDwAADwAAHgAAHgAAHAAAHAH/nAH/nAH/ngH/ngAHjwAPDwAfB8A+A/D8Af/4AP/wAD/AAAAAA=="); + if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA=="); // 2 bit unpaletted + if (s=="home assistant") return atob("FhaBAAAAAADAAAeAAD8AAf4AD/3AfP8D7fwft/D/P8ec572zbzbNsOEhw+AfD8D8P4fw/z/D/P8P8/w/z/AAAAA="); + if (s=="instagram") return atob("GBiBAAAAAAAAAAAAAAAAAAP/wAYAYAwAMAgAkAh+EAjDEAiBEAiBEAiBEAiBEAjDEAh+EAgAEAwAMAYAYAP/wAAAAAAAAAAAAAAAAA=="); + if (s=="kalender") return atob("GBgBBgBgBQCgff++RQCiRgBiQAACf//+QAACQAACR//iRJkiRIEiR//iRNsiRIEiRJkiR//iRIEiRIEiR//iQAACQAACf//+AAAA"); + if (s=="lieferando") return atob("GBgBABgAAH5wAP9wAf/4A//4B//4D//4H//4P/88fV8+fV4//V4//Vw/HVw4HVw4HBg4HBg4HBg4HDg4Hjw4Hj84Hj44Hj44Hj44"); + if (s=="mattermost") return atob("GBgBAAAAAPAAA+EAB4MADgcYHAcYOA8MOB8OeD8GcD8GcH8GcD8HcD8HeBwHeAAOfAAOfgAePwA8P8D8H//4D//wB//gAf/AAH4A"); + if (s=="n26") return atob("GBgBAAAAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAOIAAOIAAPIAANoAANoAAM4AAMYAAMYAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAA"); + if (s=="nextbike") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAACAfgDAPwDAP4HAH4N4H8f8D82GMd8CMDsDMGMDMGGGGMHOD4D8AAAAAAAAAAAAAAAAAAAAAAAA"); + if (s=="nina") return atob("GBgBAAAABAAQCAAICAAIEAAEEgAkJAgSJBwSKRxKSj4pUn8lVP+VVP+VUgAlSgApKQBKJAASJAASEgAkEAAECAAICAAIBAAQAAAA"); + if (s=="outlook mail") return atob("HBwBAAAAAAAAAAAIAAAfwAAP/gAB/+AAP/5/A//v/D/+/8P/7/g+Pv8Dye/gPd74w5znHDnOB8Oc4Pw8nv/Dwe/8Pj7/w//v/D/+/8P/7/gf/gAA/+AAAfwAAACAAAAAAAAAAAA="); + if (s=="paypal") return atob("GBgBAAAAAAAAAAAAAf+AAf/AAf/gA//gA//gA//wA//wA//wA//wB//wB//wB//gB/+AB/gAB/gAB/gAAPgAAPgAAAAAAAAAAAAA"); + if (s=="phone") return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA="); + if (s=="post & dhl") return atob("GBgBAPgAE/5wMwZ8NgN8NgP4NgP4HgP4HgPwDwfgD//AB/+AAf8AAAAABs7AHcdgG4MwAAAAGESAFESAEkSAEnyAEkSAFESAGETw"); + if (s=="signal") return atob("GBgBAAAAAGwAAQGAAhggCP8QE//AB//oJ//kL//wD//0D//wT//wD//wL//0J//kB//oA//ICf8ABfxgBYBAADoABMAABAAAAAAA"); + 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=="snapchat") return atob("GBgBAAAAAAAAAH4AAf+AAf+AA//AA//AA//AA//AA//AH//4D//wB//gA//AB//gD//wH//4f//+P//8D//wAf+AAH4AAAAAAAAA"); + if (s=="steam") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAAfgAAwwAAvQABvQABvQADvQgDww4H/g+f8A/zwAf9gAH9AAB8AAACAAAcAAAAAAAAAAAAAAAAAA"); + if (s=="teams") return atob("GBgBAAAAAAAAAAQAAB4AAD8IAA8cP/M+f/scf/gIeDgAfvvefvvffvvffvvffvvff/vff/veP/PeAA/cAH/AAD+AAD8AAAQAAAAA"); + if (s=="telegram" || s=="telegram foss") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA=="); + if (s=="threema") return atob("GBjB/4Yx//8AAAAAAAAAAAAAfgAB/4AD/8AH/+AH/+AP//AP2/APw/APw/AHw+AH/+AH/8AH/4AH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); + if (s=="to do" || s=="opentasks") return atob("GBgBAAAAAAAAAAAwAAB4AAD8AAH+AAP/DAf/Hg//Px/+f7/8///4///wf//gP//AH/+AD/8AB/4AA/wAAfgAAPAAAGAAAAAAAAAA"); + if (s=="twitch") return atob("GBgBH//+P//+P//+eAAGeAAGeAAGeDGGeDOGeDOGeDOGeDOGeDOGeDOGeAAOeAAOeAAcf4/4f5/wf7/gf//Af/+AA/AAA+AAAcAA"); + 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=="warnapp") return atob("GBgBAAAAAAAAAAAAAH4AAP8AA//AA//AD//gP//gf//4f//+/+P+/8H//8n//4n/fxh/fzg+Pj88Dn44AA4AAAwAAAwAAAgAAAAA"); + 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=="); + if (s=="wordfeud") return atob("GBgCWqqqqqqlf//////9v//////+v/////++v/////++v8///Lu+v8///L++v8///P/+v8v//P/+v9v//P/+v+fx/P/+v+Pk+P/+v/PN+f/+v/POuv/+v/Ofdv/+v/NvM//+v/I/Y//+v/k/k//+v/i/w//+v/7/6//+v//////+v//////+f//////9Wqqqqqql"); + if (s=="youtube" || s=="newpipe") return atob("GBgBAAAAAAAAAAAAAAAAAf8AH//4P//4P//8P//8P5/8P4/8f4P8f4P8P4/8P5/8P//8P//8P//4H//4Af8AAAAAAAAAAAAAAAAA"); + if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); + // if (s=="sms message" || s=="mail" || s=="gmail") // .. default icon (below) + return atob("FhKBAH//+P//yf/+c//z5/+fz/z/n+f/Pz/+ef/8D///////////////////////f//4///A"); +}; + +exports.getColor = function(msg,options) { + options = options||{}; + var st = options.settings || require('Storage').readJSON("messages.settings.json", 1) || {}; + if (options.default===undefined) options.default=g.theme.fg; + if (st.iconColorMode == 'mono') return options.default; + const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase(); + return { + // generic colors, using B2-safe colors + // DO NOT USE BLACK OR WHITE HERE, just leave the declaration out and then the theme's fg color will be used + "airbnb": "#f00", + "mail": "#ff0", + "music": "#f0f", + "phone": "#0f0", + "sms message": "#0ff", + // brands, according to https://www.schemecolor.com/?s (picking one for multicolored logos) + // all dithered on B2, but we only use the color for the icons. (Could maybe pick the closest 3-bit color for B2?) + "bibel": "#54342c", + "bring": "#455a64", + "discord": "#738adb", + "etar": "#36a18b", + "facebook": "#4267b2", + "gmail": "#ea4335", + "gmx": "#1c449b", + "google": "#4285F4", + "google home": "#fbbc05", +// "home assistant": "#41bdf5", // ha-blue is #41bdf5, but that's the background + "instagram": "#dd2a7b", + "lieferando": "#ee5c00", + "messenger": "#0078ff", + "mattermost": "#00f", + "n26": "#36a18b", + "nextbike": "#00f", + "newpipe": "#f00", + "nina": "#e57004", + "opentasks": "#409f8f", + "outlook mail": "#0072c6", + "paypal": "#003087", + "post & dhl": "#f2c101", + "signal": "#00f", + "skype": "#00aff0", + "slack": "#e51670", + "snapchat": "#ff0", + "steam": "#171a21", + "teams": "#464eb8", + "telegram": "#0088cc", + "telegram foss": "#0088cc", + "to do": "#3999e5", + "twitch": "#6441A4", + "twitter": "#1da1f2", + "whatsapp": "#4fce5d", + "wordfeud": "#e7d3c7", + "youtube": "#f00", + }[s]||options.default; +}; diff --git a/apps/messageicons/metadata.json b/apps/messageicons/metadata.json new file mode 100644 index 000000000..eb907f893 --- /dev/null +++ b/apps/messageicons/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "messageicons", + "name": "Message Icons", + "version": "0.01", + "description": "Library containing a list of icons and colors for apps", + "icon": "app.png", + "type": "module", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "provides_modules" : ["messageicons"], + "storage": [ + {"name":"messageicons","url":"lib.js"} + ] +} diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 166ff64ae..ee27d41c6 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -73,3 +73,5 @@ Move WIDGETS.messages.buzz() to require("messages").buzz() 0.52: Fix require("messages").buzz() regression Fix background color in messages list after one unread message is shown +0.53: Messages now uses Bangle.load() to load messages app faster (if possible) +0.54: Move icons out to messageicons module diff --git a/apps/messages/app.js b/apps/messages/app.js index f6226d178..bebd92816 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -316,10 +316,14 @@ function showMessage(msgid) { {type:"txt", font:fontSmall, label:msg.src||/*LANG*/"Message", bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2, halign:1 }, title?{type:"txt", font:titleFont, label:title, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2 }:{}, ]}, - { type:"btn", src:require("messages").getMessageImage(msg), col:require("messages").getMessageImageCol(msg, g.theme.fg2), pad: 3, cb:()=>{ - cancelReloadTimeout(); // don't auto-reload to clock now - showMessageSettings(msg); - }}, + { type:"btn", + src:require("messageicons").getImage(msg), + col:require("messageicons").getColor(msg, {settings:settings, default:g.theme.fg2}), + pad: 3, cb:()=>{ + cancelReloadTimeout(); // don't auto-reload to clock now + showMessageSettings(msg); + } + }, ]}, {type:"txt", font:bodyFont, label:body, fillx:1, filly:1, pad:2, cb:()=>{ // allow tapping to show a larger version @@ -382,14 +386,15 @@ function checkMessages(options) { g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h); if (!msg) return; var x = r.x+2, title = msg.title, body = msg.body; - var img = require("messages").getMessageImage(msg); + var img = require("messageicons").getImage(msg); if (msg.id=="music") { title = msg.artist || /*LANG*/"Music"; body = msg.track; } if (img) { - var fg = g.getColor(); - g.setColor(require("messages").getMessageImageCol(msg,fg)).drawImage(img, x+24, r.y+24, {rotate:0}) // force centering + var fg = g.getColor(), + col = require("messageicons").getColor(msg, {settings:settings, default:fg}); + g.setColor(col).drawImage(img, x+24, r.y+24, {rotate:0}) // force centering .setColor(fg); // only color the icon x += 50; } diff --git a/apps/messages/lib.js b/apps/messages/lib.js index 0188342ee..0ed03e4b6 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -54,7 +54,7 @@ exports.pushMessage = function(event) { // ok, saved now if (event.id=="music" && Bangle.CLOCK && messages[mIdx].new && openMusic()) { // just load the app to display music: no buzzing - load("messages.app.js"); + Bangle.load("messages.app.js"); } else if (event.t!="add") { // we only care if it's new return; @@ -81,7 +81,7 @@ exports.pushMessage = function(event) { Bangle.setLCDPower(1); // turn screen on } // we will buzz when we enter the messages app - return load("messages.new.js"); + return Bangle.load("messages.new.js"); } if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.update(messages); exports.buzz(message.src); @@ -142,22 +142,22 @@ exports.getMessages = function() { exports.buzz = function(msgSrc) { exports.stopBuzz(); // cancel any previous buzz timeouts if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return Promise.resolve(); // never buzz during Quiet Mode - + var msgSettings = require('Storage').readJSON("messages.settings.json", true) || {}; var pattern; if (msgSrc && msgSrc.toLowerCase() === "phone") { // special vibration pattern for incoming calls - pattern = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrateCalls; + pattern = msgSettings.vibrateCalls; } else { - pattern = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrate; + pattern = msgSettings.vibrate; } if (pattern === undefined) { pattern = ":"; } // pattern may be "", so we can't use || ":" here if (!pattern) return Promise.resolve(); - var repeat = (require('Storage').readJSON("messages.settings.json", true) || {}).repeat; + var repeat = msgSettings.repeat; if (repeat===undefined) repeat=4; // repeat may be zero if (repeat) { exports.buzzTimeout = setTimeout(()=>require("buzz").pattern(pattern), repeat*1000); - var vibrateTimeout = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrateTimeout; + var vibrateTimeout = msgSettings.vibrateTimeout; if (vibrateTimeout===undefined) vibrateTimeout=60; if (vibrateTimeout && !exports.stopTimeout) exports.stopTimeout = setTimeout(exports.stopBuzz, vibrateTimeout*1000); } @@ -172,107 +172,3 @@ exports.stopBuzz = function() { if (exports.stopTimeout) clearTimeout(exports.stopTimeout); delete exports.stopTimeout; }; - -exports.getMessageImage = function(msg) { - /* - * icons should be 24x24px or less with 1bpp colors and 'Transparency to Color' - * http://www.espruino.com/Image+Converter - */ - if (msg.img) return atob(msg.img); - const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase(); - if (s=="airbnb") return atob("GBgBAAAAAAAAAAAAADwAAH4AAGYAAMMAAIEAAYGAAYGAAzzAA2bABmZgBmZgDGYwDDwwCDwQCBgQDDwwB+fgA8PAAAAAAAAAAAAA"); - if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA="); - if (s=="bibel") return atob("GBgBAAAAA//wD//4D//4H//4H/f4H/f4H+P4H4D4H4D4H/f4H/f4H/f4H/f4H/f4H//4H//4H//4GAAAEAAAEAAACAAAB//4AAAA"); - if (s=="bring") return atob("GBgBAAAAAAAAAAAAAAAAAHwAAFoAAf+AA/+AA/+AA/+AA/eAA+eAA0+AAx+AA7+AA/+AA//AA/+AAf8AAAIAAAAAAAAAAAAAAAAA"); - if (s=="calendar" || s=="etar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA=="); - if (s=="corona-warn") return atob("GBgBAAAAABwAAP+AAf/gA//wB/PwD/PgDzvAHzuAP8EAP8AAPAAAPMAAP8AAH8AAHzsADzuAB/PAB/PgA//wAP/gAH+AAAwAAAAA"); - if (s=="discord") return atob("GBgBAAAAAAAAAAAAAIEABwDgDP8wH//4H//4P//8P//8P//8Pjx8fhh+fzz+f//+f//+e//ePH48HwD4AgBAAAAAAAAAAAAAAAAA"); - if (s=="facebook" || s=="messenger") return atob("GBiBAAAAAAAAAAAYAAD/AAP/wAf/4A/48A/g8B/g+B/j+B/n+D/n/D8A/B8A+B+B+B/n+A/n8A/n8Afn4APnwADnAAAAAAAAAAAAAA=="); - if (s=="gmx") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEJmfmd8Zuc85v847/88Z9s8fttmHIHiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); - if (s=="google") return atob("GBiBAAAAAAD/AAP/wAf/4A/D4B8AwDwAADwAAHgAAHgAAHAAAHAH/nAH/nAH/ngH/ngAHjwAPDwAfB8A+A/D8Af/4AP/wAD/AAAAAA=="); - if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA=="); // 2 bit unpaletted - if (s=="home assistant") return atob("FhaBAAAAAADAAAeAAD8AAf4AD/3AfP8D7fwft/D/P8ec572zbzbNsOEhw+AfD8D8P4fw/z/D/P8P8/w/z/AAAAA="); - if (s=="instagram") return atob("GBiBAAAAAAAAAAAAAAAAAAP/wAYAYAwAMAgAkAh+EAjDEAiBEAiBEAiBEAiBEAjDEAh+EAgAEAwAMAYAYAP/wAAAAAAAAAAAAAAAAA=="); - if (s=="kalender") return atob("GBgBBgBgBQCgff++RQCiRgBiQAACf//+QAACQAACR//iRJkiRIEiR//iRNsiRIEiRJkiR//iRIEiRIEiR//iQAACQAACf//+AAAA"); - if (s=="lieferando") return atob("GBgBABgAAH5wAP9wAf/4A//4B//4D//4H//4P/88fV8+fV4//V4//Vw/HVw4HVw4HBg4HBg4HBg4HDg4Hjw4Hj84Hj44Hj44Hj44"); - if (s=="mattermost") return atob("GBgBAAAAAPAAA+EAB4MADgcYHAcYOA8MOB8OeD8GcD8GcH8GcD8HcD8HeBwHeAAOfAAOfgAePwA8P8D8H//4D//wB//gAf/AAH4A"); - if (s=="n26") return atob("GBgBAAAAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAOIAAOIAAPIAANoAANoAAM4AAMYAAMYAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAA"); - if (s=="nextbike") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAACAfgDAPwDAP4HAH4N4H8f8D82GMd8CMDsDMGMDMGGGGMHOD4D8AAAAAAAAAAAAAAAAAAAAAAAA"); - if (s=="nina") return atob("GBgBAAAABAAQCAAICAAIEAAEEgAkJAgSJBwSKRxKSj4pUn8lVP+VVP+VUgAlSgApKQBKJAASJAASEgAkEAAECAAICAAIBAAQAAAA"); - if (s=="outlook mail") return atob("HBwBAAAAAAAAAAAIAAAfwAAP/gAB/+AAP/5/A//v/D/+/8P/7/g+Pv8Dye/gPd74w5znHDnOB8Oc4Pw8nv/Dwe/8Pj7/w//v/D/+/8P/7/gf/gAA/+AAAfwAAACAAAAAAAAAAAA="); - if (s=="paypal") return atob("GBgBAAAAAAAAAAAAAf+AAf/AAf/gA//gA//gA//wA//wA//wA//wB//wB//wB//gB/+AB/gAB/gAB/gAAPgAAPgAAAAAAAAAAAAA"); - if (s=="phone") return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA="); - if (s=="post & dhl") return atob("GBgBAPgAE/5wMwZ8NgN8NgP4NgP4HgP4HgPwDwfgD//AB/+AAf8AAAAABs7AHcdgG4MwAAAAGESAFESAEkSAEnyAEkSAFESAGETw"); - if (s=="signal") return atob("GBgBAAAAAGwAAQGAAhggCP8QE//AB//oJ//kL//wD//0D//wT//wD//wL//0J//kB//oA//ICf8ABfxgBYBAADoABMAABAAAAAAA"); - 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=="snapchat") return atob("GBgBAAAAAAAAAH4AAf+AAf+AA//AA//AA//AA//AA//AH//4D//wB//gA//AB//gD//wH//4f//+P//8D//wAf+AAH4AAAAAAAAA"); - if (s=="steam") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAAfgAAwwAAvQABvQABvQADvQgDww4H/g+f8A/zwAf9gAH9AAB8AAACAAAcAAAAAAAAAAAAAAAAAA"); - if (s=="teams") return atob("GBgBAAAAAAAAAAQAAB4AAD8IAA8cP/M+f/scf/gIeDgAfvvefvvffvvffvvffvvff/vff/veP/PeAA/cAH/AAD+AAD8AAAQAAAAA"); - if (s=="telegram" || s=="telegram foss") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA=="); - if (s=="threema") return atob("GBjB/4Yx//8AAAAAAAAAAAAAfgAB/4AD/8AH/+AH/+AP//AP2/APw/APw/AHw+AH/+AH/8AH/4AH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); - if (s=="to do" || s=="opentasks") return atob("GBgBAAAAAAAAAAAwAAB4AAD8AAH+AAP/DAf/Hg//Px/+f7/8///4///wf//gP//AH/+AD/8AB/4AA/wAAfgAAPAAAGAAAAAAAAAA"); - if (s=="twitch") return atob("GBgBH//+P//+P//+eAAGeAAGeAAGeDGGeDOGeDOGeDOGeDOGeDOGeDOGeAAOeAAOeAAcf4/4f5/wf7/gf//Af/+AA/AAA+AAAcAA"); - 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=="warnapp") return atob("GBgBAAAAAAAAAAAAAH4AAP8AA//AA//AD//gP//gf//4f//+/+P+/8H//8n//4n/fxh/fzg+Pj88Dn44AA4AAAwAAAwAAAgAAAAA"); - 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=="); - if (s=="wordfeud") return atob("GBgCWqqqqqqlf//////9v//////+v/////++v/////++v8///Lu+v8///L++v8///P/+v8v//P/+v9v//P/+v+fx/P/+v+Pk+P/+v/PN+f/+v/POuv/+v/Ofdv/+v/NvM//+v/I/Y//+v/k/k//+v/i/w//+v/7/6//+v//////+v//////+f//////9Wqqqqqql"); - if (s=="youtube" || s=="newpipe") return atob("GBgBAAAAAAAAAAAAAAAAAf8AH//4P//4P//8P//8P5/8P4/8f4P8f4P8P4/8P5/8P//8P//8P//4H//4Af8AAAAAAAAAAAAAAAAA"); - if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); - // if (s=="sms message" || s=="mail" || s=="gmail") // .. default icon (below) - return atob("FhKBAH//+P//yf/+c//z5/+fz/z/n+f/Pz/+ef/8D///////////////////////f//4///A"); -}; - -exports.getMessageImageCol = function(msg,def) { - let iconColorMode = (require('Storage').readJSON("messages.settings.json", 1) || {}).iconColorMode; - if (iconColorMode == 'mono') - return g.theme.fg; - const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase(); - return { - // generic colors, using B2-safe colors - // DO NOT USE BLACK OR WHITE HERE, just leave the declaration out and then the theme's fg color will be used - "airbnb": "#f00", - "mail": "#ff0", - "music": "#f0f", - "phone": "#0f0", - "sms message": "#0ff", - // brands, according to https://www.schemecolor.com/?s (picking one for multicolored logos) - // all dithered on B2, but we only use the color for the icons. (Could maybe pick the closest 3-bit color for B2?) - "bibel": "#54342c", - "bring": "#455a64", - "discord": "#738adb", - "etar": "#36a18b", - "facebook": "#4267b2", - "gmail": "#ea4335", - "gmx": "#1c449b", - "google": "#4285F4", - "google home": "#fbbc05", -// "home assistant": "#41bdf5", // ha-blue is #41bdf5, but that's the background - "instagram": "#dd2a7b", - "lieferando": "#ee5c00", - "messenger": "#0078ff", - "mattermost": "#00f", - "n26": "#36a18b", - "nextbike": "#00f", - "newpipe": "#f00", - "nina": "#e57004", - "opentasks": "#409f8f", - "outlook mail": "#0072c6", - "paypal": "#003087", - "post & dhl": "#f2c101", - "signal": "#00f", - "skype": "#00aff0", - "slack": "#e51670", - "snapchat": "#ff0", - "steam": "#171a21", - "teams": "#464eb8", - "telegram": "#0088cc", - "telegram foss": "#0088cc", - "to do": "#3999e5", - "twitch": "#6441A4", - "twitter": "#1da1f2", - "whatsapp": "#4fce5d", - "wordfeud": "#e7d3c7", - "youtube": "#f00", - }[s]||(def !== undefined?def:g.theme.fg); -}; diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index a31c21e03..f3051958e 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,12 +1,13 @@ { "id": "messages", "name": "Messages", - "version": "0.52", + "version": "0.54", "description": "App to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", "tags": "tool,system", "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies" : { "messageicons":"module" }, "readme": "README.md", "storage": [ {"name":"messages.app.js","url":"app.js"}, diff --git a/apps/messages/widget.js b/apps/messages/widget.js index c8d132f82..c0dcd132f 100644 --- a/apps/messages/widget.js +++ b/apps/messages/widget.js @@ -21,7 +21,8 @@ WIDGETS["messages"]={area:"tl", width:0, draw:function(recall) { g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+23); for(let i = 0;i < msgsShown;i++) { const msg = this.msgs[i]; - const colors = [g.theme.bg, g.setColor(require("messages").getMessageImageCol(msg)).getColor()]; + const colors = [g.theme.bg, + require("messageicons").getColor(msg, {settings:settings})]; if (settings.flash && ((Date.now()/1000)&1)) { if (colors[1] == g.theme.fg) { colors.reverse(); @@ -31,7 +32,7 @@ WIDGETS["messages"]={area:"tl", width:0, draw:function(recall) { } g.setColor(colors[1]).setBgColor(colors[0]); // draw the icon, or '...' if too many messages - g.drawImage(i == (settings.maxMessages - 1) && this.msgs.length > settings.maxMessages ? atob("EASBAGGG88/zz2GG") : require("messages").getMessageImage(msg), + g.drawImage(i == (settings.maxMessages - 1) && this.msgs.length > settings.maxMessages ? atob("EASBAGGG88/zz2GG") : require("messageicons").getImage(msg), this.x + 12 + i * 24, this.y + 12, {rotate:0/*force centering*/}); } } diff --git a/apps/messages_light/ChangeLog b/apps/messages_light/ChangeLog new file mode 100644 index 000000000..328e2a120 --- /dev/null +++ b/apps/messages_light/ChangeLog @@ -0,0 +1,7 @@ +1.0: New App! +1.1: fix app opening when a remove notification arrives +1.2: message_light overrides require() by sending requests to "message" to a proxy library which overrides pushMessage + settings now points to message settings + implemented use of the "messageicons" library + removed lib no longer used +1.3: icon changed \ No newline at end of file diff --git a/apps/messages_light/README.md b/apps/messages_light/README.md new file mode 100644 index 000000000..00fe39bd0 --- /dev/null +++ b/apps/messages_light/README.md @@ -0,0 +1,11 @@ +# Messages app + +This app handles the display of messages and message notifications. + +It is a GUI replacement for the `messages` apps. + + +## Creator + +Rarder44 + diff --git a/apps/messages_light/app-icon.js b/apps/messages_light/app-icon.js new file mode 100644 index 000000000..7d1da35c9 --- /dev/null +++ b/apps/messages_light/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA/4ACBIMQwhL/ABMBqoAEoALDioLFqgLDBQoABERIkEBZcFBY9QBYVe1QAB1YLGrSlC/YLGrYHCr4Lrr9drpLC1oLEAAN5rxKB/ILHEYV5EY4LIHYoLorRaBqoPCBYlfUoXrBYwGBrdeDIILIvXVBZFa1I+CBY/5BZIHBBwOq1ILGrXVvf//oLGq+trLLFBYVVvQxCBY9XJIQLCgILDHoVVoALHAAQLCgALHBQUAioKFqgLDEgwiDAH4AGA")) \ No newline at end of file diff --git a/apps/messages_light/app-icon.png b/apps/messages_light/app-icon.png new file mode 100644 index 000000000..c9b4b62ac Binary files /dev/null and b/apps/messages_light/app-icon.png differ diff --git a/apps/messages_light/app.png b/apps/messages_light/app.png new file mode 100644 index 000000000..1f738504d Binary files /dev/null and b/apps/messages_light/app.png differ diff --git a/apps/messages_light/full-size-app.png b/apps/messages_light/full-size-app.png new file mode 100644 index 000000000..2df7915ed Binary files /dev/null and b/apps/messages_light/full-size-app.png differ diff --git a/apps/messages_light/messages_light.app.js b/apps/messages_light/messages_light.app.js new file mode 100644 index 000000000..5d5363d38 --- /dev/null +++ b/apps/messages_light/messages_light.app.js @@ -0,0 +1,496 @@ +/* MESSAGES is a list of: + {id:int, + src, + title, + subject, + body, + sender, + tel:string, + new:true // not read yet + } +*/ + +let LOG=function(){ + //print.apply(null, arguments); +} + + + + +let settings= (()=>{ + let tmp={}; + tmp.NewEventFileName="messages_light.NewEvent.json"; + + tmp.fontSmall = "6x8"; + tmp.fontMedium = g.getFonts().includes("Vector")?"Vector:16":"6x8:2"; + tmp.fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2"; + tmp.fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4"; + + + tmp.colHeadBg = g.theme.dark ? "#141":"#4f4"; + tmp.colBg = g.theme.dark ? "#000":"#fff"; + tmp.colLock = g.theme.dark ? "#ff0000":"#ff0000"; + + tmp.quiet=((require('Storage').readJSON('setting.json', 1) || {}).quiet) + + return tmp; +})(); +let EventQueue=[]; //in posizione 0, c'è quello attualmente visualizzato +let callInProgress=false; + + + + +//TODO: RICORDARSI DI FARE IL DELETE +var manageEvent = function(event) { + + event.new=true; + + + LOG("manageEvent"); + if( event.id=="call") + { + showCall(event); + return; + } + switch(event.t) + { + case "add": + EventQueue.unshift(event); + + if(!callInProgress) + showMessage(event); + break; + + case "modify": + //cerco l'evento nella lista, se lo trovo, lo modifico, altrimenti lo pusho + let find=false; + EventQueue.forEach(element => { + if(element.id == event.id) + { + find=true; + Object.assign(element,event); + } + }); + if(!find) //se non l'ho trovato, lo aggiungo in fondo + EventQueue.unshift(event); + + if(!callInProgress) + showMessage(event); + break; + + case "remove": + + //se non c'è niente nella queue e non c'è una chiamata in corso + if( EventQueue.length==0 && !callInProgress) + next(); + + //se l'id è uguale a quello attualmente visualizzato ( e non siamo in chiamata ) + if(!callInProgress && EventQueue[0] !== undefined && EventQueue[0].id == event.id) + next(); //passo al messaggio successivo ( per la rimozione ci penserà la next ) + + else{ + //altrimenti rimuovo tutti gli elementi con quell'id( creando un nuovo array ) + let newEventQueue=[]; + EventQueue.forEach(element => { + if(element.id != event.id) + newEventQueue.push(element); + }); + EventQueue=newEventQueue; + } + + + + + break; + case "musicstate": + case "musicinfo": + + break; + } +}; + + + + + + +let showMessage = function(msg){ + LOG("showMessage"); + LOG(msg); + g.setBgColor(settings.colBg); + + + if(typeof msg.CanScrollDown==="undefined") + msg.CanScrollDown=false; + if(typeof msg.CanScrollUp==="undefined") + msg.CanScrollUp=false; + + + + + + // Normal text message display + let title=msg.title, titleFont = settings.fontLarge, lines; + if (title) { + let w = g.getWidth()-48; + if (g.setFont(titleFont).stringWidth(title) > w) + titleFont = settings.fontMedium; + if (g.setFont(titleFont).stringWidth(title) > w) { + lines = g.wrapString(title, w); + title = (lines.length>2) ? lines.slice(0,2).join("\n")+"..." : lines.join("\n"); + } + } + + + + let Layout = require("Layout"); + layout = new Layout({ type:"v", c: [ + {type:"h", fillx:1, bgCol:settings.colHeadBg, c: [ + { type:"btn", src:require("messageicons").getImage(msg), col:require("messageicons").getColor(msg), pad: 3}, + { type:"v", fillx:1, c: [ + {type:"txt", font:settings.fontSmall, label:msg.src||/*LANG*/"Message", bgCol:settings.colHeadBg, fillx:1, pad:2, halign:1 }, + title?{type:"txt", font:titleFont, label:title, bgCol:settings.colHeadBg, fillx:1, pad:2 }:{}, + ]}, + ]}, + {type:"v",fillx:1,filly:1,pad:2 ,halign:-1,c:[]}, + + + + + ]}); + + + if (!settings.quiet && msg.new) + { + msg.new=false; + Bangle.buzz(); + } + + + g.clearRect(Bangle.appRect); + layout.render(); + + PrintMessageStrings(msg); + Bangle.setLCDPower(1); + + DrawLock(); + +}; +let DrawLock=function() +{ + let w=8,h=8; + let x = g.getWidth()-w; + let y = 0; + if(Bangle.isLocked()) + g.setBgColor(settings.colLock); + else + g.setBgColor(settings.colHeadBg); + g.clearRect(x,y,x+w,y+h); +}; + + + + + + +let showCall = function(msg) +{ + LOG("showCall"); + LOG(msg); + // se anche prima era una call PrevMessage==msg.id + //non so perchè prima era cosi + if( msg.t=="remove") + { + LOG("hide call screen"); + next(); //dont shift + return; + } + + callInProgress=true; + + + + //se è una chiamata ( o una nuova chiamata, diversa dalla precedente ) + //la visualizzo + + let title=msg.title, titleFont = settings.fontLarge, lines; + if (title) { + let w = g.getWidth()-48; + if (g.setFont(titleFont).stringWidth(title) > w) + titleFont = settings.fontMedium; + if (g.setFont(titleFont).stringWidth(title) > w) { + lines = g.wrapString(title, w); + title = (lines.length>2) ? lines.slice(0,2).join("\n")+"..." : lines.join("\n"); + } + } + let Layout = require("Layout"); + layout = new Layout({ type:"v", c: [ + {type:"h", fillx:1, bgCol:settings.colHeadBg, c: [ + { type:"btn", src:require("messageicons").getImage(msg), col:require("messageicons").getColor(msg), pad: 3}, + { type:"v", fillx:1, c: [ + {type:"txt", font:settings.fontSmall, label:msg.src||/*LANG*/"Message", bgCol:settings.colHeadBg, fillx:1, pad:2, halign:1 }, + title?{type:"txt", font:titleFont, label:title, bgCol:settings.colHeadBg, fillx:1, pad:2 }:{}, + ]}, + ]}, + {type:"txt", font:settings.fontMedium, label:msg.body, fillx:1,filly:1,pad:2 ,halign:0} + ]}); + + + StopBuzzCall(); + if ( !settings.quiet ) { + if(msg.new) + { + msg.new=false; + CallBuzzTimer = setInterval(function() { + Bangle.buzz(500); + }, 1000); + + Bangle.buzz(500); + } + } + g.clearRect(Bangle.appRect); + layout.render(); + PrintMessageStrings(msg); + Bangle.setLCDPower(1); + DrawLock(); +}; + + + + + + + + + +let next=function(){ + LOG("next"); + StopBuzzCall(); + + + //se c'è una chiamata, non shifto + if(!callInProgress) + EventQueue.shift(); //passa al messaggio successivo, se presente - tolgo il primo + + callInProgress=false; + if( EventQueue.length == 0) + { + LOG("no element in queue - closing") + setTimeout(_ => load()); + return; + } + + + showMessage(EventQueue[0]); + +}; + + + + + + + + + + + +let showMapMessage=function(msg) { + + g.clearRect(Bangle.appRect); + PrintMessageStrings({body:"Not implemented!"}); + +} + + + + + +let CallBuzzTimer=null; +let StopBuzzCall=function() +{ + if (CallBuzzTimer){ + clearInterval(CallBuzzTimer); + CallBuzzTimer=null; + } +} +let DrawTriangleUp=function() +{ + g.fillPoly([169,46,164,56,174,56]); +} +let DrawTriangleDown=function() +{ + g.fillPoly([169,170,164,160,174,160]); +} + + + + +let ScrollUp=function() +{ + msg= EventQueue[0]; + + if(typeof msg.FirstLine==="undefined") + msg.FirstLine=0; + if(typeof msg.CanScrollUp==="undefined") + msg.CanScrollUp=false; + + if(!msg.CanScrollUp) return; + + msg.FirstLine = msg.FirstLine>0?msg.FirstLine-1:0; + + PrintMessageStrings(msg); +} +let ScrollDown=function() +{ + msg= EventQueue[0]; + if(typeof msg.FirstLine==="undefined") + msg.FirstLine=0; + if(typeof msg.CanScrollDown==="undefined") + msg.CanScrollDown=false; + + if(!msg.CanScrollDown) return; + + msg.FirstLine = msg.FirstLine+1; + PrintMessageStrings(msg); +} + + + + + + +let PrintMessageStrings=function(msg) +{ + let MyWrapString = function (str,maxWidth) + { + str=str.replace("\r\n","\n").replace("\r","\n"); + return g.wrapString(str,maxWidth); + } + + + if(typeof msg.FirstLine==="undefined") msg.FirstLine=0; + + let bodyFont = typeof msg.bodyFont==="undefined"? settings.fontMedium : msg.bodyFont; + let Padding=2; + if(typeof msg.lines==="undefined") + { + g.setFont(bodyFont); + msg.lines = MyWrapString(msg.body,g.getWidth()-(Padding*2)) + if ( msg.lines.length<=2) + { + bodyFont= g.getFonts().includes("Vector")?"Vector:20":"6x8:3"; + g.setFont(bodyFont); + msg.lines = MyWrapString(msg.body,g.getWidth()-(Padding*2)) + msg.bodyFont = bodyFont; + } + } + + + + //prendo le linee da stampare + let NumLines=8; + let linesToPrint = (msg.lines.length>NumLines) ? msg.lines.slice(msg.FirstLine,msg.FirstLine+NumLines):msg.lines; + + + let yText=45; + + //invalido l'area e disegno il testo + g.setBgColor(settings.colBg); + g.clearRect(0,yText,176,176); + let xText=Padding; + yText+=Padding; + g.setFont(bodyFont); + let HText=g.getFontHeight(); + + yText=((176-yText)/2)-(linesToPrint.length * HText / 2) + yText; + + if( linesToPrint.length<=2) + { + g.setFontAlign(0,-1); + xText = g.getWidth()/2; + } + else + g.setFontAlign(-1,-1); + + + linesToPrint.forEach((line, i)=>{ + g.drawString(line,xText,yText+HText*i); + }); + + //disegno le freccie + if(msg.FirstLine!=0) + { + msg.CanScrollUp=true; + DrawTriangleUp(); + } + else + msg.CanScrollUp=false; + + if(msg.FirstLine+linesToPrint.length < msg.lines.length) + { + msg.CanScrollDown=true; + DrawTriangleDown(); + } + else + msg.CanScrollDown=false; + + +} + + + + +let doubleTapUnlock=function(data) { + if( data.double) //solo se in double + { + Bangle.setLocked(false); + Bangle.setLCDPower(1); + } +} +let toushScroll=function(button, xy) { + let height=176; //g.getHeight(); -> 176 B2 + height/=2; + + if(xy.y next(), BTN1,{repeat: true}); + + //il tap è il tocco con l'accellerometro! + Bangle.on('tap', doubleTapUnlock); + Bangle.on('touch', toushScroll); + + //quando apro quest'app, do per scontato che c'è un messaggio da leggere posto in un file particolare ( NewMessage.json ) + let eventToShow = require('Storage').readJSON(settings.NewEventFileName, true); + require("Storage").erase(settings.NewEventFileName) + if( eventToShow!==undefined) + manageEvent(eventToShow); + else + { + LOG("file not found!"); + setTimeout(_ => load(), 0); + } +}; + + + + +main(); \ No newline at end of file diff --git a/apps/messages_light/messages_light.boot.js b/apps/messages_light/messages_light.boot.js new file mode 100644 index 000000000..741d08b96 --- /dev/null +++ b/apps/messages_light/messages_light.boot.js @@ -0,0 +1,33 @@ +/* +//OLD CODE -> backup purpose + +let messageBootManager=function(type,event){ + //se l'app non è aperta + if ("undefined"==typeof manageEvent) + { + if(event.t=="remove") return; //l'app non è aperta, non c'è nessun messaggio da rimuovere dalla queue -> non lancio l'app + + //la apro + require("Storage").writeJSON("messages_light.NewEvent.json",{"event":event,"type":type}); + load("messages_light.app.js"); + } + else + { + //altrimenti gli dico di gestire il messaggio + manageEvent(type,event); + } +} +Bangle.on("message", messageBootManager); +Bangle.on("call", messageBootManager);*/ + + + +//override require to filter require("message") +global.require_real=global.require; +global.require = (_require => file => { + if (file==="messages") file = "messagesProxy"; + //else if (file==="messages_REAL") file = "messages"; //backdoor to real message + + return _require(file); +})(require); + diff --git a/apps/messages_light/messages_light.messagesProxy.js b/apps/messages_light/messages_light.messagesProxy.js new file mode 100644 index 000000000..723397057 --- /dev/null +++ b/apps/messages_light/messages_light.messagesProxy.js @@ -0,0 +1,30 @@ + +//gestisco il messaggio a modo mio +exports.pushMessage = function(event) { + + //TODO: now i can't handle the music, so i call the real message app + if( event.id=="music") return require_real("messages").pushMessage(event); + + //se l'app non è aperta + if ("undefined"==typeof manageEvent) + { + if(event.t=="remove") return; //l'app non è aperta, non c'è nessun messaggio da rimuovere dalla queue -> non lancio l'app + + //la apro + require_real("Storage").writeJSON("messages_light.NewEvent.json",event); + load("messages_light.app.js"); + } + else + { + //altrimenti gli dico di gestire il messaggio + manageEvent(event); + } +} + + +//Call original message library +exports.clearAll = function() { return require_real("messages").clearAll()} +exports.getMessages = function() { return require_real("messages").getMessages()} +exports.status = function() { return require_real("messages").status()} +exports.buzz = function() { return require_real("messages").buzz(msgSrc)} +exports.stopBuzz = function() { return require_real("messages").stopBuzz()} \ No newline at end of file diff --git a/apps/messages_light/messages_light.settings.js b/apps/messages_light/messages_light.settings.js new file mode 100644 index 000000000..b7197c70a --- /dev/null +++ b/apps/messages_light/messages_light.settings.js @@ -0,0 +1 @@ +eval(require("Storage").read("messages.settings.js")); diff --git a/apps/messages_light/metadata.json b/apps/messages_light/metadata.json new file mode 100644 index 000000000..3515a75c2 --- /dev/null +++ b/apps/messages_light/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "messages_light", + "name": "Messages Light", + "version": "1.3", + "description": "A light implementation of messages App (display notifications from iOS and Gadgetbridge/Android)", + "icon": "app.png", + "type": "app", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies" : { "messageicons":"module","messages":"app" }, + "readme": "README.md", + "storage": [ + {"name":"messages_light.app.js","url":"messages_light.app.js"}, + {"name":"messages_light.settings.js","url":"messages_light.settings.js"}, + {"name":"messages_light.img","url":"app-icon.js","evaluate":true}, + {"name":"messagesProxy","url":"messages_light.messagesProxy.js"}, + {"name":"messages_light.boot.js","url":"messages_light.boot.js"} + ], + "data": [{"name":"messages_light.settings.json"},{"name":"messages_light.NewMessage.json"}], + "screenshots": [{"url":"screenshot-notify.png"} ,{"url":"screenshot-long-text1.png"},{"url":"screenshot-long-text2.png"}, {"url":"screenshot-call.png"} ] +} diff --git a/apps/messages_light/screenshot-call.png b/apps/messages_light/screenshot-call.png new file mode 100644 index 000000000..703faad6f Binary files /dev/null and b/apps/messages_light/screenshot-call.png differ diff --git a/apps/messages_light/screenshot-long-text1.png b/apps/messages_light/screenshot-long-text1.png new file mode 100644 index 000000000..147b0cd5c Binary files /dev/null and b/apps/messages_light/screenshot-long-text1.png differ diff --git a/apps/messages_light/screenshot-long-text2.png b/apps/messages_light/screenshot-long-text2.png new file mode 100644 index 000000000..5408f2059 Binary files /dev/null and b/apps/messages_light/screenshot-long-text2.png differ diff --git a/apps/messages_light/screenshot-notify.png b/apps/messages_light/screenshot-notify.png new file mode 100644 index 000000000..8896b803a Binary files /dev/null and b/apps/messages_light/screenshot-notify.png differ diff --git a/apps/mylocation/ChangeLog b/apps/mylocation/ChangeLog index c14e64ba9..b46b3b178 100644 --- a/apps/mylocation/ChangeLog +++ b/apps/mylocation/ChangeLog @@ -5,3 +5,4 @@ 0.05: Fixed issue with back option 0.06: renamed source files to match standard 0.07: Move mylocation app into 'Settings -> Apps' +0.08: Allow setting location from webinterface in the AppLoader diff --git a/apps/mylocation/README.md b/apps/mylocation/README.md index a6a16ce83..b12ed5dcf 100644 --- a/apps/mylocation/README.md +++ b/apps/mylocation/README.md @@ -4,7 +4,7 @@ To access, go to `Settings -> Apps -> My Location` -* Select one of the preset Cities or setup through the GPS +* Select one of the preset Cities, setup through the GPS or use the webinterface from the AppLoader * Other Apps can read this information to do calculations based on location * When the City shows ??? it means the location has been set through the GPS diff --git a/apps/mylocation/custom.html b/apps/mylocation/custom.html new file mode 100644 index 000000000..5c0130199 --- /dev/null +++ b/apps/mylocation/custom.html @@ -0,0 +1,95 @@ + + + + + + + + + +
+
+
+
+
+ + + + + + + + + + + diff --git a/apps/mylocation/metadata.json b/apps/mylocation/metadata.json index 4ab9aa37e..bef5b983c 100644 --- a/apps/mylocation/metadata.json +++ b/apps/mylocation/metadata.json @@ -4,11 +4,12 @@ "icon": "app.png", "type": "settings", "screenshots": [{"url":"screenshot_1.png"}], - "version":"0.07", - "description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README", + "version":"0.08", + "description": "Sets and stores the latitude and longitude of your preferred City. It can be set from GPS or webinterface. `mylocation.json` can be used by other apps that need your main location. See README for details.", "readme": "README.md", "tags": "tool,utility", "supports": ["BANGLEJS", "BANGLEJS2"], + "custom": "custom.html","custom": "custom.html", "storage": [ {"name":"mylocation.settings.js","url":"settings.js"} ], diff --git a/apps/nixie/nixie.info b/apps/nixie/nixie.info deleted file mode 100644 index 66f5ff2a5..000000000 --- a/apps/nixie/nixie.info +++ /dev/null @@ -1,10 +0,0 @@ -{ -"id":"jvNixie", -"name":"Nixie Clock", -"type":"clock", -"src":"nixie.app.js", -"icon": "nixie.img", -"sortorder":1, -"version":"1.1", -"files":"nixie.info,nixie.app.js,nixie.img, m_vatch.js" -} diff --git a/apps/openhaystack/ChangeLog b/apps/openhaystack/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/openhaystack/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/openhaystack/README.md b/apps/openhaystack/README.md new file mode 100644 index 000000000..e2d5e2212 --- /dev/null +++ b/apps/openhaystack/README.md @@ -0,0 +1,18 @@ +# OpenHaystack (AirTag) + +Copy a base64 key from https://github.com/seemoo-lab/openhaystack and make your Bangle.js trackable as if it's an AirTag + +Based on https://github.com/seemoo-lab/openhaystack/issues/59 + +## Usage + +* Follow the steps on https://github.com/seemoo-lab/openhaystack#how-to-use-openhaystack to install OpenHaystack and get a unique base64 code +* Click the ≡ icon next to `OpenHaystack (AirTag)` +* Paste in the base64 code +* Click `Upload` + +## Note + +This code changes your Bangle's MAC address, so while it still advertises with +the same `Bangle.js abcd` name, devices that were previously paired with it +won't automatically reconnect it until you re-pair. diff --git a/apps/openhaystack/custom.html b/apps/openhaystack/custom.html new file mode 100644 index 000000000..f56e94a98 --- /dev/null +++ b/apps/openhaystack/custom.html @@ -0,0 +1,56 @@ + + + + + + +

Follow the steps on https://github.com/seemoo-lab/openhaystack to install OpenHaystack and get a unique base64 code

+

Then paste the key in below and click Upload

+ +

Base64 key:

+

 

+ +

Click

+ + + + + + + diff --git a/apps/openhaystack/icon.png b/apps/openhaystack/icon.png new file mode 100644 index 000000000..f5e4f7f3b Binary files /dev/null and b/apps/openhaystack/icon.png differ diff --git a/apps/openhaystack/metadata.json b/apps/openhaystack/metadata.json new file mode 100644 index 000000000..5573529f7 --- /dev/null +++ b/apps/openhaystack/metadata.json @@ -0,0 +1,14 @@ +{ "id": "openhaystack", + "name": "OpenHaystack (AirTag)", + "icon": "icon.png", + "version":"0.01", + "description": "Copy a base64 key from https://github.com/seemoo-lab/openhaystack and make your Bangle.js trackable as if it's an AirTag", + "tags": "openhaystack,bluetooth,ble,tracking,airtag", + "type": "bootloader", + "custom": "custom.html", + "readme": "README.md", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"openhaystack.boot.js"} + ] +} diff --git a/apps/openstmap/custom.html b/apps/openstmap/custom.html index 3bba997e7..c1a161458 100644 --- a/apps/openstmap/custom.html +++ b/apps/openstmap/custom.html @@ -43,8 +43,8 @@ - - + + - - + + + + - diff --git a/loader.js b/loader.js index 3a6b41cfe..251f94ff1 100644 --- a/loader.js +++ b/loader.js @@ -159,6 +159,11 @@ window.addEventListener('load', (event) => {
+
`; showPrompt("Which Bangle.js?",html,{},false); + var usageStats = document.getElementById("usage_stats"); + usageStats.addEventListener("change",event=>{ + console.log("Send Usage Stats "+(event.target.checked?"on":"off")); + SETTINGS.sendUsageStats = event.target.checked; + saveSettings(); + }); htmlToArray(document.querySelectorAll(".devicechooser")).forEach(button => { button.addEventListener("click",event => { - let rememberDevice = document.getElementById("remember_device").checked; - + let rememberDevice = !!document.getElementById("remember_device").checked; let button = event.currentTarget; let deviceId = button.getAttribute("deviceid"); hidePrompt(); @@ -196,7 +206,9 @@ window.addEventListener('load', (event) => { // Button to install all default apps in one go document.getElementById("reinstallall").addEventListener("click",event=>{ var promise = showPrompt("Reinstall","Really re-install all apps?").then(() => { - getInstalledApps().then(installedapps => { + Comms.reset().then(_ => + getInstalledApps() + ).then(installedapps => { console.log(installedapps); var promise = Promise.resolve(); installedapps.forEach(app => { @@ -206,10 +218,12 @@ window.addEventListener('load', (event) => { app = appJSON.find(a => a.id==oldApp.id); if (!app) return console.log(`Ignoring ${oldApp.id} as not found`); - promise = promise.then(() => updateApp(app)); + promise = promise.then(() => updateApp(app, {noReset:true, noFinish:true})); }); return promise; - }).catch(err=>{ + }).then( _ => + Comms.showUploadFinished() + ).catch(err=>{ Progress.hide({sticky:true}); showToast("App re-install failed, "+err,"error"); }); diff --git a/modules/.eslintrc.json b/modules/.eslintrc.json new file mode 100644 index 000000000..d656c2555 --- /dev/null +++ b/modules/.eslintrc.json @@ -0,0 +1,163 @@ +{ + "env": { + // TODO: "espruino": false + // TODO: "banglejs": false + }, + "extends": "eslint:recommended", + "globals": { + // Methods and Fields at https://banglejs.com/reference + "Array": "readonly", + "ArrayBuffer": "readonly", + "ArrayBufferView": "readonly", + "Bangle": "readonly", + "BluetoothDevice": "readonly", + "BluetoothRemoteGATTCharacteristic": "readonly", + "BluetoothRemoteGATTServer": "readonly", + "BluetoothRemoteGATTService": "readonly", + "Boolean": "readonly", + "console": "readonly", + "DataView": "readonly", + "Date": "readonly", + "E": "readonly", + "Error": "readonly", + "Flash": "readonly", + "Float32Array": "readonly", + "Float64Array": "readonly", + "fs": "readonly", + "Function": "readonly", + "Graphics": "readonly", + "heatshrink": "readonly", + "I2C": "readonly", + "Int16Array": "readonly", + "Int32Array": "readonly", + "Int8Array": "readonly", + "InternalError": "readonly", + "JSON": "readonly", + "Math": "readonly", + "Modules": "readonly", + "NRF": "readonly", + "Number": "readonly", + "Object": "readonly", + "OneWire": "readonly", + "Pin": "readonly", + "process": "readonly", + "Promise": "readonly", + "ReferenceError": "readonly", + "RegExp": "readonly", + "Serial": "readonly", + "SPI": "readonly", + "Storage": "readonly", + "StorageFile": "readonly", + "String": "readonly", + "SyntaxError": "readonly", + "tensorflow": "readonly", + "TFMicroInterpreter": "readonly", + "TypeError": "readonly", + "Uint16Array": "readonly", + "Uint24Array": "readonly", + "Uint32Array": "readonly", + "Uint8Array": "readonly", + "Uint8ClampedArray": "readonly", + "Waveform": "readonly", + // Methods and Fields at https://banglejs.com/reference + "analogRead": "readonly", + "analogWrite": "readonly", + "arguments": "readonly", + "atob": "readonly", + "Bluetooth": "readonly", + "BTN": "readonly", + "BTN1": "readonly", + "BTN2": "readonly", + "BTN3": "readonly", + "BTN4": "readonly", + "BTN5": "readonly", + "btoa": "readonly", + "changeInterval": "readonly", + "clearInterval": "readonly", + "clearTimeout": "readonly", + "clearWatch": "readonly", + "decodeURIComponent": "readonly", + "digitalPulse": "readonly", + "digitalRead": "readonly", + "digitalWrite": "readonly", + "dump": "readonly", + "echo": "readonly", + "edit": "readonly", + "encodeURIComponent": "readonly", + "eval": "readonly", + "getPinMode": "readonly", + "getSerial": "readonly", + "getTime": "readonly", + "global": "readonly", + "HIGH": "readonly", + "I2C1": "readonly", + "Infinity": "readonly", + "isFinite": "readonly", + "isNaN": "readonly", + "LED": "readonly", + "LED1": "readonly", + "LED2": "readonly", + "load": "readonly", + "LoopbackA": "readonly", + "LoopbackB": "readonly", + "LOW": "readonly", + "NaN": "readonly", + "parseFloat": "readonly", + "parseInt": "readonly", + "peek16": "readonly", + "peek32": "readonly", + "peek8": "readonly", + "pinMode": "readonly", + "poke16": "readonly", + "poke32": "readonly", + "poke8": "readonly", + "print": "readonly", + "require": "readonly", + "reset": "readonly", + "save": "readonly", + "Serial1": "readonly", + "setBusyIndicator": "readonly", + "setInterval": "readonly", + "setSleepIndicator": "readonly", + "setTime": "readonly", + "setTimeout": "readonly", + "setWatch": "readonly", + "shiftOut": "readonly", + "SPI1": "readonly", + "Terminal": "readonly", + "trace": "readonly", + "VIBRATE": "readonly", + // Aliases and not defined at https://banglejs.com/reference + "g": "readonly", + "WIDGETS": "readonly" + }, + "parserOptions": { + "ecmaVersion": 11 + }, + "rules": { + "indent": [ + "off", + 2, + { + "SwitchCase": 1 + } + ], + "no-case-declarations": "off", + "no-constant-condition": "off", + "no-delete-var": "off", + "no-empty": "off", + "no-global-assign": "off", + "no-inner-declarations": "off", + "no-octal": "off", + "no-prototype-builtins": "off", + "no-redeclare": "off", + "no-unreachable": "warn", + "no-cond-assign": "warn", + "no-useless-catch": "warn", + // TODO: "no-undef": "warn", + "no-undef": "off", + "no-unused-vars": "off", + "no-useless-escape": "off", + "no-control-regex" : "off" + } +} diff --git a/modules/ClockFace.js b/modules/ClockFace.js index a89281d2d..c12360626 100644 --- a/modules/ClockFace.js +++ b/modules/ClockFace.js @@ -82,7 +82,7 @@ ClockFace.prototype.start = function() { /* Some widgets want to know if we're in a clock or not (like chrono, widget clock, etc). Normally .CLOCK is set by Bangle.setUI('clock') but we want to load widgets so we can check appRect and *then* call setUI. see #1864 */ - Bangle.CLOCK = 1; + Bangle.CLOCK = 1; if (this.loadWidgets) Bangle.loadWidgets(); if (this.init) this.init.apply(this); if (this._upDown) Bangle.setUI("clockupdown", d=>this._upDown.apply(this,[d])); diff --git a/modules/Layout.js b/modules/Layout.js index fdcf0ceae..cfc9ee79f 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -10,18 +10,20 @@ */ -function Layout(layout, options) { +function Layout(layout, options) { this._l = this.l = layout; // Do we have >1 physical buttons? - this.physBtns = (process.env.HWVERSION==2) ? 1 : 3; + this.options = options || {}; this.lazy = this.options.lazy || false; + this.physBtns = 1; let btnList; if (process.env.HWVERSION!=2) { + this.physBtns = 3; // no touchscreen, find any buttons in 'layout' btnList = []; - function btnRecurser(l) { + function btnRecurser(l) {"ram"; if (l.type=="btn") btnList.push(l); if (l.c) l.c.forEach(btnRecurser); } @@ -64,7 +66,7 @@ function Layout(layout, options) { this.setUI(); // recurse over layout doing some fixing up if needed var ll = this; - function recurser(l) { + function recurser(l) {"ram"; // add IDs if (l.id) ll[l.id] = l; // fix type up @@ -153,26 +155,25 @@ Layout.prototype.render = function (l) { if (!l) l = this._l; if (this.updateNeeded) this.update(); - function render(l) {"ram" - g.reset(); - if (l.col!==undefined) g.setColor(l.col); - if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); + var gfx=g; // define locally, because this is faster + function render(l) {"ram"; + gfx.reset(); + if (l.col!==undefined) gfx.setColor(l.col); + if (l.bgCol!==undefined) gfx.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); cb[l.type](l); } var cb = { "":function(){}, - "txt":function(l){ + "txt":function(l){"ram"; if (l.wrap) { - g.setFont(l.font).setFontAlign(0,-1); - var lines = g.wrapString(l.label, l.w); - var y = l.y+((l.h-g.getFontHeight()*lines.length)>>1); - // TODO: on 2v11 we can just render in a single drawString call - lines.forEach((line, i) => g.drawString(line, l.x+(l.w>>1), y+g.getFontHeight()*i)); + var lines = gfx.setFont(l.font).setFontAlign(0,-1).wrapString(l.label, l.w); + var y = l.y+((l.h-gfx.getFontHeight()*lines.length)>>1); + gfx.drawString(lines.join("\n"), l.x+(l.w>>1), y); } else { - g.setFont(l.font).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); + gfx.setFont(l.font).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); } - }, "btn":function(l){ + }, "btn":function(l){"ram"; var x = l.x+(0|l.pad), y = l.y+(0|l.pad), w = l.w-(l.pad<<1), h = l.h-(l.pad<<1); var poly = [ @@ -185,27 +186,26 @@ Layout.prototype.render = function (l) { x+4,y+h-1, x,y+h-5, x,y+4 - ], bg = l.selected?g.theme.bgH:g.theme.bg2; - g.setColor(bg).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly); - if (l.col!==undefined) g.setColor(l.col); - if (l.src) g.setBgColor(bg).drawImage( + ], bg = l.selected?gfx.theme.bgH:gfx.theme.bg2; + gfx.setColor(bg).fillPoly(poly).setColor(l.selected ? gfx.theme.fgH : gfx.theme.fg2).drawPoly(poly); + if (l.col!==undefined) gfx.setColor(l.col); + if (l.src) gfx.setBgColor(bg).drawImage( "function"==typeof l.src?l.src():l.src, l.x + l.w/2, l.y + l.h/2, {scale: l.scale||undefined, rotate: Math.PI*0.5*(l.r||0)} ); - else g.setFont(l.font||"6x8:2").setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); - }, "img":function(l){ - g.drawImage( + else gfx.setFont(l.font||"6x8:2").setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); + }, "img":function(l){"ram"; + gfx.drawImage( "function"==typeof l.src?l.src():l.src, l.x + l.w/2, l.y + l.h/2, {scale: l.scale||undefined, rotate: Math.PI*0.5*(l.r||0)} ); - }, "custom":function(l){ - l.render(l); - },"h":function(l) { l.c.forEach(render); }, - "v":function(l) { l.c.forEach(render); } + }, "custom":function(l){"ram"; l.render(l); + }, "h":function(l) { "ram"; l.c.forEach(render); + }, "v":function(l) { "ram"; l.c.forEach(render); } }; if (this.lazy) { @@ -218,7 +218,7 @@ Layout.prototype.render = function (l) { prepareLazyRender(l, rectsToClear, drawList, this.rects, null); for (var h in rectsToClear) delete this.rects[h]; var clearList = Object.keys(rectsToClear).map(k=>rectsToClear[k]).reverse(); // Rects are cleared in reverse order so that the original bg color is restored - for (var r of clearList) g.setBgColor(r.bg).clearRect.apply(g, r); + for (var r of clearList) gfx.setBgColor(r.bg).clearRect.apply(g, r); drawList.forEach(render); } else { // non-lazy render(l); @@ -231,9 +231,8 @@ Layout.prototype.forgetLazyState = function () { Layout.prototype.layout = function (l) { // l = current layout element - // exw,exh = extra width/height available - switch (l.type) { - case "h": { + var cb = { + "h" : function(l) {"ram"; var acc_w = l.x + (0|l.pad); var accfillx = 0; var fillx = l.c && l.c.reduce((a,l)=>a+(0|l.fillx),0); @@ -247,11 +246,10 @@ Layout.prototype.layout = function (l) { c.w = 0|(x - c.x); c.h = 0|(c.filly ? l.h - (l.pad<<1) : c._h); c.y = 0|(l.y + (0|l.pad) + ((1+(0|c.valign))*(l.h-(l.pad<<1)-c.h)>>1)); - if (c.c) this.layout(c); + if (c.c) cb[c.type](c); }); - break; - } - case "v": { + }, + "v" : function(l) {"ram"; var acc_h = l.y + (0|l.pad); var accfilly = 0; var filly = l.c && l.c.reduce((a,l)=>a+(0|l.filly),0); @@ -265,11 +263,11 @@ Layout.prototype.layout = function (l) { c.h = 0|(y - c.y); c.w = 0|(c.fillx ? l.w - (l.pad<<1) : c._w); c.x = 0|(l.x + (0|l.pad) + ((1+(0|c.halign))*(l.w-(l.pad<<1)-c.w)>>1)); - if (c.c) this.layout(c); + if (c.c) cb[c.type](c); }); - break; } - } + }; + cb[l.type](l); }; Layout.prototype.debug = function(l,c) { if (!l) l = this._l; @@ -282,50 +280,51 @@ Layout.prototype.debug = function(l,c) { }; Layout.prototype.update = function() { delete this.updateNeeded; + var gfx=g; // define locally, because this is faster // update sizes - function updateMin(l) {"ram" + function updateMin(l) {"ram"; cb[l.type](l); if (l.r&1) { // rotation var t = l._w;l._w=l._h;l._h=t; } - l._w = 0|Math.max(l._w + (l.pad<<1), 0|l.width); - l._h = 0|Math.max(l._h + (l.pad<<1), 0|l.height); + l._w = Math.max(l._w + (l.pad<<1), 0|l.width); + l._h = Math.max(l._h + (l.pad<<1), 0|l.height); } var cb = { - "txt" : function(l) { + "txt" : function(l) {"ram"; if (l.font.endsWith("%")) - l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); + l.font = "Vector"+Math.round(gfx.getHeight()*l.font.slice(0,-1)/100); if (l.wrap) { l._h = l._w = 0; } else { var m = g.setFont(l.font).stringMetrics(l.label); l._w = m.width; l._h = m.height; } - }, "btn": function(l) { + }, "btn": function(l) {"ram"; if (l.font && l.font.endsWith("%")) - l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); - var m = l.src?g.imageMetrics("function"==typeof l.src?l.src():l.src):g.setFont(l.font||"6x8:2").stringMetrics(l.label); + l.font = "Vector"+Math.round(gfx.getHeight()*l.font.slice(0,-1)/100); + var m = l.src?gfx.imageMetrics("function"==typeof l.src?l.src():l.src):gfx.setFont(l.font||"6x8:2").stringMetrics(l.label); l._h = 16 + m.height; l._w = 20 + m.width; - }, "img": function(l) { - var m = g.imageMetrics("function"==typeof l.src?l.src():l.src), s=l.scale||1; // get width and height out of image + }, "img": function(l) {"ram"; + var m = gfx.imageMetrics("function"==typeof l.src?l.src():l.src), s=l.scale||1; // get width and height out of image l._w = m.width*s; l._h = m.height*s; - }, "": function(l) { + }, "": function(l) {"ram"; // size should already be set up in width/height l._w = 0; l._h = 0; - }, "custom": function(l) { + }, "custom": function(l) {"ram"; // size should already be set up in width/height l._w = 0; l._h = 0; - }, "h": function(l) { + }, "h": function(l) {"ram"; l.c.forEach(updateMin); l._h = l.c.reduce((a,b)=>Math.max(a,b._h),0); l._w = l.c.reduce((a,b)=>a+b._w,0); if (l.fillx == null && l.c.some(c=>c.fillx)) l.fillx = 1; if (l.filly == null && l.c.some(c=>c.filly)) l.filly = 1; - }, "v": function(l) { + }, "v": function(l) {"ram"; l.c.forEach(updateMin); l._h = l.c.reduce((a,b)=>a+b._h,0); l._w = l.c.reduce((a,b)=>Math.max(a,b._w),0); @@ -336,6 +335,7 @@ Layout.prototype.update = function() { var l = this._l; updateMin(l); + delete cb; if (l.fillx || l.filly) { // fill all l.w = Bangle.appRect.w; l.h = Bangle.appRect.h; diff --git a/modules/Layout.min.js b/modules/Layout.min.js index b5a924358..f801177aa 100644 --- a/modules/Layout.min.js +++ b/modules/Layout.min.js @@ -1,14 +1,14 @@ -function p(b,k){function d(h){h.id&&(f[h.id]=h);h.type||(h.type="");h.c&&h.c.forEach(d)}this._l=this.l=b;this.physBtns=2==process.env.HWVERSION?1:3;this.options=k||{};this.lazy=this.options.lazy||!1;let a;if(2!=process.env.HWVERSION){a=[];function h(m){"btn"==m.type&&a.push(m);m.c&&m.c.forEach(h)}h(b);a.length&&(this.physBtns=0,this.buttons=a,this.selectedButton=-1)}if(this.options.btns)if(b=this.options.btns,this.physBtns>=b.length){this.b=b;let h=Math.floor(Bangle.appRect.h/ -this.physBtns);for(2b.length;)b.push({label:""});this._l.width=g.getWidth()-8;this._l={type:"h",filly:1,c:[this._l,{type:"v",pad:1,filly:1,c:b.map(m=>(m.type="txt",m.font="6x8",m.height=h,m.r=1,m))}]}}else this._l.width=g.getWidth()-32,this._l={type:"h",c:[this._l,{type:"v",c:b.map(h=>(h.type="btn",h.filly=1,h.width=32,h.r=1,h))}]},a&&a.push.apply(a,this._l.c[1].c);this.setUI();var f=this;d(this._l);this.updateNeeded=!0}function r(b, -k,d,a,f){var h=null==b.bgCol?f:g.toColor(b.bgCol);if(h!=f||"txt"==b.type||"btn"==b.type||"img"==b.type||"custom"==b.type){var m=b.c;delete b.c;var c="H"+E.CRC32(E.toJS(b));m&&(b.c=m);delete k[c]||((a[c]=[b.x,b.y,b.x+b.w-1,b.y+b.h-1]).bg=null==f?g.theme.bg:f,d&&(d.push(b),d=null))}if(b.c)for(var l of b.c)r(l,k,d,a,h)}p.prototype.setUI=function(){Bangle.setUI();let b;this.buttons&&(Bangle.setUI({mode:"updown",back:this.options.back},k=>{var d=this.selectedButton,a=this.buttons.length;if(void 0===k&& -this.buttons[d])return this.buttons[d].cb();this.buttons[d]&&(delete this.buttons[d].selected,this.render(this.buttons[d]));d=(d+a+k)%a;this.buttons[d]&&(this.buttons[d].selected=1,this.render(this.buttons[d]));this.selectedButton=d}),b=!0);this.options.back&&!b&&Bangle.setUI({mode:"custom",back:this.options.back});if(this.b){function k(d,a){.75=d.x&&a.y>=d.y&&a.x<=d.x+d.w&&a.y<=d.y+d.h&&(2==a.type&&d.cbl?d.cbl(a):d.cb&&d.cb(a));d.c&&d.c.forEach(f=>k(f,a))}Bangle.touchHandler=(d,a)=>k(this._l,a);Bangle.on("touch",Bangle.touchHandler)}}; -p.prototype.render=function(b){function k(c){"ram";g.reset();void 0!==c.col&&g.setColor(c.col);void 0!==c.bgCol&&g.setBgColor(c.bgCol).clearRect(c.x,c.y,c.x+c.w-1,c.y+c.h-1);d[c.type](c)}b||(b=this._l);this.updateNeeded&&this.update();var d={"":function(){},txt:function(c){if(c.wrap){g.setFont(c.font).setFontAlign(0,-1);var l=g.wrapString(c.label,c.w),e=c.y+(c.h-g.getFontHeight()*l.length>>1);l.forEach((n,q)=>g.drawString(n,c.x+(c.w>>1),e+g.getFontHeight()*q))}else g.setFont(c.font).setFontAlign(0, -0,c.r).drawString(c.label,c.x+(c.w>>1),c.y+(c.h>>1))},btn:function(c){var l=c.x+(0|c.pad),e=c.y+(0|c.pad),n=c.w-(c.pad<<1),q=c.h-(c.pad<<1);l=[l,e+4,l+4,e,l+n-5,e,l+n-1,e+4,l+n-1,e+q-5,l+n-5,e+q-1,l+4,e+q-1,l,e+q-5,l,e+4];e=c.selected?g.theme.bgH:g.theme.bg2;g.setColor(e).fillPoly(l).setColor(c.selected?g.theme.fgH:g.theme.fg2).drawPoly(l);void 0!==c.col&&g.setColor(c.col);c.src?g.setBgColor(e).drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI* -(c.r||0)}):g.setFont(c.font||"6x8:2").setFontAlign(0,0,c.r).drawString(c.label,c.x+c.w/2,c.y+c.h/2)},img:function(c){g.drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)})},custom:function(c){c.render(c)},h:function(c){c.c.forEach(k)},v:function(c){c.c.forEach(k)}};if(this.lazy){this.rects||(this.rects={});var a=this.rects.clone(),f=[];r(b,a,f,this.rects,null);for(var h in a)delete this.rects[h];b=Object.keys(a).map(c=>a[c]).reverse(); -for(var m of b)g.setBgColor(m.bg).clearRect.apply(g,m);f.forEach(k)}else k(b)};p.prototype.forgetLazyState=function(){this.rects={}};p.prototype.layout=function(b){switch(b.type){case "h":var k=b.x+(0|b.pad),d=0,a=b.c&&b.c.reduce((e,n)=>e+(0|n.fillx),0);a||(k+=b.w-b._w>>1,a=1);var f=k;b.c.forEach(e=>{e.x=0|f;k+=e._w;d+=0|e.fillx;f=k+Math.floor(d*(b.w-b._w)/a);e.w=0|f-e.x;e.h=0|(e.filly?b.h-(b.pad<<1):e._h);e.y=0|b.y+(0|b.pad)+((1+(0|e.valign))*(b.h-(b.pad<<1)-e.h)>>1);e.c&&this.layout(e)});break; -case "v":var h=b.y+(0|b.pad),m=0,c=b.c&&b.c.reduce((e,n)=>e+(0|n.filly),0);c||(h+=b.h-b._h>>1,c=1);var l=h;b.c.forEach(e=>{e.y=0|l;h+=e._h;m+=0|e.filly;l=h+Math.floor(m*(b.h-b._h)/c);e.h=0|l-e.y;e.w=0|(e.fillx?b.w-(b.pad<<1):e._w);e.x=0|b.x+(0|b.pad)+((1+(0|e.halign))*(b.w-(b.pad<<1)-e.w)>>1);e.c&&this.layout(e)})}};p.prototype.debug=function(b,k){b||(b=this._l);k=k||1;g.setColor(k&1,k&2,k&4).drawRect(b.x+k-1,b.y+k-1,b.x+b.w-k,b.y+b.h-k);b.pad&&g.drawRect(b.x+b.pad-1,b.y+b.pad-1,b.x+b.w-b.pad,b.y+ -b.h-b.pad);k++;b.c&&b.c.forEach(d=>this.debug(d,k))};p.prototype.update=function(){function b(a){"ram";k[a.type](a);if(a.r&1){var f=a._w;a._w=a._h;a._h=f}a._w=0|Math.max(a._w+(a.pad<<1),0|a.width);a._h=0|Math.max(a._h+(a.pad<<1),0|a.height)}delete this.updateNeeded;var k={txt:function(a){a.font.endsWith("%")&&(a.font="Vector"+Math.round(g.getHeight()*a.font.slice(0,-1)/100));if(a.wrap)a._h=a._w=0;else{var f=g.setFont(a.font).stringMetrics(a.label);a._w=f.width;a._h=f.height}},btn:function(a){a.font&& -a.font.endsWith("%")&&(a.font="Vector"+Math.round(g.getHeight()*a.font.slice(0,-1)/100));var f=a.src?g.imageMetrics("function"==typeof a.src?a.src():a.src):g.setFont(a.font||"6x8:2").stringMetrics(a.label);a._h=16+f.height;a._w=20+f.width},img:function(a){var f=g.imageMetrics("function"==typeof a.src?a.src():a.src),h=a.scale||1;a._w=f.width*h;a._h=f.height*h},"":function(a){a._w=0;a._h=0},custom:function(a){a._w=0;a._h=0},h:function(a){a.c.forEach(b);a._h=a.c.reduce((f,h)=>Math.max(f,h._h),0);a._w= -a.c.reduce((f,h)=>f+h._w,0);null==a.fillx&&a.c.some(f=>f.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(f=>f.filly)&&(a.filly=1)},v:function(a){a.c.forEach(b);a._h=a.c.reduce((f,h)=>f+h._h,0);a._w=a.c.reduce((f,h)=>Math.max(f,h._w),0);null==a.fillx&&a.c.some(f=>f.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(f=>f.filly)&&(a.filly=1)}},d=this._l;b(d);d.fillx||d.filly?(d.w=Bangle.appRect.w,d.h=Bangle.appRect.h,d.x=Bangle.appRect.x,d.y=Bangle.appRect.y):(d.w=d._w,d.h=d._h,d.x=Bangle.appRect.w-d.w>>1,d.y= -Bangle.appRect.y+(Bangle.appRect.h-d.h>>1));this.layout(d)};p.prototype.clear=function(b){b||(b=this._l);g.reset();void 0!==b.bgCol&&g.setBgColor(b.bgCol);g.clearRect(b.x,b.y,b.x+b.w-1,b.y+b.h-1)};exports=p \ No newline at end of file +function p(d,h){function b(e){"ram";e.id&&(a[e.id]=e);e.type||(e.type="");e.c&&e.c.forEach(b)}this._l=this.l=d;this.options=h||{};this.lazy=this.options.lazy||!1;this.physBtns=1;let f;if(2!=process.env.HWVERSION){this.physBtns=3;f=[];function e(l){"ram";"btn"==l.type&&f.push(l);l.c&&l.c.forEach(e)}e(d);f.length&&(this.physBtns=0,this.buttons=f,this.selectedButton=-1)}if(this.options.btns)if(d=this.options.btns,this.physBtns>=d.length){this.b=d;let e=Math.floor(Bangle.appRect.h/ +this.physBtns);for(2d.length;)d.push({label:""});this._l.width=g.getWidth()-8;this._l={type:"h",filly:1,c:[this._l,{type:"v",pad:1,filly:1,c:d.map(l=>(l.type="txt",l.font="6x8",l.height=e,l.r=1,l))}]}}else this._l.width=g.getWidth()-32,this._l={type:"h",c:[this._l,{type:"v",c:d.map(e=>(e.type="btn",e.filly=1,e.width=32,e.r=1,e))}]},f&&f.push.apply(f,this._l.c[1].c);this.setUI();var a=this;b(this._l);this.updateNeeded=!0}function t(d, +h,b,f,a){var e=null==d.bgCol?a:g.toColor(d.bgCol);if(e!=a||"txt"==d.type||"btn"==d.type||"img"==d.type||"custom"==d.type){var l=d.c;delete d.c;var k="H"+E.CRC32(E.toJS(d));l&&(d.c=l);delete h[k]||((f[k]=[d.x,d.y,d.x+d.w-1,d.y+d.h-1]).bg=null==a?g.theme.bg:a,b&&(b.push(d),b=null))}if(d.c)for(var c of d.c)t(c,h,b,f,e)}p.prototype.setUI=function(){Bangle.setUI();let d;this.buttons&&(Bangle.setUI({mode:"updown",back:this.options.back},h=>{var b=this.selectedButton,f=this.buttons.length;if(void 0===h&& +this.buttons[b])return this.buttons[b].cb();this.buttons[b]&&(delete this.buttons[b].selected,this.render(this.buttons[b]));b=(b+f+h)%f;this.buttons[b]&&(this.buttons[b].selected=1,this.render(this.buttons[b]));this.selectedButton=b}),d=!0);this.options.back&&!d&&Bangle.setUI({mode:"custom",back:this.options.back});if(this.b){function h(b,f){.75=b.x&&f.y>=b.y&&f.x<=b.x+b.w&&f.y<=b.y+b.h&&(2==f.type&&b.cbl?b.cbl(f):b.cb&&b.cb(f));b.c&&b.c.forEach(a=>h(a,f))}Bangle.touchHandler=(b,f)=>h(this._l,f);Bangle.on("touch",Bangle.touchHandler)}}; +p.prototype.render=function(d){function h(c){"ram";b.reset();void 0!==c.col&&b.setColor(c.col);void 0!==c.bgCol&&b.setBgColor(c.bgCol).clearRect(c.x,c.y,c.x+c.w-1,c.y+c.h-1);f[c.type](c)}d||(d=this._l);this.updateNeeded&&this.update();var b=g,f={"":function(){},txt:function(c){"ram";if(c.wrap){var m=b.setFont(c.font).setFontAlign(0,-1).wrapString(c.label,c.w),n=c.y+(c.h-b.getFontHeight()*m.length>>1);b.drawString(m.join("\n"),c.x+(c.w>>1),n)}else b.setFont(c.font).setFontAlign(0,0,c.r).drawString(c.label, +c.x+(c.w>>1),c.y+(c.h>>1))},btn:function(c){"ram";var m=c.x+(0|c.pad),n=c.y+(0|c.pad),q=c.w-(c.pad<<1),r=c.h-(c.pad<<1);m=[m,n+4,m+4,n,m+q-5,n,m+q-1,n+4,m+q-1,n+r-5,m+q-5,n+r-1,m+4,n+r-1,m,n+r-5,m,n+4];n=c.selected?b.theme.bgH:b.theme.bg2;b.setColor(n).fillPoly(m).setColor(c.selected?b.theme.fgH:b.theme.fg2).drawPoly(m);void 0!==c.col&&b.setColor(c.col);c.src?b.setBgColor(n).drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)}):b.setFont(c.font|| +"6x8:2").setFontAlign(0,0,c.r).drawString(c.label,c.x+c.w/2,c.y+c.h/2)},img:function(c){"ram";b.drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)})},custom:function(c){"ram";c.render(c)},h:function(c){"ram";c.c.forEach(h)},v:function(c){"ram";c.c.forEach(h)}};if(this.lazy){this.rects||(this.rects={});var a=this.rects.clone(),e=[];t(d,a,e,this.rects,null);for(var l in a)delete this.rects[l];d=Object.keys(a).map(c=>a[c]).reverse(); +for(var k of d)b.setBgColor(k.bg).clearRect.apply(g,k);e.forEach(h)}else h(d)};p.prototype.forgetLazyState=function(){this.rects={}};p.prototype.layout=function(d){var h={h:function(b){"ram";var f=b.x+(0|b.pad),a=0,e=b.c&&b.c.reduce((k,c)=>k+(0|c.fillx),0);e||(f+=b.w-b._w>>1,e=1);var l=f;b.c.forEach(k=>{k.x=0|l;f+=k._w;a+=0|k.fillx;l=f+Math.floor(a*(b.w-b._w)/e);k.w=0|l-k.x;k.h=0|(k.filly?b.h-(b.pad<<1):k._h);k.y=0|b.y+(0|b.pad)+((1+(0|k.valign))*(b.h-(b.pad<<1)-k.h)>>1);if(k.c)h[k.type](k)})},v:function(b){"ram"; +var f=b.y+(0|b.pad),a=0,e=b.c&&b.c.reduce((k,c)=>k+(0|c.filly),0);e||(f+=b.h-b._h>>1,e=1);var l=f;b.c.forEach(k=>{k.y=0|l;f+=k._h;a+=0|k.filly;l=f+Math.floor(a*(b.h-b._h)/e);k.h=0|l-k.y;k.w=0|(k.fillx?b.w-(b.pad<<1):k._w);k.x=0|b.x+(0|b.pad)+((1+(0|k.halign))*(b.w-(b.pad<<1)-k.w)>>1);if(k.c)h[k.type](k)})}};h[d.type](d)};p.prototype.debug=function(d,h){d||(d=this._l);h=h||1;g.setColor(h&1,h&2,h&4).drawRect(d.x+h-1,d.y+h-1,d.x+d.w-h,d.y+d.h-h);d.pad&&g.drawRect(d.x+d.pad-1,d.y+d.pad-1,d.x+d.w-d.pad, +d.y+d.h-d.pad);h++;d.c&&d.c.forEach(b=>this.debug(b,h))};p.prototype.update=function(){function d(a){"ram";b[a.type](a);if(a.r&1){var e=a._w;a._w=a._h;a._h=e}a._w=Math.max(a._w+(a.pad<<1),0|a.width);a._h=Math.max(a._h+(a.pad<<1),0|a.height)}delete this.updateNeeded;var h=g,b={txt:function(a){"ram";a.font.endsWith("%")&&(a.font="Vector"+Math.round(h.getHeight()*a.font.slice(0,-1)/100));if(a.wrap)a._h=a._w=0;else{var e=g.setFont(a.font).stringMetrics(a.label);a._w=e.width;a._h=e.height}},btn:function(a){"ram"; +a.font&&a.font.endsWith("%")&&(a.font="Vector"+Math.round(h.getHeight()*a.font.slice(0,-1)/100));var e=a.src?h.imageMetrics("function"==typeof a.src?a.src():a.src):h.setFont(a.font||"6x8:2").stringMetrics(a.label);a._h=16+e.height;a._w=20+e.width},img:function(a){"ram";var e=h.imageMetrics("function"==typeof a.src?a.src():a.src),l=a.scale||1;a._w=e.width*l;a._h=e.height*l},"":function(a){"ram";a._w=0;a._h=0},custom:function(a){"ram";a._w=0;a._h=0},h:function(a){"ram";a.c.forEach(d);a._h=a.c.reduce((e, +l)=>Math.max(e,l._h),0);a._w=a.c.reduce((e,l)=>e+l._w,0);null==a.fillx&&a.c.some(e=>e.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(e=>e.filly)&&(a.filly=1)},v:function(a){"ram";a.c.forEach(d);a._h=a.c.reduce((e,l)=>e+l._h,0);a._w=a.c.reduce((e,l)=>Math.max(e,l._w),0);null==a.fillx&&a.c.some(e=>e.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(e=>e.filly)&&(a.filly=1)}},f=this._l;d(f);delete b;f.fillx||f.filly?(f.w=Bangle.appRect.w,f.h=Bangle.appRect.h,f.x=Bangle.appRect.x,f.y=Bangle.appRect.y):(f.w=f._w, +f.h=f._h,f.x=Bangle.appRect.w-f.w>>1,f.y=Bangle.appRect.y+(Bangle.appRect.h-f.h>>1));this.layout(f)};p.prototype.clear=function(d){d||(d=this._l);g.reset();void 0!==d.bgCol&&g.setBgColor(d.bgCol);g.clearRect(d.x,d.y,d.x+d.w-1,d.y+d.h-1)};exports=p \ No newline at end of file diff --git a/modules/README.md b/modules/README.md index 62ce90a97..3f8e90b06 100644 --- a/modules/README.md +++ b/modules/README.md @@ -13,7 +13,8 @@ Development When apps that use these modules are uploaded via the app loader, the module is automatically included in the app's source. However -when developing via the IDE the module won't get pulled in by default. +when developing via the IDE the module won't get pulled in by default +so you may see the error "Module not found" in the IDE when sending code to the Bangle. To fix this you have three options: @@ -52,3 +53,6 @@ to the module. to `https://banglejs.com/apps/modules|https://www.espruino.com/modules` The next time you upload your app, the module will automatically be included. + +**Note:** You can optionally use `https://raw.githubusercontent.com/espruino/BangleApps/master/modules|https://www.espruino.com/modules` +as the module URL to pull in modules direct from the development app loader (which could be slightly newer than the ones on https://banglejs.com/apps) diff --git a/modules/clock_info.js b/modules/clock_info.js index b87c022f2..4ce2a08fc 100644 --- a/modules/clock_info.js +++ b/modules/clock_info.js @@ -1,4 +1,3 @@ -var exports = {}; /* Module that allows for loading of clock 'info' displays that can be scrolled through on the clock face. @@ -10,10 +9,13 @@ that can be scrolled through on the clock face. Note that each item is an object with: * 'item.name' : friendly name to identify an item (e.g. temperature) +* 'item.hasRange' : if `true`, `.get` returns `v/min/max` values (for progress bar/guage) * 'item.get' : function that resolves with: { 'text' : the text to display for this item 'img' : a 24x24px image to display for this item + 'v' : (if hasRange==true) a numerical value + 'min','max' : (if hasRange==true) a minimum and maximum numerical value (if this were to be displayed as a guage) } * 'item.show' : called when item should be shown. Enables updates. Call BEFORE 'get' * 'item.hide' : called when item should be hidden. Disables updates. @@ -30,7 +32,7 @@ example.clkinfo.js : img: atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") }), items: [ { name : "Item1", - get : () => ({ text : "TextOfItem1", + get : () => ({ text : "TextOfItem1", v : 10, min : 0, max : 100, img : atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") }), show : () => {}, hide : () => {} @@ -63,21 +65,29 @@ exports.load = function() { img: atob("GBiBAf8B//4B//4B//4B//4A//x4//n+f/P/P+fPn+fPn+fP3+/Px+/Px+fn3+fzn+f/n/P/P/n+f/x4//4A//4B//4B//4B//8B/w=="), items: [ { name : "Battery", - get : () => ({ - text : E.getBattery() + "%", - img : atob(Bangle.isCharging() ? "GBiBAAABgAADwAAHwAAPgACfAAHOAAPkBgHwDwP4Hwf8Pg/+fB//OD//kD//wD//4D//8D//4B//QB/+AD/8AH/4APnwAHAAACAAAA==" : "GBiBAAAAAAAAAAAAAAAAAAAAAD//+P///IAAAr//Ar//Ar//A7//A7//A7//A7//Ar//AoAAAv///D//+AAAAAAAAAAAAAAAAAAAAA==") }), + hasRange : true, + get : () => { let v = E.getBattery(); return { + text : v + "%", v : v, min:0, max:100, + img : atob(Bangle.isCharging() ? "GBiBAAABgAADwAAHwAAPgACfAAHOAAPkBgHwDwP4Hwf8Pg/+fB//OD//kD//wD//4D//8D//4B//QB/+AD/8AH/4APnwAHAAACAAAA==" : "GBiBAAAAAAAAAAAAAAAAAAAAAD//+P///IAAAr//Ar//Ar//A7//A7//A7//A7//Ar//AoAAAv///D//+AAAAAAAAAAAAAAAAAAAAA==") + }}, show : function() { this.interval = setInterval(()=>this.emit('redraw'), 60000); Bangle.on("charging", batteryUpdateHandler); batteryUpdateHandler(); }, hide : function() { clearInterval(this.interval); delete this.interval; Bangle.removeListener("charging", batteryUpdateHandler); }, }, - { name : "Steps", get : () => ({ - text : Bangle.getHealthStatus("day").steps, - img : atob("GBiBAAcAAA+AAA/AAA/AAB/AAB/gAA/g4A/h8A/j8A/D8A/D+AfH+AAH8AHn8APj8APj8AHj4AHg4AADAAAHwAAHwAAHgAAHgAADAA==") }), + { name : "Steps", + hasRange : true, + get : () => { let v = Bangle.getHealthStatus("day").steps; return { + text : v, v : v, min : 0, max : 10000, // TODO: do we have a target step amount anywhere? + img : atob("GBiBAAcAAA+AAA/AAA/AAB/AAB/gAA/g4A/h8A/j8A/D8A/D+AfH+AAH8AHn8APj8APj8AHj4AHg4AADAAAHwAAHwAAHgAAHgAADAA==") + }}, show : function() { Bangle.on("step", stepUpdateHandler); stepUpdateHandler(); }, hide : function() { Bangle.removeListener("step", stepUpdateHandler); }, }, - { name : "HRM", get : () => ({ - text : Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", - img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAADAAADAAAHAAAHjAAHjgAPngH9n/n82/gA+AAA8AAA8AAAcAAAYAAAYAAAAAAAAAAAAAAAAAAAAAA==") }), + { name : "HRM", + hasRange : true, + get : () => { let v = Math.round(Bangle.getHealthStatus("last").bpm); return { + text : v + " bpm", v : v, min : 40, max : 200, + img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAADAAADAAAHAAAHjAAHjgAPngH9n/n82/gA+AAA8AAA8AAAcAAAYAAAYAAAAAAAAAAAAAAAAAAAAAA==") + }}, show : function() { Bangle.setHRMPower(1,"clkinfo"); Bangle.on("HRM", hrmUpdateHandler); hrm = Math.round(Bangle.getHealthStatus("last").bpm); hrmUpdateHandler(); }, hide : function() { Bangle.setHRMPower(0,"clkinfo"); Bangle.removeListener("HRM", hrmUpdateHandler); hrm = "--"; }, } @@ -86,9 +96,11 @@ exports.load = function() { var bangleItems = menu[0].items; if (Bangle.getPressure){ // Altimeter may not exist - bangleItems.push({ name : "Altitude", get : () => ({ - text : alt, - img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA==") }), + bangleItems.push({ name : "Altitude", + get : () => ({ + text : alt, v : alt, + img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA==") + }), show : function() { this.interval = setInterval(altUpdateHandler, 60000); alt = "--"; altUpdateHandler(); }, hide : function() { clearInterval(this.interval); delete this.interval; }, }); @@ -116,59 +128,147 @@ Simply supply the menu data (from .load) and a function to draw the clock info. For example: -var clockInfoMenu = require("clock_info").addInteractive(require("clock_info").load(), (itm, info) => { - var y = 0; - g.reset().setFont("6x8:2").setFontAlign(-1,0); - g.clearRect(0,y,g.getWidth(),y+23); - g.drawImage(info.img, 0,y); - g.drawString(info.text, 48,y+12); +let clockInfoMenu = require("clock_info").addInteractive(require("clock_info").load(), { + x : 20, y: 20, w: 80, h:80, // dimensions of area used for clock_info + draw : (itm, info, options) => { + g.reset().clearRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1); + if (options.focus) g.drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1); // show if focused + var midx = options.x+options.w/2; + g.drawImage(info.img, midx-12,options.y+4); + g.setFont("6x8:2").setFontAlign(0,0).drawString(info.text, midx,options.y+36); + } }); +// then when clock 'unloads': +clockInfoMenu.remove(); +delete clockInfoMenu; Then if you need to unload the clock info so it no longer uses memory or responds to swipes, you can call clockInfoMenu.remove() and delete clockInfoMenu + +clockInfoMenu is the 'options' parameter, with the following added: + +* 'index' : int - which instance number are we? Starts at 0 +* 'menuA' : int - index in 'menu' of showing clockInfo item +* 'menuB' : int - index in 'menu[menuA].items' of showing clockInfo item +* 'remove' : function - remove this clockInfo item +* 'redraw' : function - force a redraw +* 'focus' : function - bool to show if menu is focused or not + +You can have more than one clock_info at once as well, sfor instance: + +let clockInfoDraw = (itm, info, options) => { + g.reset().setBgColor(options.bg).setColor(options.fg).clearRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1); + if (options.focus) g.drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1) + var midx = options.x+options.w/2; + g.drawImage(info.img, midx-12,options.y); + g.setFont("6x15").setFontAlign(0,-1).drawString(info.text, midx,options.y+26); +}; +let clockInfoItems = require("clock_info").load(); +let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:126, y:24, w:50, h:40, draw : clockInfoDraw, bg : g.theme.bg, fg : g.theme.fg }); +let clockInfoMenu2 = require("clock_info").addInteractive(clockInfoItems, { x:0, y:120, w:50, h:40, draw : clockInfoDraw, bg : bgColor, fg : g.theme.bg}); + */ -exports.addInteractive = function(menu, drawFn) { - if (!menu.length || !menu[0].items.length) return; // no info - var menuA = 0, menuB = 0; +exports.addInteractive = function(menu, options) { + if (!menu.length || !menu[0].items.length) return; // no infos - can't load a clock_info + if ("function" == typeof options) options = {draw:options}; // backwards compatibility + options.index = 0|exports.loadCount; + exports.loadCount = options.index+1; + options.focus = options.index==0 && options.x===undefined; // focus if we're the first one loaded and no position has been defined + const appName = "default:"+options.index; + + { // load the currently showing clock_infos + let settings = require("Storage").readJSON("clock_info.json",1)||{}; + if (settings[appName]) { + let a = settings[appName].a|0; + let b = settings[appName].b|0; + if (menu[a] && menu[a].items[b]) { // all ok + options.menuA = a; + options.menuB = b; + } + } + } + if (options.menuA===undefined) options.menuA = 0; + if (options.menuB===undefined) options.menuB = Math.min(exports.loadCount, menu[options.menuA].items.length)-1; + function drawItem(itm) { + options.draw(itm, itm.get(), options); + } function menuShowItem(itm) { - itm.on('redraw', ()=>drawFn(itm, itm.get())); - itm.show(); + options.redrawHandler = ()=>drawItem(itm); + itm.on('redraw', options.redrawHandler); + itm.uses = (0|itm.uses)+1; + if (itm.uses==1) itm.show(); itm.emit("redraw"); } + function menuHideItem(itm) { + itm.removeListener('redraw',options.redrawHandler); + delete options.redrawHandler; + itm.uses--; + if (!itm.uses) + itm.hide(); + } // handling for swipe between menu items function swipeHandler(lr,ud){ + if (!options.focus) return; // ignore if we're not focussed var oldMenuItem; if (ud) { - if (menu[menuA].items.length==1) return; // 1 item - can't move - oldMenuItem = menu[menuA].items[menuB]; - menuB += ud; - if (menuB<0) menuB = menu[menuA].items.length-1; - if (menuB>=menu[menuA].items.length) menuB = 0; + if (menu[options.menuA].items.length==1) return; // 1 item - can't move + oldMenuItem = menu[options.menuA].items[options.menuB]; + options.menuB += ud; + if (options.menuB<0) options.menuB = menu[options.menuA].items.length-1; + if (options.menuB>=menu[options.menuA].items.length) options.menuB = 0; } else if (lr) { if (menu.length==1) return; // 1 item - can't move - oldMenuItem = menu[menuA].items[menuB]; - menuA += ud; - if (menuA<0) menuA = menu.length-1; - if (menuA>=menu.length) menuA = 0; - menuB = 0; + oldMenuItem = menu[options.menuA].items[options.menuB]; + options.menuA += ud; + if (options.menuA<0) options.menuA = menu.length-1; + if (options.menuA>=menu.length) options.menuA = 0; + options.menuB = 0; } if (oldMenuItem) { - oldMenuItem.hide(); + menuHideItem(oldMenuItem); oldMenuItem.removeAllListeners("draw"); - menuShowItem(menu[menuA].items[menuB]); + menuShowItem(menu[options.menuA].items[options.menuB]); } + // save the currently showing clock_info + let settings = require("Storage").readJSON("clock_info.json",1)||{}; + settings[appName] = {a:options.menuA,b:options.menuB}; + require("Storage").writeJSON("clock_info.json",settings); } Bangle.on("swipe",swipeHandler); + var touchHandler; + if (options.x!==undefined && options.y!==undefined && options.w && options.h) { + touchHandler = function(_,e) { + if (e.x(options.x+options.w) || e.y>(options.y+options.h)) { + if (options.focus) { + options.focus=false; + options.redraw(); + } + return; // outside area + } + if (!options.focus) { + options.focus=true; // if not focussed, set focus + options.redraw(); + } else if (menu[options.menuA].items[options.menuB].run) + menu[options.menuA].items[options.menuB].run(); // allow tap on an item to run it (eg home assistant) + else options.focus=true; + }; + Bangle.on("touch",touchHandler); + } // draw the first item - menuShowItem(menu[menuA].items[menuB]); + menuShowItem(menu[options.menuA].items[options.menuB]); // return an object with info that can be used to remove the info - return { - remove : function() { - Bangle.removeListener("swipe",swipeHandler); - menu[menuA].items[menuB].hide(); - } + options.remove = function() { + Bangle.removeListener("swipe",swipeHandler); + if (touchHandler) Bangle.removeListener("touch",touchHandler); + menuHideItem(menu[options.menuA].items[options.menuB]); + exports.loadCount--; }; + options.redraw = function() { + drawItem(menu[options.menuA].items[options.menuB]); + }; + return options; }; // Code for testing (plots all elements from first list) diff --git a/modules/widget_utils.js b/modules/widget_utils.js index 5ee98ce05..3440a01d2 100644 --- a/modules/widget_utils.js +++ b/modules/widget_utils.js @@ -1,7 +1,6 @@ -/* Utilities for handling widgets - mainly showing/hiding */ - /// hide any visible widgets exports.hide = function() { + exports.cleanup(); if (!global.WIDGETS) return; g.reset(); // reset colors for (var w of global.WIDGETS) { @@ -16,6 +15,7 @@ exports.hide = function() { /// Show any hidden widgets exports.show = function() { + exports.cleanup(); if (!global.WIDGETS) return; for (var w of global.WIDGETS) { if (!w._draw) return; // not hidden @@ -26,3 +26,112 @@ exports.show = function() { w.draw(w); } }; + +/// Remove any intervals/handlers/etc that we might have added. Does NOT re-show widgets that were hidden +exports.cleanup = function() { + delete Bangle.appRect; + if (exports.swipeHandler) { + Bangle.removeListener("swipe", exports.swipeHandler); + delete exports.swipeHandler; + } + if (exports.animInterval) { + clearInterval(exports.animInterval); + delete exports.animInterval; + } + if (exports.hideTimeout) { + clearTimeout(exports.hideTimeout); + delete exports.hideTimeout; + } + if (exports.origDraw) { + Bangle.drawWidgets = exports.origDraw; + delete exports.origDraw; + } +} + +/** Put widgets offscreen, and allow them to be swiped +back onscreen with a downwards swipe. Use .show to undo. +Bangle.js 2 only at the moment. */ +exports.swipeOn = function() { + exports.cleanup(); + if (!global.WIDGETS) return; + + /* TODO: maybe when widgets are offscreen we don't even + store them in an offscreen buffer? */ + + // force app rect to be fullscreen + Bangle.appRect = { x: 0, y: 0, w: g.getWidth(), h: g.getHeight(), x2: g.getWidth()-1, y2: g.getHeight()-1 }; + // setup offscreen graphics for widgets + let og = Graphics.createArrayBuffer(g.getWidth(),24,16,{msb:true}); + og.theme = g.theme; + og._reset = og.reset; + og.reset = function() { + return this._reset().setColor(g.theme.fg).setBgColor(g.theme.bg); + }; + og.reset().clearRect(0,0,og.getWidth(),og.getHeight()); + let _g = g; + let offset = -24; // where on the screen are we? -24=hidden, 0=full visible + + function queueDraw() { + Bangle.appRect.y = offset+24; + Bangle.appRect.h = 1 + Bangle.appRect.y2 - Bangle.appRect.y; + if (offset>-24) Bangle.setLCDOverlay(og, 0, offset); + else Bangle.setLCDOverlay(); + } + + for (var w of global.WIDGETS) { + if (w._draw) return; // already hidden + w._draw = w.draw; + w.draw = function() { + g=og; + this._draw(this); + g=_g; + if (offset>-24) queueDraw(); + }; + w._area = w.area; + if (w.area.startsWith("b")) + w.area = "t"+w.area.substr(1); + } + + exports.origDraw = Bangle.drawWidgets; + Bangle.drawWidgets = ()=>{ + g=og; + exports.origDraw(); + g=_g; + }; + + function anim(dir, callback) { + if (exports.animInterval) clearInterval(exports.interval); + exports.animInterval = setInterval(function() { + offset += dir; + let stop = false; + if (dir>0 && offset>=0) { // fully down + stop = true; + offset = 0; + } else if (dir<0 && offset<-23) { // fully up + stop = true; + offset = -24; + } + if (stop) { + clearInterval(exports.animInterval); + delete exports.animInterval; + if (callback) callback(); + } + queueDraw(); + }, 50); + } + // On swipe down, animate to show widgets + exports.swipeHandler = function(lr,ud) { + if (exports.hideTimeout) { + clearTimeout(exports.hideTimeout); + delete exports.hideTimeout; + } + if (ud>0 && offset<0) anim(4, function() { + exports.hideTimeout = setTimeout(function() { + anim(-4); + }, 2000); + }); + if (ud<0 && offset>-24) anim(-4); + + }; + Bangle.on("swipe", exports.swipeHandler); +}; diff --git a/package.json b/package.json index e11e79ae5..08f3a19ce 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "scripts": { "lint-apps": "eslint ./apps --ext .js", - "test": "node bin/sanitycheck.js && eslint ./apps --ext .js", + "test": "node bin/sanitycheck.js && eslint ./apps --ext .js && eslint ./modules --ext .js", "update-local-apps": "./bin/create_apps_json.sh apps.local.json", "local": "npm-watch & npx http-server -a localhost -c-1", "start": "npx http-server -c-1" diff --git a/webtools b/webtools new file mode 160000 index 000000000..2ab71a33d --- /dev/null +++ b/webtools @@ -0,0 +1 @@ +Subproject commit 2ab71a33d69bfda40465174ffe57adb03c21fc42