Skip to content

Commit

Permalink
Merge pull request #2866 from v-crispadvice/master
Browse files Browse the repository at this point in the history
Flash Cards app
  • Loading branch information
gfwilliams committed Jul 12, 2023
2 parents 8af5885 + 38de211 commit 3f0e746
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 0 deletions.
4 changes: 4 additions & 0 deletions apps/flashcards/ChangeLog
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
19 changes: 19 additions & 0 deletions apps/flashcards/README.md
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
1 change: 1 addition & 0 deletions apps/flashcards/app-icon.js
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"))
183 changes: 183 additions & 0 deletions apps/flashcards/app.js
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();

Check warning on line 167 in apps/flashcards/app.js

View workflow job for this annotation

GitHub Actions / build

Clock flashcards file calls loadWidgets before setUI (clock widget/etc won't be aware a clock app is running)
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); }
}});
Binary file added apps/flashcards/app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/flashcards/flashcards.data.json
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"}]
1 change: 1 addition & 0 deletions apps/flashcards/flashcards.settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"listId":"","fontSize":1,"cardWidth":9,"swipeGesture":0}
22 changes: 22 additions & 0 deletions apps/flashcards/metadata.json
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"}
]
}
Binary file added apps/flashcards/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 79 additions & 0 deletions apps/flashcards/settings.js
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)

0 comments on commit 3f0e746

Please sign in to comment.