diff --git a/apps/timecal/ChangeLog b/apps/timecal/ChangeLog new file mode 100644 index 000000000..43bff461d --- /dev/null +++ b/apps/timecal/ChangeLog @@ -0,0 +1,9 @@ +0.01: Initial creation of the clock face time and calendar +0.02: Feature Request #1154 and some findings... + -> get rendered time from optimisations + -> *BATT SAFE* only update once a minute instead of once a second + -> *RAM optimized* clean code, corrected minute update (timout, no intervall) + -> locale: weekday name (first two characters) from locale + -> added settings to render cal view begin day (-1: today, 0:sunday, 1:monday [default]) +0.03: a lot of more settings for outline, colors and highlights +0.04: finalized README, fixed settings cancel, fixed border-setting \ No newline at end of file diff --git a/apps/timecal/README.md b/apps/timecal/README.md new file mode 100644 index 000000000..d26f9ba4d --- /dev/null +++ b/apps/timecal/README.md @@ -0,0 +1,22 @@ +# Calendar Clock + +## Features +Shows the +* Date +* Time (hh:mm) - respecting 12/24 (uses locale string) +* 3 weeks calendar view (last,current and next week) + +### The settings menu +Calendar View can be customized +* < Save: Exist and save the current settings +* Show date: Choose if and how the date is displayed: none, locale (default), monthfull or monthshort.yearshort #weeknum with 0 prefixed +* Start wday: Set day of week start. Values: 0=Sunday, 1=Monday,...,6=Saturday or -1=Relative to today (default 0: Sunday) +* Su color: Set Sundays color. Values: none (default), red, green or blue +* Border: show or none (default) +* Submenu Today settings - choose how today is highlighted + * < Back: + * Color: none, red (default), green or blue + * Marker: Outline today graphically. Values: none (default), circle, rect(angle) + * Mrk.Color: Circle/rectangle color: red (default), green or blue + * Mrk.Size: Circle/rectangle thickness in pixel: min:1, max: 10, default:3 +* < Cancel: Exit and no change. Nevertheless missing default settings and superflous settings will be removed and saved. diff --git a/apps/timecal/metadata.json b/apps/timecal/metadata.json index 3237dd08a..4dd8a8ca0 100644 --- a/apps/timecal/metadata.json +++ b/apps/timecal/metadata.json @@ -1,13 +1,16 @@ { "id": "timecal", "name": "TimeCal", "shortName":"TimeCal", + "version":"0.04", + "description": "TimeCal shows the date/time along with a 3 week calendar", "icon": "icon.png", - "version":"0.01", - "description": "TimeCal shows the Time along with a 3 week calendar", - "tags": "clock", "type": "clock", + "tags": "clock,calendar", "supports":["BANGLEJS2"], + "readme": "README.md", + "allow_emulator":true, "storage": [ - {"name":"timecal.app.js","url":"timecal.app.js"} + {"name":"timecal.app.js","url":"timecal.app.js"}, + {"name":"timecal.settings.js","url":"timecal.settings.js"} ] } diff --git a/apps/timecal/testing/timecal.app.test.js b/apps/timecal/testing/timecal.app.test.js new file mode 100644 index 000000000..e41f3d848 --- /dev/null +++ b/apps/timecal/testing/timecal.app.test.js @@ -0,0 +1,798 @@ +//Clock renders date, time and pre,current,next week calender view +class TimeCalClock{ + DATE_FONT_SIZE(){ return 20; } + TIME_FONT_SIZE(){ return 40; } + + /** + * @param{Date} date optional the date (e.g. for testing) + * @param{Settings} settings optional settings to use e.g. for testing + */ + constructor(date, settings){ + if (date) + this.date=date; + + if (settings) + this._settings = settings; + else + this._settings = require("Storage").readJSON("timecal.settings.json", 1) || {}; + + const defaults = { + shwDate:1, //0:none, 1:locale, 2:month, 3:monthshort.year #week + + wdStrt:0, //identical to getDay() 0->Su, 1->Mo, ... //Issue #1154: weekstart So/Mo, -1 for use today + + tdyNumClr:3, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E + tdyMrkr:0, //0:none, 1:circle, 2:rectangle, 3:filled + tdyMrkClr:2, //1:red=#E00, 2:green=#0E0, 3:blue=#00E + tdyMrkPxl:3, //px + + suClr:1, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E + //phColor:"#E00", //public holiday + + calBrdr:false + }; + for (const k in this._settings) if (!defaults.hasOwnProperty(k)) delete this._settings[k]; //remove invalid settings + for (const k in defaults) if(!this._settings.hasOwnProperty(k)) this._settings[k] = defaults[k]; //assign missing defaults + + g.clear(); + Bangle.setUI("clock"); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + + this.centerX = Bangle.appRect.w/2; + this.nrgb = [g.theme.fg, "#E00", "#0E0", "#00E"]; //fg, r ,g , b + + this.ABR_DAY=[]; + if (require("locale") && require("locale").dow) + for (let d=0; d<=6; d++) { + var refDay=new Date(); + refDay.setFullYear(1972); + refDay.setMonth(0); + refDay.setDate(2+d); + this.ABR_DAY.push(require("locale").dow(refDay)); + + } + else + this.ABR_DAY=["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + } + + /** + * @returns {Object} current settings object + */ + settings(){ + return this._settings; + } + + + /* + * Run forest run + **/ + draw(){ + this.drawTime(); + + if (this.TZOffset===undefined || this.TZOffset!==d.getTimezoneOffset()) + this.drawDateAndCal(); + } + + /** + * draw given or current time from date + * overwatch timezone changes + * schedules itself to update + */ + drawTime(){ + d=this.date ? this.date : new Date(); + const Y=Bangle.appRect.y+this.DATE_FONT_SIZE()+10; + + d=d?d :new Date(); + + g.setFontAlign(0, -1).setFont("Vector", this.TIME_FONT_SIZE()).setColor(g.theme.fg) + .clearRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+this.TIME_FONT_SIZE()-7) + .drawString(("0" + require("locale").time(d, 1)).slice(-5), this.centerX, Y, true); + //.drawRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+this.TIME_FONT_SIZE()-7); //DEV-Option + + setTimeout(this.draw.bind(this), 60000-(d.getSeconds()*1000)-d.getMilliseconds()); + } + + /** + * draws given date and cal + * @param{Date} d provide date or uses today + */ + drawDateAndCal(){ + d=this.date ? this.date : new Date(); + + this.TZOffset=d.getTimezoneOffset(); + this.drawDate(); + this.drawCal(); + + if (this.tOutD) //abort exisiting + clearTimeout(this.tOutD); + this.tOutD=setTimeout(this.drawDateAndCal.bind(this), 86400000-(d.getHours()*24*60*1000)-(d.getMinutes()*60*1000)-d.getSeconds()-d.getMilliseconds()); + } + + /** + * draws given date as defiend in settings + */ + drawDate(){ + d=this.date ? this.date : new Date(); + + const FONT_SIZE=20; + const Y=Bangle.appRect.y; + var render=false; + var dateStr = ""; + if (this.settings().shwDate>0) { //skip if exactly -none + const dateSttngs = ["","l","M","m.Y #W"]; + for (let c of dateSttngs[this.settings().shwDate]) { //add part as configured + switch (c){ + case "l":{ //locale + render=true; + dateStr+=require("locale").date(d,1); + break; + } + case "m":{ //month e.g. Jan. + render=true; + dateStr+=require("locale").month(d,1); + break; + } + case "M":{ //month e.g. January + render=true; + dateStr+=require("locale").month(d,0); + break; + } + case "y":{ //year e.g. 22 + render=true; + dateStr+=d.getFullYear().slice(-2); + break; + } + case "Y":{ //year e.g. 2022 + render=true; + dateStr+=d.getFullYear(); + break; + } + case "w":{ //week e.g. #2 + dateStr+=(this.ISO8601calWeek(d)); + break; + } + case "W":{ //week e.g. #02 + dateStr+=("0"+this.ISO8601calWeek(d)).slice(-2); + break; + } + default: //append c + dateStr+=c; + render=dateStr.length>0; + break; //noop + } + } + } + if (render){ + g.setFont("Vector", FONT_SIZE).setColor(g.theme.fg).setFontAlign(0, -1).clearRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+FONT_SIZE-3).drawString(dateStr,this.centerX,Y); + } + //g.drawRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+FONT_SIZE-3); //DEV-Option + } + + /** + * draws calender week view (-1,0,1) for given date + */ + drawCal(){ + d=this.date ? this.date : new Date(); + + const DAY_NAME_FONT_SIZE=10; + const CAL_Y=Bangle.appRect.y+this.DATE_FONT_SIZE()+10+this.TIME_FONT_SIZE()+3; + const CAL_AREA_H=Bangle.appRect.h-CAL_Y+24; //+24: top widegtes only + const CELL_W=Bangle.appRect.w/7; //cell width + const CELL_H=(CAL_AREA_H-DAY_NAME_FONT_SIZE)/3; //cell heigth + const DAY_NUM_FONT_SIZE=Math.min(CELL_H-1,15); //size down, max 15 + + g.setFont("Vector", DAY_NAME_FONT_SIZE).setColor(g.theme.fg).setFontAlign(-1, -1).clearRect(Bangle.appRect.x, CAL_Y, Bangle.appRect.x2, CAL_Y+CAL_AREA_H); + + //draw grid & Headline + const dNames = this.ABR_DAY.map((a) => a.length<=2 ? a : a.substr(0, 2)); //force shrt 2 + for(var dNo=0; dNo=0 ? (dNo+this.settings().wdStrt)%7 : (dNo+d.getDay()+4)%7; + const dName=dNames[dIdx]; + if(dNo>0) + g.drawLine(dNo*CELL_W, CAL_Y, dNo*CELL_W, CAL_Y+CAL_AREA_H-1); + + if (dIdx==0) g.setColor(this.nrgb[this.settings().suClr]); //sunday maybe colorize txt + g.drawString(dName, dNo*CELL_W+(CELL_W-g.stringWidth(dName))/2+2, CAL_Y+1).setColor(g.theme.fg); + } + + var nextY=CAL_Y+DAY_NAME_FONT_SIZE; + + for(i=0; i<3; i++){ + const y=nextY+i*CELL_H; + g.drawLine(Bangle.appRect.x, y, Bangle.appRect.x2, y); + } + + g.setFont("Vector", DAY_NUM_FONT_SIZE); + + //write days + const tdyDate=d.getDate(); + const days=this.settings().wdStrt>=0 ? 7+((7+d.getDay()-this.settings().wdStrt)%7) : 10;//start day (week before=7 days + days in this week realtive to week start) or fixed 7+3 days + var rD=new Date(d.getTime()); + rD.setDate(rD.getDate()-days); + var rDate=rD.getDate(); + for(var y=0; y<3; y++){ + for(var x=0; x", + cases: [ + { + value: "required,", + beforeTxt: "optional,", + beforeExpression: "optional,", + afterText: "optional,", + afterExpression: "optional," + } + ], + constructorParams: ["optional: ","|TEST_SETTINGS|","..."], //TEST_SETTINGS will be replcaed with each current {setting: case} + functionNames: ["required, ", "..."], + functionParams: ["optional: ","|TEST_SETTINGS|","..."] + }; + } + + constructor(data){ + + this._validate(data); + + this.setting = data.setting; + this.cases = data.cases.map((entry) => { + return { + value: entry.value, + beforeTxt: entry.beforeTxt||"", + beforeExpression: entry.beforeExpression||true, + afterTxt: entry.afterTxt||"", + afterExpression: entry.afterExpression||true + }; + }); + this.constructorParams = data.constructorParams; + this.functionNames = data.functionNames; + this.functionParams = data.functionParams; + } + + /** + * validates the given data config + */ + _validate(data){ + //validate given config + if (!data.setting) throw new EmptyMandatoryError("setting", data, this.TEST_SETTING_SAMPLE()); + if (!(data.cases instanceof Array) || data.cases.length==0) throw new EmptyMandatoryError("cases", data, this.TEST_SETTING_SAMPLE()); + if (!(data.functionNames instanceof Array) || data.functionNames==0) throw new EmptyMandatoryError("functionNames", data, this.TEST_SETTING_SAMPLE()); + + data.cases.forEach((entry,idx) => { + if (entry.value === undefined) throw new EmptyMandatoryError("cases["+idx+"].value", entry, this.TEST_SETTING_SAMPLE()); + }); + } +} + +/*************************************************************************/ + +/** + * Testing a Bangle object + */ +class BangleTestRunner{ + /** + * create for ObjClass + * @param {Class} objClass + * @param {LogSeverity} minSeverity to Log + */ + constructor(objClass, minSeverity){ + this.TESTCASE_MSG_BEFORE_TIMEOUT = 1000; //5s + this.TESTCASE_RUN_TIMEOUT = 1000; //5s + this.TESTCASE_MSG_AFTER_TIMEOUT = 1000; //5s + + this.oClass = objClass; + this.minSvrty = minSeverity; + this.tests = []; + + this.currentCaseNum = this.currentTestNum = this.currentTest = this.currentCase = undefined; + } + + /** + * add a Setting Test, return instance for chaining + * @param {TestSetting} + */ + addTestSettings(sttngs) { + this.tests.push(new TestSetting(sttngs)); + return this; + } + + /** + * Test execution of all tests + */ + execute() { + this._init(); + while (this._nextTest()) { + this._beforeTest(); + while (this._nextCase()) { + this._beforeCase(); + this._runCase(); + this._afterCase(); + } + this._afterTest(); + this._firstCase(); + } + this._exit(); + } + + /** + * global prepare - before all test + */ + _init() { + console.log(this._nowTime(), ">>init"); + this.currentTestNum=-1; + this.currentCaseNum=-1; + } + + /** + * before each test + */ + _beforeTest() { + console.log(this._nowTime(), ">>test #" + this.currentTestNum); + } + + /** + * befor each testcase + */ + _beforeCase() { + console.log(this.currentTest); + console.log(this._nowTime(), ">>case #" + this.currentTestNum + "." + this.currentCaseNum + "/" + (this.currentTest.cases.length-1)); + if (this.currentTest instanceof TestSetting) + console.log(this.currentTest.setting+"="+this.currentCase.value+"/n"+(this.currentCase.beforeTxt ? "#"+this.currentCase.beforeTxt : "")); + } + + /** + * testcase runner + */ + _runCase() { + console.log(this._nowTime(), ">>running..."); + var returns = []; + this.currentTest.functionNames.forEach((fName) => { + var settings={}; settings[this.currentTest.setting] = this.currentCase.value; + var cParams = this.currentTest.constructorParams||[]; + cParams = cParams.map((v) => (v && v instanceof String && v==="|TEST_SETTINGS|") ? settings : v);//replace settings in call params + var fParams = this.currentTest.functionParams||[]; + fParams = fParams.map((v) => (v && v instanceof String && v==="|TEST_SETTINGS|") ? settings : v);//replace settings in call params + + var creatorFunc = new Function("console.log('Constructor params:', arguments); return new " + this.oClass + "(arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5],arguments[6],arguments[7],arguments[8],arguments[9])"); //prepare spwan arguments[0],arguments[1] + let instance = creatorFunc.call(this.oClass, cParams[0], cParams[1], cParams[2], cParams[3], cParams[4], cParams[5], cParams[6], cParams[7], cParams[8], cParams[9]); //spwan + + console.log(">>"+this.oClass+"["+fName+"]()"); + + console.log('Instance:', instance); + console.log('Function params:', fParams); + returns.push(instance[fName](fParams[0], fParams[1], fParams[2], fParams[3], fParams[4], fParams[5], fParams[6], fParams[7], fParams[8], fParams[9])); //run method and store result + g.dump(); + console.log("<<"+this.oClass+"["+fName+"]()"); + }); + console.log(this._nowTime(), "<<...running"); + } + + /** + * after each testcase + */ + _afterCase() { + if (this.currentTest instanceof TestSetting) + if (this.currentCase.afterTxt.length>0) + console.log("++EXPECTED:" + this.currentCase.afterTxt + "EXPECTED++"); + console.log(this._nowTime(), "< setTimeout(resolve, sec)); + } + + _waits(sec) { + this._delay(1).then(); + } + + _log() { + + } + + _nextTest() { + if (this.currentTestNum>=-1 && (this.currentTestNum+1)=-1 && (this.currentCaseNum+1)Su, 1->Mo, ... //Issue #1154: weekstart So/Mo, -1 for use today + + tdyNumClr:3, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E + tdyMrkr:0, //0:none, 1:circle, 2:rectangle, 3:filled + tdyMrkClr:2, //1:red=#E00, 2:green=#0E0, 3:blue=#00E + tdyMrkPxl:3, //px + + suClr:1, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E + //phColor:"#E00", //public holiday + + calBrdr:false + }; + for (const k in this._settings) if (!defaults.hasOwnProperty(k)) delete this._settings[k]; //remove invalid settings + for (const k in defaults) if(!this._settings.hasOwnProperty(k)) this._settings[k] = defaults[k]; //assign missing defaults + + g.clear(); + Bangle.setUI("clock"); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + + this.centerX = Bangle.appRect.w/2; + this.nrgb = [g.theme.fg, "#E00", "#0E0", "#00E"]; //fg, r ,g , b + + this.ABR_DAY=[]; + if (require("locale") && require("locale").dow) + for (let d=0; d<=6; d++) { + var refDay=new Date(); + refDay.setFullYear(1972); + refDay.setMonth(0); + refDay.setDate(2+d); + this.ABR_DAY.push(require("locale").dow(refDay)); -function drawCal(d){ - var calStart = 101; - var cellSize = g.getWidth() / 7; - var halfSize = cellSize / 2; - g.clearRect(0,calStart,g.getWidth(),g.getHeight()); - g.drawLine(0,calStart,g.getWidth(),calStart); - var days = ["Mo","Tu","We","Th","Fr","Sa","Su"]; - g.setFont("Vector",10); - g.setColor(fontColor); - g.setFontAlign(-1,-1,0); - for(var i = 0; i < days.length;i++){ - g.drawString(days[i],i*cellSize+5,calStart -11); - if(i!=0){ - g.drawLine(i*cellSize,calStart,i*cellSize,g.getHeight()); - } - } - var cellHeight = (g.getHeight() -calStart ) / 3; - for(var i = 0;i < 3;i++){ - var starty = calStart + i * cellHeight; - g.drawLine(0,starty,g.getWidth(),starty); - } - - g.setFont("Vector",15); - - var dayOfWeek = d.getDay(); - var dayRem = d.getDay() - 1; - if(dayRem <0){ - dayRem = 0; - } - - var start = new Date(); - start.setDate(start.getDate()-(7+dayRem)); - g.setFontAlign(0,-1,0); - for (var y = 0;y < 3; y++){ - for(var x = 0;x < 7; x++){ - if(start.getDate() === d.getDate()){ - g.setColor(accentColor); - }else{ - g.setColor(fontColor); } - g.drawString(start.getDate(),x*cellSize +(cellSize / 2) + 2,calStart+(cellHeight*y) + 5); - start.setDate(start.getDate()+1); + else + this.ABR_DAY=["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + } + + /** + * @returns {Object} current settings object + */ + settings(){ + return this._settings; + } + + + /* + * Run forest run + **/ + draw(){ + this.drawTime(); + + if (this.TZOffset===undefined || this.TZOffset!==d.getTimezoneOffset()) + this.drawDateAndCal(); } + + /** + * draw given or current time from date + * overwatch timezone changes + * schedules itself to update + */ + drawTime(){ + d=this.date ? this.date : new Date(); + const Y=Bangle.appRect.y+this.DATE_FONT_SIZE()+10; + + d=d?d :new Date(); + + g.setFontAlign(0, -1).setFont("Vector", this.TIME_FONT_SIZE()).setColor(g.theme.fg) + .clearRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+this.TIME_FONT_SIZE()-7) + .drawString(("0" + require("locale").time(d, 1)).slice(-5), this.centerX, Y, true); + //.drawRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+this.TIME_FONT_SIZE()-7); //DEV-Option + + setTimeout(this.draw.bind(this), 60000-(d.getSeconds()*1000)-d.getMilliseconds()); + } + + /** + * draws given date and cal + * @param{Date} d provide date or uses today + */ + drawDateAndCal(){ + d=this.date ? this.date : new Date(); + + this.TZOffset=d.getTimezoneOffset(); + this.drawDate(); + this.drawCal(); + + if (this.tOutD) //abort exisiting + clearTimeout(this.tOutD); + this.tOutD=setTimeout(this.drawDateAndCal.bind(this), 86400000-(d.getHours()*24*60*1000)-(d.getMinutes()*60*1000)-d.getSeconds()-d.getMilliseconds()); + } + + /** + * draws given date as defiend in settings + */ + drawDate(){ + d=this.date ? this.date : new Date(); + + const FONT_SIZE=20; + const Y=Bangle.appRect.y; + var render=false; + var dateStr = ""; + if (this.settings().shwDate>0) { //skip if exactly -none + const dateSttngs = ["","l","M","m.Y #W"]; + for (let c of dateSttngs[this.settings().shwDate]) { //add part as configured + switch (c){ + case "l":{ //locale + render=true; + dateStr+=require("locale").date(d,1); + break; + } + case "m":{ //month e.g. Jan. + render=true; + dateStr+=require("locale").month(d,1); + break; + } + case "M":{ //month e.g. January + render=true; + dateStr+=require("locale").month(d,0); + break; + } + case "y":{ //year e.g. 22 + render=true; + dateStr+=d.getFullYear().slice(-2); + break; + } + case "Y":{ //year e.g. 2022 + render=true; + dateStr+=d.getFullYear(); + break; + } + case "w":{ //week e.g. #2 + dateStr+=(this.ISO8601calWeek(d)); + break; + } + case "W":{ //week e.g. #02 + dateStr+=("0"+this.ISO8601calWeek(d)).slice(-2); + break; + } + default: //append c + dateStr+=c; + render=dateStr.length>0; + break; //noop + } + } + } + if (render){ + g.setFont("Vector", FONT_SIZE).setColor(g.theme.fg).setFontAlign(0, -1).clearRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+FONT_SIZE-3).drawString(dateStr,this.centerX,Y); + } + //g.drawRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+FONT_SIZE-3); //DEV-Option + } + + /** + * draws calender week view (-1,0,1) for given date + */ + drawCal(){ + d=this.date ? this.date : new Date(); + + const DAY_NAME_FONT_SIZE=10; + const CAL_Y=Bangle.appRect.y+this.DATE_FONT_SIZE()+10+this.TIME_FONT_SIZE()+3; + const CAL_AREA_H=Bangle.appRect.h-CAL_Y+24; //+24: top widegtes only + const CELL_W=Bangle.appRect.w/7; //cell width + const CELL_H=(CAL_AREA_H-DAY_NAME_FONT_SIZE)/3; //cell heigth + const DAY_NUM_FONT_SIZE=Math.min(CELL_H-1,15); //size down, max 15 + + g.setFont("Vector", DAY_NAME_FONT_SIZE).setColor(g.theme.fg).setFontAlign(-1, -1).clearRect(Bangle.appRect.x, CAL_Y, Bangle.appRect.x2, CAL_Y+CAL_AREA_H); + + //draw grid & Headline + const dNames = this.ABR_DAY.map((a) => a.length<=2 ? a : a.substr(0, 2)); //force shrt 2 + for(var dNo=0; dNo=0 ? (dNo+this.settings().wdStrt)%7 : (dNo+d.getDay()+4)%7; + const dName=dNames[dIdx]; + if(dNo>0) + g.drawLine(dNo*CELL_W, CAL_Y, dNo*CELL_W, CAL_Y+CAL_AREA_H-1); + + if (dIdx==0) g.setColor(this.nrgb[this.settings().suClr]); //sunday maybe colorize txt + g.drawString(dName, dNo*CELL_W+(CELL_W-g.stringWidth(dName))/2+2, CAL_Y+1).setColor(g.theme.fg); + } + + var nextY=CAL_Y+DAY_NAME_FONT_SIZE; + + for(i=0; i<3; i++){ + const y=nextY+i*CELL_H; + g.drawLine(Bangle.appRect.x, y, Bangle.appRect.x2, y); + } + + g.setFont("Vector", DAY_NUM_FONT_SIZE); + + //write days + const tdyDate=d.getDate(); + const days=this.settings().wdStrt>=0 ? 7+((7+d.getDay()-this.settings().wdStrt)%7) : 10;//start day (week before=7 days + days in this week realtive to week start) or fixed 7+3 days + var rD=new Date(d.getTime()); + rD.setDate(rD.getDate()-days); + var rDate=rD.getDate(); + for(var y=0; y<3; y++){ + for(var x=0; xSu, 1->Mo, ... //Issue #1154: weekstart So/Mo, -1 for use today + + tdyNumClr:3, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E + tdyMrkr:0, //0:none, 1:circle, 2:rectangle, 3:filled + tdyMrkClr:2, //1:red=#E00, 2:green=#0E0, 3:blue=#00E + tdyMrkPxl:3, //px + + suClr:1, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E + //phColor:"#E00", //public holiday + + calBrdr:false + }; + validSttngs = require("Storage").readJSON("timecal.validSttngs.json", 1) || {}; + for (const k in validSttngs) if (!DEFAULTS.hasOwnProperty(k)) delete this.validSttngs[k]; //remove invalid settings + for (const k in DEFAULTS) if(!validSttngs.hasOwnProperty(k)) validSttngs[k] = validSttngs[k]; //assign missing defaults + + var changedSttngs = Object.assign({}, validSttngs); + + var saveExitSettings = () => { + require('Storage').writeJSON(FILE, changedSttngs); + exit(); + }; + + var cancelExitSettings = () => { + require('Storage').writeJSON(FILE, validSttngs); + exit(); + }; + + var showMainMenu = () => { + E.showMenu({ + "": { + "title": "TimeCal "+ /*LANG*/"settings" + }, + /*LANG*/"< Save": () => saveExitSettings(), + /*LANG*/"Show date": { + value: validSttngs.shwDate, + min: 0, max: 3, + format: v => [/*LANG*/"none", /*LANG*/"locale", /*LANG*/"M", /*LANG*/"m.Y #W"][v], + onchange: v => validSttngs.shwDate = v + }, + /*LANG*/"Start wday": { + value: validSttngs.wdStrt, + min: -1, max: 6, + format: v => v>=0 ? ABR_DAY[v] : /*LANG*/"today", + onchange: v => validSttngs.wdStrt = v + }, + /*LANG*/"Su color": { + value: validSttngs.suClr, + min: 0, max: 3, + format: v => [/*LANG*/"none", /*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v], + onchange: v => validSttngs.suClr = v + }, + /*LANG*/"Border": { + value: validSttngs.calBrdr, + format: v => v ? /*LANG*/"show" : /*LANG*/"none", + onchange: v => validSttngs.calBrdr = v + }, + /*LANG*/"Today settings": () => { + showTodayMenu(); + }, + /*LANG*/"< Cancel": () => cancelExitSettings() + }); + }; + + var showTodayMenu = () => { + E.showMenu({ + "": { + "title": /*LANG*/"Today settings" + }, + "< Back": () => showMainMenu(), + /*LANG*/"Color": { + value: validSttngs.tdyNumClr, + min: 0, max: 3, + format: v => [/*LANG*/"none", /*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v], + onchange: v => validSttngs.tdyNumClr = v + }, + /*LANG*/"Marker": { + value: validSttngs.tdyMrkr, + min: 0, max: 3, + format: v => [/*LANG*/"none", /*LANG*/"circle", /*LANG*/"rectangle", /*LANG*/"filled"][v], + onchange: v => validSttngs.tdyMrkr = v + }, + /*LANG*/"Mrk.Color": { + value: validSttngs.tdyMrkClr, + min: 0, max: 2, + format: v => [/*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v], + onchange: v => validSttngs.tdyMrkClr = v + }, + /*LANG*/"Mrk.Size": { + value: validSttngs.tdyMrkPxl, + min: 1, max: 10, + format: v => v+"px", + onchange: v => validSttngs.tdyMrkPxl = v + }, + /*LANG*/"< Cancel": () => cancelExitSettings() + }); + }; + + showMainMenu(); +});