diff --git a/apps/bwclklite/ChangeLog b/apps/bwclklite/ChangeLog new file mode 100644 index 000000000..c728997da --- /dev/null +++ b/apps/bwclklite/ChangeLog @@ -0,0 +1,36 @@ +0.01: New App. +0.02: Use build in function for steps and other improvements. +0.03: Adapt colors based on the theme of the user. +0.04: Steps can be hidden now such that the time is even larger. +0.05: Included icons for information. +0.06: Design and usability improvements. +0.07: Improved positioning. +0.08: Select the color of widgets correctly. Additional settings to hide colon. +0.09: Larger font size if colon is hidden to improve readability further. +0.10: HomeAssistant integration if HomeAssistant is installed. +0.11: Performance improvements. +0.12: Implements a 2D menu. +0.13: Clicks < 24px are for widgets, if fullscreen mode is disabled. +0.14: Adds humidity to weather data. +0.15: Added option for a dynamic mode to show widgets only if unlocked. +0.16: You can now show your agenda if your calendar is synced with Gadgetbridge. +0.17: Fix - Step count was no more shown in the menu. +0.18: Set timer for an agenda entry by simply clicking in the middle of the screen. Only one timer can be set. +0.19: Fix - Compatibility with "Digital clock widget" +0.20: Better handling of async data such as getPressure. +0.21: On the default menu the week of year can be shown. +0.22: Use the new clkinfo module for the menu. +0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo. +0.24: Update clock_info to avoid a redraw +0.25: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on fw2v16. + ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc. +0.26: Use clkinfo.addInteractive instead of a custom implementation +0.27: Clean out some leftovers in the remove function after switching to +clkinfo.addInteractive that would cause ReferenceError. +0.28: Option to show (1) time only and (2) week of year. +0.29: use setItem of clockInfoMenu to change the active item +0.30: Use widget_utils +0.31: Use clock_info module as an app +0.32: Diverge from BW Clock. Change out the custom font for a standard bitmap one to speed up loading times. + Remove invertion of theme as this doesn'twork very well with fastloading. + Do an quick inital fillRect on theclock info area. diff --git a/apps/bwclklite/README.md b/apps/bwclklite/README.md new file mode 100644 index 000000000..1ad320894 --- /dev/null +++ b/apps/bwclklite/README.md @@ -0,0 +1,30 @@ +# BW Clock Lite +This is a fork of a very minimalistic clock. + +![](screenshot.png) + +## Features +The BW clock implements features that are exposed by other apps through the `clkinfo` module. +For example, if you install the Simple Timer app, this menu item will be shown if you first +touch the bottom of the screen and then swipe left/right to the Simple Timer menu. To select +sub-items simply swipe up/down. To run an action (e.g. add 5 min), simply select the clkinfo (border) and touch on the item again. See also the screenshot below: + +![](screenshot_3.png) + +Note: Check out the settings to change different themes. + +## Settings +- Screen: Normal (widgets shown), Dynamic (widgets shown if unlocked) or Full (widgets are hidden). +- Enable/disable lock icon in the settings. Useful if fullscreen mode is on. +- The colon (e.g. 7:35 = 735) can be hidden in the settings for an even larger time font to improve readability further. +- Your bangle uses the sys color settings so you can change the color too. + +## Thanks to +- Thanks to Gordon Williams not only for the great BangleJs, but specifically also for the implementation of `clkinfo` which simplified the BWClock a lot and moved complexety to the apps where it should be located. +- Icons created by Flaticon + +## Creator +[David Peer](https://github.com/peerdavid) + +## Contributors +thyttan diff --git a/apps/bwclklite/app-icon.js b/apps/bwclklite/app-icon.js new file mode 100644 index 000000000..1df0fa6a5 --- /dev/null +++ b/apps/bwclklite/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgIcah0EgEB/H8iFsAoOY4kMBYMDhmGgXkAoUGiWkAoQQBoAFCjgnCAoM4hgFDuEI+wpC8EKyg1C/0eAoMAsEAiQvBAAeAApQAB/4Ao+P4v/wn0P8Pgn/wnkH4Pjv/j/nn9PH//n/nj/IFF4F88AXBAoM88EcAoPHj//jlDAoOf/+Y+YFHjnnjAjBEIIjD+BHDO9IALA==")) diff --git a/apps/bwclklite/app.js b/apps/bwclklite/app.js new file mode 100644 index 000000000..1008eae9c --- /dev/null +++ b/apps/bwclklite/app.js @@ -0,0 +1,331 @@ +{ // must be inside our own scope here so that when we are unloaded everything disappears + +/************************************************ + * Includes + */ +const locale = require('locale'); +const storage = require('Storage'); +const clock_info = require("clock_info"); +const widget_utils = require("widget_utils"); + +/************************************************ + * Globals + */ +const SETTINGS_FILE = "bwclklite.setting.json"; +const W = g.getWidth(); +const H = g.getHeight(); + +/************************************************ + * Settings + */ +let settings = { + screen: "Normal", + showLock: true, + hideColon: false, + menuPosX: 0, + menuPosY: 0, +}; + +let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; +for (const key in saved_settings) { + settings[key] = saved_settings[key]; +} + +let isFullscreen = function() { + let s = settings.screen.toLowerCase(); + if(s == "dynamic"){ + return Bangle.isLocked(); + } else { + return s == "full"; + } +}; + +let getLineY = function(){ + return H/5*2 + (isFullscreen() ? 0 : 8); +}; + +/************************************************ + * Assets + */ +let imgLock = function() { + return { + width : 16, height : 16, bpp : 1, + transparent : 0, + buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w=")) + }; +}; + + +/************************************************ + * Clock Info + */ +let clockInfoItems = clock_info.load(); + +// Add some custom clock-infos +let weekOfYear = function() { + let date = new Date(); + date.setHours(0, 0, 0, 0); + // Thursday in current week decides the year. + date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7); + // January 4 is always in week 1. + let week1 = new Date(date.getFullYear(), 0, 4); + // Adjust to Thursday in week 1 and count number of weeks from date to week1. + return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 + - 3 + (week1.getDay() + 6) % 7) / 7); +}; + +clockInfoItems[0].items.unshift({ name : "weekofyear", + get : function() { return { text : "Week " + weekOfYear(), + img : null};}, + show : function() {}, + hide : function() {}, +}); + +// Empty for large time +clockInfoItems[0].items.unshift({ name : "nop", + get : function() { return { text : null, + img : null};}, + show : function() {}, + hide : function() {}, +}); + + + +let clockInfoMenu = clock_info.addInteractive(clockInfoItems, { + app: "bwclklite", + x : 0, + y: 135, + w: W, + h: H-135, + draw : (itm, info, options) => { + let hideClkInfo = info.text == null; + + g.setColor(g.theme.fg); + g.fillRect(options.x, options.y, options.x+options.w, options.y+options.h); + + g.setFontAlign(0,0); + g.setColor(g.theme.bg); + + if (options.focus){ + let y = hideClkInfo ? options.y+20 : options.y+2; + let h = hideClkInfo ? options.h-20 : options.h-2; + g.drawRect(options.x, y, options.x+options.w-2, y+h-1); // show if focused + g.drawRect(options.x+1, y+1, options.x+options.w-3, y+h-2); // show if focused + } + + // In case we hide the clkinfo, we show the time again as the time should + // be drawn larger. + if(hideClkInfo){ + drawTime(); + return; + } + + // Set text and font + let image = info.img; + let text = String(info.text); + if(text.split('\n').length > 1){ + g.setFont("6x8"); //g.setMiniFont(); + } else { + g.setFont("6x8:3"); //g.setSmallFont(); + } + + // Compute sizes + let strWidth = g.stringWidth(text); + let imgWidth = image == null ? 0 : 24; + let midx = options.x+options.w/2; + + // Draw + if (image) { + let scale = imgWidth / image.width; + g.drawImage(image, midx-parseInt(imgWidth*1.3/2)-parseInt(strWidth/2), options.y+6, {scale: scale}); + } + g.drawString(text, midx+parseInt(imgWidth*1.3/2), options.y+20); + + // In case we are in focus and the focus box changes (fullscreen yes/no) + // we draw the time again. Otherwise it could happen that a while line is + // not cleared correctly. + if(options.focus) drawTime(); + } +}); + + +/************************************************ + * Draw + */ +let draw = function() { + // Queue draw again + queueDraw(); + + // Draw clock + drawDate(); + drawTime(); + drawLock(); + drawWidgets(); +}; + + +let drawDate = function() { + // Draw background + let y = getLineY(); + g.reset().clearRect(0,0,W,y); + + // Draw date + y = parseInt(y/2)+4; + y += isFullscreen() ? 0 : 8; + let date = new Date(); + let dateStr = date.getDate(); + dateStr = ("0" + dateStr).substr(-2); + g.setFont("6x8:4"); //g.setMediumFont(); // Needed to compute the width correctly + let dateW = g.stringWidth(dateStr); + + g.setFont("6x8:3"); //g.setSmallFont(); + let dayStr = locale.dow(date, true); + let monthStr = locale.month(date, 1); + let dayW = Math.max(g.stringWidth(dayStr), g.stringWidth(monthStr)); + let fullDateW = dateW + 10 + dayW; + + g.setFontAlign(-1,0); + g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-12); + g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+11); + + g.setFont("6x8:4"); //g.setMediumFont(); + g.setColor(g.theme.fg); + g.drawString(dateStr, W/2 - fullDateW / 2, y+2); +}; + + +let drawTime = function() { + let hideClkInfo = clockInfoMenu.menuA == 0 && clockInfoMenu.menuB == 0; + + // Draw background + let y1 = getLineY(); + let y = y1; + let date = new Date(); + + let hours = String(date.getHours()); + let minutes = date.getMinutes(); + minutes = minutes < 10 ? String("0") + minutes : minutes; + let colon = settings.hideColon ? "" : ":"; + let timeStr = hours + colon + minutes; + + // Set y coordinates correctly + y += parseInt((H - y)/2) + 5; + + if (hideClkInfo){ + g.setFont("6x8:5"); //g.setLargeFont(); + } else { + y -= 15; + g.setFont("6x8:4"); //g.setMediumFont(); + } + + // Clear region and draw time + g.setColor(g.theme.fg); + g.fillRect(0,y1,W,y+20 + (hideClkInfo ? 1 : 0) + (isFullscreen() ? 3 : 0)); + + g.setColor(g.theme.bg); + g.setFontAlign(0,0); + g.drawString(timeStr, W/2, y); +}; + + +let drawLock = function() { + if(settings.showLock && Bangle.isLocked()){ + g.setColor(g.theme.fg); + g.drawImage(imgLock(), W-16, 2); + } +}; + + +let drawWidgets = function() { + if(isFullscreen()){ + widget_utils.hide(); + } else { + Bangle.drawWidgets(); + } +}; + + +/************************************************ + * Listener + */ +// timeout used to update every minute +let drawTimeout; + +// schedule a draw for the next minute +let queueDraw = function() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +}; + + +// Stop updates when LCD is off, restart when on +let lcdListenerBw = function(on) { + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}; +Bangle.on('lcdPower', lcdListenerBw); + +let lockListenerBw = function(isLocked) { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + + if(!isLocked && settings.screen.toLowerCase() == "dynamic"){ + // If we have to show the widgets again, we load it from our + // cache and not through Bangle.loadWidgets as its much faster! + widget_utils.show(); + } + + draw(); +}; +Bangle.on('lock', lockListenerBw); + +let charging = function(charging){ + // Jump to battery + clockInfoMenu.setItem(0, 2); + drawTime(); +}; +Bangle.on('charging', charging); + +let kill = function(){ + clockInfoMenu.remove(); + delete clockInfoMenu; +}; +E.on("kill", kill); + +/************************************************ + * Startup Clock + */ + +// Show launcher when middle button pressed +Bangle.setUI({ + mode : "clock", + remove : function() { + // Called to unload all of the clock app + Bangle.removeListener('lcdPower', lcdListenerBw); + Bangle.removeListener('lock', lockListenerBw); + Bangle.removeListener('charging', charging); + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + // save settings + kill(); + E.removeListener("kill", kill); + Bangle.removeListener('charging', charging); + widget_utils.show(); + } +}); + +// Load widgets and draw clock the first time +Bangle.loadWidgets(); + +// Draw first time +g.setColor(g.theme.fg).fillRect(0,135,W,H); // Otherwise this rect will wait for clock_info before updating +draw(); + +} // End of app scope diff --git a/apps/bwclklite/app.png b/apps/bwclklite/app.png new file mode 100644 index 000000000..5073f0ed0 Binary files /dev/null and b/apps/bwclklite/app.png differ diff --git a/apps/bwclklite/metadata.json b/apps/bwclklite/metadata.json new file mode 100644 index 000000000..bab852623 --- /dev/null +++ b/apps/bwclklite/metadata.json @@ -0,0 +1,43 @@ +{ + "id": "bwclklite", + "name": "BW Clock Lite", + "version": "0.32", + "description": "A very minimalistic clock. This version of BW Clock is quicker at the cost of the custom font.", + "readme": "README.md", + "icon": "app.png", + "screenshots": [ + { + "url": "screenshot.png" + }, + { + "url": "screenshot_2.png" + }, + { + "url": "screenshot_3.png" + } + ], + "type": "clock", + "tags": "clock,clkinfo", + "supports": [ + "BANGLEJS2" + ], + "dependencies": { + "clock_info": "module" + }, + "allow_emulator": true, + "storage": [ + { + "name": "bwclklite.app.js", + "url": "app.js" + }, + { + "name": "bwclklite.img", + "url": "app-icon.js", + "evaluate": true + }, + { + "name": "bwclklite.settings.js", + "url": "settings.js" + } + ] +} diff --git a/apps/bwclklite/screenshot.png b/apps/bwclklite/screenshot.png new file mode 100644 index 000000000..28983c9c4 Binary files /dev/null and b/apps/bwclklite/screenshot.png differ diff --git a/apps/bwclklite/screenshot_2.png b/apps/bwclklite/screenshot_2.png new file mode 100644 index 000000000..8d2f1717f Binary files /dev/null and b/apps/bwclklite/screenshot_2.png differ diff --git a/apps/bwclklite/screenshot_3.png b/apps/bwclklite/screenshot_3.png new file mode 100644 index 000000000..573675d28 Binary files /dev/null and b/apps/bwclklite/screenshot_3.png differ diff --git a/apps/bwclklite/settings.js b/apps/bwclklite/settings.js new file mode 100644 index 000000000..2d3916a3d --- /dev/null +++ b/apps/bwclklite/settings.js @@ -0,0 +1,50 @@ +(function(back) { + const SETTINGS_FILE = "bwclklite.setting.json"; + + // initialize with default settings... + const storage = require('Storage') + let settings = { + screen: "Normal", + showLock: true, + hideColon: false, + }; + let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; + for (const key in saved_settings) { + settings[key] = saved_settings[key] + } + + function save() { + storage.write(SETTINGS_FILE, settings) + } + + var screenOptions = ["Normal", "Dynamic", "Full"]; + E.showMenu({ + '': { 'title': 'BW Clock' }, + '< Back': back, + 'Screen': { + value: 0 | screenOptions.indexOf(settings.screen), + min: 0, max: 2, + format: v => screenOptions[v], + onchange: v => { + settings.screen = screenOptions[v]; + save(); + }, + }, + 'Show Lock': { + value: settings.showLock, + format: () => (settings.showLock ? 'Yes' : 'No'), + onchange: () => { + settings.showLock = !settings.showLock; + save(); + }, + }, + 'Hide Colon': { + value: settings.hideColon, + format: () => (settings.hideColon ? 'Yes' : 'No'), + onchange: () => { + settings.hideColon = !settings.hideColon; + save(); + }, + } + }); + })