-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2866 from v-crispadvice/master
Flash Cards app
- Loading branch information
Showing
10 changed files
with
310 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
1.0: Local cards data | ||
1.1: Download cards data from Trello public board | ||
1.2: Configuration instructions added and card layout optimized | ||
1.3: Font size can be changed in Settings |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
A simple flash cards application based on Trello public board. | ||
|
||
Configuration: | ||
|
||
1. Create public Trello board | ||
2. Create new Trello list | ||
3. Add Trello cards: | ||
- card name will be flash card front text | ||
- card description will be flash card back text | ||
4. Add ".json" to the end of the Trello board URL and refresh page | ||
5. Find your list ID | ||
6. Save list ID to the "flashcards.settings.json" file on your watch, e.g.: | ||
{"listId":"65942f7b27z68000996ddc00","fontSize":1,"cardWidth":9,"swipeGesture":0} | ||
7. Connect phone with Gadgetbridge to the watch | ||
8. Enable "Allow Internet Access" in Gadgetbridge | ||
9. On the watch go to Settings -> Apps -> Flash Cards -> Get from Trello | ||
10. Start Flash Cards as watch app or set it as watch clock face | ||
11. Swipe left/right to change card | ||
12. Tap to switch card front/back text |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require("heatshrink").decompress(atob("mEwwhC/AH0iABQWKgQXLkAXhCxZIKFxgwKC68zABwWGgYXPmAX/C/4X/C/4X/C9ndACQX/C6dEAAQXS6gXDpovpR/4X/C8ENCyPQC4YA/AGo")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
/** | ||
* Copyright 2023 Crisp Advice | ||
* We believe in Finnish | ||
*/ | ||
|
||
// Modules | ||
var Layout = require("Layout"); | ||
var locale = require("locale"); | ||
var storage = require("Storage"); | ||
|
||
// Global variables | ||
let SWAP_SIDE_BUZZ_MILLISECONDS = 50; | ||
let CARD_DATA_FILE = "flashcards.data.json"; | ||
let CARD_SETTINGS_FILE = "flashcards.settings.json"; | ||
let CARD_EMPTY = "no cards found"; | ||
let cards = []; | ||
let cardIndex = 0; | ||
let backSide = false; | ||
let drawTimeout; | ||
let fontSizes = ["15%","20%","25%"]; | ||
let lastDragX = 0; | ||
let lastDragY = 0; | ||
|
||
let settings = Object.assign({ | ||
listId: "", | ||
fontSize: 1, | ||
cardWidth: 9, | ||
swipeGesture: 0 | ||
}, storage.readJSON(CARD_SETTINGS_FILE, true) || {}); | ||
|
||
// Cards data | ||
function wordWrap(textStr, maxLength) { | ||
if (maxLength == undefined) { | ||
maxLength = settings.cardWidth; | ||
} | ||
let res = ''; | ||
let str = textStr.trim(); | ||
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() { | ||
var cardsJSON = ""; | ||
if (storage.read(CARD_DATA_FILE)) | ||
{ | ||
cardsJSON = storage.readJSON(CARD_DATA_FILE, 1) || {}; | ||
} | ||
refreshCards(cardsJSON,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 queueDraw = function() { | ||
let timeout = 60000; | ||
if (drawTimeout) clearTimeout(drawTimeout); | ||
drawTimeout = setTimeout(function() { | ||
drawTimeout = undefined; | ||
draw(); | ||
}, timeout - (Date.now() % timeout)); | ||
}; | ||
|
||
let cardLayout = new Layout( { | ||
type:"v", c: [ | ||
{type:"txt", font:"6x8:3", label:"", id:"widgets", fillx:1 }, | ||
{type:"txt", font:fontSizes[settings.fontSize], label:"ABCDEFGHIJ KLMNOPQRST UVWXYZÅÖÄ", filly:1, fillx: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(); | ||
} | ||
|
||
function swipeCard(forward) | ||
{ | ||
if(forward) { | ||
cardIndex = (cardIndex + 1) % cards.length; | ||
} | ||
else if(--cardIndex < 0) { | ||
cardIndex = cards.length - 1; | ||
} | ||
drawCard(); | ||
} | ||
|
||
// Handle a touch: swap card side | ||
function handleTouch(zone, event) { | ||
backSide = !backSide; | ||
drawCard(); | ||
Bangle.buzz(SWAP_SIDE_BUZZ_MILLISECONDS); | ||
} | ||
|
||
// Handle a stroke event: cycle cards | ||
function handleStroke(event) { | ||
let first_x = event.xy[0]; | ||
let last_x = event.xy[event.xy.length - 2]; | ||
swipeCard((last_x - first_x) > 0); | ||
} | ||
|
||
// Handle a drag event: cycle cards | ||
function handleDrag(event) { | ||
let isFingerReleased = (event.b === 0); | ||
if(isFingerReleased) { | ||
let isHorizontalDrag = (Math.abs(lastDragX) >= Math.abs(lastDragY)) && | ||
(lastDragX !== 0); | ||
if(isHorizontalDrag) { | ||
swipeCard(lastDragX > 0); | ||
} | ||
} | ||
else { | ||
lastDragX = event.dx; | ||
lastDragY = event.dy; | ||
} | ||
} | ||
|
||
|
||
// initialize | ||
cardLayout.update(); | ||
Bangle.loadWidgets(); | ||
loadLocalCards(); | ||
|
||
Bangle.on("touch", handleTouch); | ||
if (settings.swipeGesture) { Bangle.on("drag", handleDrag); } else { Bangle.on("stroke", handleStroke); } | ||
|
||
// 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); | ||
if (settings.swipeGesture) { Bangle.removeListener("drag", handleDrag);} else { Bangle.removeListener("stroke", handleStroke); } | ||
}}); |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
[{"id":"634fb1548fcbf401dcb49cf4","name":"monikeitin","desc":"multicooker"},{"id":"634fb18855a957017a5d03f7","name":"lihamylly","desc":"meat grinder"},{"id":"634fb2b50f1f1101890aa7f7","name":"uuni","desc":"oven"},{"id":"633988016d09b300c3770b6d","name":"riittää","desc":"is enough"},{"id":"634fb26ea0aef000ec78e481","name":"kahvinkeitin","desc":"coffee maker"},{"id":"634fb1f8e1378600b51bb317","name":"mehulinko","desc":"juicer"},{"id":"634fb307df637101f7f36b2d","name":"palovaroitin","desc":"smoke \nsensor"},{"id":"634fb29699f51701d79a2cbb","name":"tiskikone","desc":"dishwashing \nmachine"},{"id":"634fb2bbe01e1c0446179a40","name":"liesi","desc":"kitchen\nstove"},{"id":"63419634ce475100b444d577","name":"kohtalainen","desc":"moderate"}] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"listId":"","fontSize":1,"cardWidth":9,"swipeGesture":0} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"id": "flashcards", | ||
"name": "Flash Cards", | ||
"shortName": "Flash Cards", | ||
"version": "1.3", | ||
"description": "Flash cards based on public Trello board", | ||
"readme":"README.md", | ||
"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"} | ||
] | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
(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: "", | ||
fontSize: 1, | ||
cardWidth: 9, | ||
swipeGesture: 0 | ||
}, storage.readJSON(settingsFile, true) || {}); | ||
|
||
function writeSettings() { | ||
storage.writeJSON(settingsFile, settings); | ||
} | ||
|
||
const fontSizes = [/*LANG*/"Small",/*LANG*/"Medium",/*LANG*/"Large"]; | ||
const swipeGestures = [/*LANG*/"Stroke",/*LANG*/"Drag"]; | ||
var settingsMenu = { | ||
"" : { "title" : "Flash Cards" }, | ||
"< Back" : () => back(), | ||
/*LANG*/"Get from Trello": () => { | ||
if (!storage.read(settingsFile)) { writeSettings();} | ||
E.showPrompt("Download cards?").then((v) => { | ||
let delay = 500; | ||
if (v) { | ||
if (Bangle.http) | ||
{ | ||
if (settings.listId) | ||
{ | ||
delay = delay + trelloTimeout; | ||
E.showMessage(/*LANG*/"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(cardsJSON)); | ||
E.showMessage(/*LANG*/"Downloaded"); | ||
}) | ||
.catch((e) => { | ||
E.showMessage(/*LANG*/"Error:" + e); | ||
}); | ||
} else { | ||
E.showMessage(/*LANG*/"List Id not found"); | ||
} | ||
} else { | ||
E.showMessage(/*LANG*/"Gadgetbridge not found"); | ||
} | ||
} | ||
setTimeout(() => E.showMenu(settingsMenu), delay); | ||
}); | ||
}, | ||
/*LANG*/"Font size": { | ||
value: settings.fontSize, | ||
min: 0, max: 2, wrap: true, | ||
format: v => fontSizes[v], | ||
onchange: v => { settings.fontSize = v; writeSettings(); } | ||
}, | ||
/*LANG*/"Card width": { | ||
value: settings.cardWidth, | ||
min: 6, max: 12, | ||
onchange: v => { settings.cardWidth = v; writeSettings(); } | ||
}, | ||
/*LANG*/"Swipe gesture": { | ||
value: settings.swipeGesture, | ||
min: 0, max: 1, wrap: true, | ||
format: v => swipeGestures[v], | ||
onchange: v => { settings.swipeGesture = v; writeSettings(); } | ||
} | ||
} | ||
// Show the menu | ||
E.showMenu(settingsMenu); | ||
})//(load) |