diff --git a/apps/flashcards/README.md b/apps/flashcards/README.md new file mode 100644 index 000000000..948ee7a0e --- /dev/null +++ b/apps/flashcards/README.md @@ -0,0 +1,3 @@ +A simple flash cards application based on Trello public board. + + diff --git a/apps/flashcards/app-icon.js b/apps/flashcards/app-icon.js new file mode 100644 index 000000000..72371cac4 --- /dev/null +++ b/apps/flashcards/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH0iABQWKgQXLkAXhCxZIKFxgwKC68zABwWGgYXPmAX/C/4X/C/4X/C9ndACQX/C6dEAAQXS6gXDpovpR/4X/C8ENCyPQC4YA/AGo")) diff --git a/apps/flashcards/app.js b/apps/flashcards/app.js new file mode 100644 index 000000000..7c689a6a8 --- /dev/null +++ b/apps/flashcards/app.js @@ -0,0 +1,158 @@ +/** + * Copyright 2023 SHOGEL + * We believe in Finnish + */ +var Layout = require("Layout"); +var locale = require("locale"); +var storage = require("Storage"); + +// Constants +let SWAP_SIDE_BUZZ_MILLISECONDS = 50; +let CARD_DATA_FILE = "flashcards.data.json"; +let CARD_EMPTY = "empty card"; +let CARD_LINE_LENGTH = 9; +let CARD_LINE_FONT = "20%"; + +// Global variables +let cards = []; + +let cardIndex = 0; +let backSide = false; +let lastDragX = 0; +let lastDragY = 0; + +// Cards data +function wordWrap(str, maxLength) { + if (maxLength == undefined) { + maxLength = CARD_LINE_LENGTH; + } + let res = ''; + while (str.length > maxLength) { + let found = false; + // Inserts new line at first whitespace of the line + for (i = maxLength - 1; i >= 0; i--) { + if (str.charAt(i)==' ') { + res = res + [str.slice(0, i), "\n"].join(''); + str = str.slice(i + 1); + found = true; + break; + } + } + // Inserts new line at MAX_LENGTH position, the word is too long to wrap + if (!found) { + res += [str.slice(0, maxLength), "\n"].join(''); + str = str.slice(maxLength); + } + } + return res + str; +} + +function loadLocalCards() { + if (storage.read(CARD_DATA_FILE)) + { + refreshCards(storage.readJSON(CARD_DATA_FILE, 1) || {},false); + } +} + +function refreshCards(cardsJSON,showMsg) +{ + cardIndex = 0; + backSide = false; + cards = []; + + if (cardsJSON && cardsJSON.length) { + cardsJSON.forEach(card => { + cards.push([ wordWrap(card.name), wordWrap(card.desc) ]); + }); + } + + if (!cards.length) { + cards.push([ wordWrap(CARD_EMPTY), wordWrap(CARD_EMPTY) ]); + drawMessage("e: cards not found"); + } else if (showMsg) { + drawMessage("i: cards refreshed"); + } +} + +// Drawing a card +let drawTimeout; +let queueDraw = function() { + let timeout = 60000; + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, timeout - (Date.now() % timeout)); +}; + +var cardLayout = new Layout( { + type:"v", c: [ + {type:"txt", font:"6x8:3", label:"", id:"widgets", fillx:1 }, + {type:"txt", font:CARD_LINE_FONT, label:"ABCDEFGHIJ KLMNOPQRST UVWXYZÅÄÖ", filly:1, id:"card" }, + {type:"txt", font:"6x8:2", label:"00:00", id:"clock", fillx:1, bgCol:g.theme.fg, col:g.theme.bg } + ] +}, {lazy:true}); + +function drawCard() { + cardLayout.card.label = cards[cardIndex][backSide ? 1 : 0]; + cardLayout.clock.label = locale.time(new Date(),1); + cardLayout.render(); +} + +function drawMessage(msg) { + cardLayout.card.label = wordWrap(msg); + cardLayout.render(); + console.log(msg); +} + +function draw() { + drawCard(); + Bangle.drawWidgets(); + queueDraw(); +} + +// Handle a touch: swap card side +function handleTouch(zone, event) { + backSide = !backSide; + drawCard(); + Bangle.buzz(SWAP_SIDE_BUZZ_MILLISECONDS); +} + +// Handle a drag event: cycle cards +function handleDrag(event) { + let first_x = event.xy[0]; + let last_x = event.xy[event.xy.length - 2]; + let xdiff = last_x - first_x; + /* + let first_y = event.xy[1]; + let last_y = event.xy[event.xy.length - 1]; + let ydiff = last_y - first_y; + */ + if(xdiff > 0) { + cardIndex = (cardIndex + 1) % cards.length; + } + else if(--cardIndex < 0) { + cardIndex = cards.length - 1; + } + drawCard(); +} + + +// initialize +loadLocalCards(); +cardLayout.update(); +Bangle.loadWidgets(); +Bangle.on("touch", handleTouch); +Bangle.on("stroke", handleDrag); + +// On start: display the first card +g.clear(); +draw(); + +// cleanup +Bangle.setUI({mode:"clock", remove:function() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + Bangle.removeListener("touch", handleTouch); + Bangle.removeListener("stroke", handleDrag); +}}); diff --git a/apps/flashcards/app.png b/apps/flashcards/app.png new file mode 100644 index 000000000..e16b8a263 Binary files /dev/null and b/apps/flashcards/app.png differ diff --git a/apps/flashcards/metadata.json b/apps/flashcards/metadata.json new file mode 100644 index 000000000..bdba0b9b0 --- /dev/null +++ b/apps/flashcards/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "flashcards", + "name": "Flash Cards", + "shortName": "Flash Cards", + "version": "1.1", + "description": "Flash cards based on public Trello board", + "screenshots" : [ { "url":"screenshot.png" }], + "icon": "app.png", + "tags": "flash cards", + "type": "clock", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"flashcards.app.js","url":"app.js"}, + {"name":"flashcards.settings.js","url":"settings.js"}, + {"name":"flashcards.img","url":"app-icon.js","evaluate":true} + ], + "data": [ + {"name":"flashcards.data.json"}, + {"name":"flashcards.settings.json"} + ] +} diff --git a/apps/flashcards/screenshot.png b/apps/flashcards/screenshot.png new file mode 100644 index 000000000..f9c5bcb63 Binary files /dev/null and b/apps/flashcards/screenshot.png differ diff --git a/apps/flashcards/settings.js b/apps/flashcards/settings.js new file mode 100644 index 000000000..de66488cc --- /dev/null +++ b/apps/flashcards/settings.js @@ -0,0 +1,56 @@ +(function(back) { + var storage = require("Storage"); + + var settingsFILE = "flashcards.settings.json"; + var dataFile = "flashcards.data.json"; + var trelloTimeout = 3000; + var trelloURL = "https://api.trello.com/1/lists/$cardsListId/cards/?fields=name%2Cdesc%2Clist"; + + var settings = Object.assign({ + listId: "" + }, storage.readJSON(settingsFILE, true) || {}); + + function writeSettings() { + storage.writeJSON(FILE, settings); + } + + var settingsMenu = { + "" : { "title" : "Flash Cards" }, + "< Back" : () => back(), + "Get from Trello": () => { + E.showPrompt("Download cards?").then((v) => { + let delay = 500; + if (v) { + if (Bangle.http) + { + if (settings.listId.length) + { + delay = delay + trelloTimeout; + E.showMessage('i: downloading'); + Bangle.http(trelloURL.replace("$cardsListId", settings.listId), + { + timeout : trelloTimeout, + method: "GET", + headers: { "Content-Type": "application/json" } + }).then(data=>{ + var cardsJSON = JSON.parse(data.resp); + storage.write(dataFile, JSON.stringify(result)); + E.showMessage('i: downloaded'); + }) + .catch((e) => { + E.showMessage("e: " + e); + }); + } else { + E.showMessage("e: list Id not found"); + } + } else { + E.showMessage("e: Gadgetbridge not found"); + } + } + setTimeout(() => E.showMenu(settingsMenu), delay); + }); + } + } + // Show the menu + E.showMenu(settingsMenu); +})(load) \ No newline at end of file