diff --git a/apps/imageclock/ChangeLog b/apps/imageclock/ChangeLog index 17bf5db55..b67a4bac0 100644 --- a/apps/imageclock/ChangeLog +++ b/apps/imageclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App 0.02: Allow drawing polys +0.03: Allow partly importing Amazfit decompiler formatted watchfaces diff --git a/apps/imageclock/app.js b/apps/imageclock/app.js index 53f4f2573..d5dd28435 100644 --- a/apps/imageclock/app.js +++ b/apps/imageclock/app.js @@ -30,16 +30,23 @@ function prepareImg(resource){ } function getByPath(object, path, lastElem){ + //print("getByPath", path,lastElem); var current = object; - for (var c of path){ - if (!current[c]) return undefined; - current = current[c]; + if (path.length) { + for (var c of path){ + if (!current[c]) return undefined; + current = current[c]; + } } if (lastElem!==undefined){ if (!current["" + lastElem]) return undefined; + //print("Found by lastElem", lastElem); current = current["" + lastElem]; } - if (typeof current == "function") return undefined; + if (typeof current == "function"){ + //print("current was function"); + return undefined; + } return current; } @@ -49,6 +56,14 @@ function splitNumberToDigits(num){ function drawNumber(element, offset){ var number = numbers[element.Value](); + var spacing = element.Spacing ? element.Spacing : 0; + var unit = element.Unit; + + var imageIndexMinus = element.ImageIndexMinus; + var imageIndexUnit = element.ImageIndexUnit; + var numberOfDigits = element.Digits; + + //print("drawNumber: ", number, element, offset); if (number) number = number.toFixed(0); @@ -59,8 +74,9 @@ function drawNumber(element, offset){ var isNegative; var digits; if (number == undefined){ - isNegative = false; - digits = ["minus","minus","minus"]; + isNegative = true; + digits = []; + numberOfDigits = 0; } else { isNegative = number < 0; if (isNegative) number *= -1; @@ -68,27 +84,69 @@ function drawNumber(element, offset){ } //print("digits: ", digits); - var numberOfDigits = element.Digits; if (!numberOfDigits) numberOfDigits = digits.length; var firstDigitX = element.X; var firstDigitY = element.Y; - var firstImage = getByPath(resources, element.ImagePath, 0); + var imageIndex = element.ImageIndex ? element.ImageIndex : 0; - if (element.Alignment == "BottomRight"){ - var digitWidth = firstImage.width + element.Spacing; - var numberWidth = (numberOfDigits * digitWidth); - if (isNegative){ - numberWidth += firstImage.width + element.Spacing; - } - //print("Number width: ", numberWidth, firstImage.width, element.Spacing); + var firstImage; + if (imageIndex){ + firstImage = getByPath(resources, [], "" + (0 + imageIndex)); + } else { + firstImage = getByPath(resources, element.ImagePath, 0); + } + + var minusImage; + if (imageIndexMinus){ + minusImage = getByPath(resources, [], "" + (0 + imageIndexMinus)); + } else { + minusImage = getByPath(resources, element.ImagePath, "minus"); + } + + var unitImage; + //print("Get image for unit", imageIndexUnit); + if (imageIndexUnit !== undefined){ + unitImage = getByPath(resources, [], "" + (0 + imageIndexUnit)); + //print("Unit image is", unitImage); + } else if (element.Unit){ + unitImage = getByPath(resources, element.ImagePath, getMultistate(element.Unit, "unknown")); + } + + var numberWidth = (numberOfDigits * firstImage.width) + (Math.max((numberOfDigits - 1),0) * spacing); + if (isNegative && minusImage){ + //print("Adding to width", minusImage); + numberWidth += minusImage.width + spacing; + } + if (unitImage){ + //print("Adding to width", unitImage); + numberWidth += unitImage.width + spacing; + } + //print("numberWidth:", numberWidth); + + if (element.Alignment == "Center") { + firstDigitX = Math.round(element.X - (numberWidth/2)) + 1; + firstDigitY = Math.round(element.Y - (firstImage.height/2)) + 1; + } else if (element.Alignment == "BottomRight"){ firstDigitX = element.X - numberWidth + 1; firstDigitY = element.Y - firstImage.height + 1; - //print("Calculated start " + firstDigitX + "," + firstDigitY + " From:" + element.BottomRightX + " " + firstImage.width + " " + element.Spacing); + } else if (element.Alignment == "TopRight") { + firstDigitX = element.X - numberWidth + 1; + firstDigitY = element.Y; + } else if (element.Alignment == "BottomLeft") { + firstDigitX = element.X; + firstDigitY = element.Y - firstImage.height + 1; } + var currentX = firstDigitX; - if (isNegative){ + if (isNegative && minusImage){ + //print("Draw minus at", currentX); + if (imageIndexMinus){ + drawElement({X:currentX,Y:firstDigitY}, numberOffset, element.ImagePath, "" + (0 + imageIndexMinus)); + } else { + drawElement({X:currentX,Y:firstDigitY}, numberOffset, element.ImagePath, "minus"); + } drawElement({X:currentX,Y:firstDigitY}, numberOffset, element.ImagePath, "minus"); - currentX += firstImage.width + element.Spacing; + currentX += minusImage.width + spacing; } for (var d = 0; d < numberOfDigits; d++){ var currentDigit; @@ -99,8 +157,14 @@ function drawNumber(element, offset){ currentDigit = 0; } //print("Digit " + currentDigit + " " + currentX); - drawElement({X:currentX,Y:firstDigitY}, numberOffset, element.ImagePath, currentDigit); - currentX += firstImage.width + element.Spacing; + drawElement({X:currentX,Y:firstDigitY}, numberOffset, element.ImagePath, currentDigit + imageIndex); + currentX += firstImage.width + spacing; + } + if (imageIndexUnit){ + //print("Draw unit at", currentX); + drawElement({X:currentX,Y:firstDigitY}, numberOffset, element.ImagePath, "" + (0 + imageIndexUnit)); + } else if (element.Unit){ + drawElement({X:currentX,Y:firstDigitY}, numberOffset, element.ImagePath, getMultistate(element.Unit,"unknown")); } element.lastDrawnValue = number; } @@ -113,13 +177,15 @@ function setColors(properties){ function drawElement(pos, offset, path, lastElem){ //print("drawElement ",pos, offset, path, lastElem); + //print("drawElement offset", offset, pos.X, pos.Y); var resource = getByPath(resources, path, lastElem); + //print("Got resource", resource); if (resource){ - //print("resource ",pos, offset, path, lastElem); + //print("resource ", resource,pos, offset, path, lastElem); var image = prepareImg(resource); if (image){ - offset = updateColors(pos, offset); - setColors(offset); + var imageOffset = updateColors(pos, offset); + setColors(imageOffset); //print("drawImage from drawElement", image, pos, offset); var options={}; if (pos.RotationValue){ @@ -131,7 +197,7 @@ function drawElement(pos, offset, path, lastElem){ } //print("options", options); //print("Memory before drawing", process.memory(false)); - g.drawImage(image ,offset.X + pos.X,offset.Y + pos.Y, options); + g.drawImage(image ,(imageOffset.X ? imageOffset.X : 0) + (pos.X ? pos.X : 0),(imageOffset.Y ? imageOffset.Y :0) + (pos.Y ? pos.Y : 0), options); } else { //print("Could not create image from", resource); } @@ -153,12 +219,30 @@ function checkRedraw(element, newValue){ } } +function getValue(value, defaultValue){ + if (typeof value == "string"){ + return numbers[value](); + } + if (value == undefined) return defaultValue; + return value; +} + +function getMultistate(name, defaultValue){ + if (typeof name == "string"){ + return multistates[name](); + } else { + if (name == undefined) return defaultValue; + } + return undefined; +} + function drawScale(scale, offset){ //print("drawScale", scale, offset); var segments = scale.Segments; var value = numbers[scale.Value](); - var maxValue = scale.MaxValue ? scale.MaxValue : 1; - var minValue = scale.MinValue ? scale.MinValue : 0; + var maxValue = getValue(scale.MaxValue, 1); + var minValue = getValue(scale.MinValue, 0); + var imageIndex = scale.ImageIndex !== undefined ? scale.ImageIndex : 0; value = value/maxValue; value -= minValue; @@ -171,7 +255,7 @@ function drawScale(scale, offset){ if (checkRedraw(scale, segmentsToDraw)){ for (var i = 0; i < segmentsToDraw; i++){ - drawElement(segments[i], scaleOffset, scale.ImagePath, i); + drawElement(segments[i], scaleOffset, scale.ImagePath, imageIndex + i); } scale.lastDrawnValue = segmentsToDraw; } @@ -182,20 +266,22 @@ function drawDigit(element, offset, digit){ } function drawImage(image, offset, name){ + var imageOffset = updateColors(image, offset); if (image.ImagePath) { //print("drawImage", image, offset, name); - offset = updateColors(image, offset); if (image.Value && image.Steps){ - var steps = Math.floor(scaledown(numbers[image.Value](), image.MinValue, image.MaxValue) * (image.Steps - 1)); + var steps = Math.floor(scaledown(getValue(image.Value), getValue(image.MinValue, 0), getValue(image.MaxValue, 1)) * (image.Steps - 1)); //print("Step", steps, "of", image.Steps); - drawElement(image, offset, image.ImagePath, "" + steps); + drawElement(image, imageOffset, image.ImagePath, "" + steps); + } else if (image.ImageIndex !== undefined) { + drawElement(image, imageOffset, image.ImagePath, image.ImageIndex); } else { - drawElement(image, offset, image.ImagePath, name ? "" + name: undefined); + drawElement(image, imageOffset, image.ImagePath, name ? "" + name: undefined); } } else if (image.ImageFile) { var file = require("Storage").readJSON(image.ImageFile); - setColors(offset); - g.drawImage(prepareImg(file),image.X + offset.X, image.Y + offsetY); + setColors(imageOffset); + g.drawImage(prepareImg(file),image.X + imageOffset.X, image.Y + imageOffset.Y); } } @@ -257,32 +343,35 @@ function getWeatherTemperature(){ } function updateOffset(element, offset){ - var newOffset = { X: offset.X, Y: offset.Y }; - if (element.X) newOffset.X += element.X; - if (element.Y) newOffset.Y += element.Y; + var newOffset = { X: offset.X ? offset.X : 0, Y: offset.Y ? offset.Y : 0 }; + if (element && element.X) newOffset.X += element.X; + if (element && element.Y) newOffset.Y += element.Y; newOffset = updateColors(element, newOffset); //print("Updated offset from ", offset, "to", newOffset); return newOffset; } function updateColors(element, offset){ - var newOffset = { X: offset.X, Y: offset.Y }; - newOffset.fg = element.ForegroundColor ? element.ForegroundColor: offset.fg; - newOffset.bg = element.BackgroundColor ? element.BackgroundColor: offset.bg; + var newOffset = { X: offset.X ? offset.X : 0, Y: offset.Y ? offset.Y : 0 }; + if (element){ + newOffset.fg = element.ForegroundColor ? element.ForegroundColor: offset.fg; + newOffset.bg = element.BackgroundColor ? element.BackgroundColor: offset.bg; + } //print("Updated offset from ", offset, "to", newOffset); return newOffset; } function scaledown(value, min, max){ - print("scaledown", value, min, max); - var scaled = E.clip(value,min?min:0,max?max:1); - scaled -= min?min:0; - scaled /= max?max:1; + //print("scaledown", value, min, max); + var scaled = E.clip(value,getValue(min,0),getValue(max,1)); + scaled -= getValue(min,0); + scaled /= getValue(max,1); return scaled; } function rotate(center, coords, rotation) { var value = scaledown(numbers[rotation.RotationValue](), rotation.MinRotationValue, rotation.MaxRotationValue); + value -= rotation.RotationOffset ? rotation.RotationOffset : 0; value *= 360; value -= 180; @@ -298,7 +387,9 @@ function rotate(center, coords, rotation) { function drawPoly(element, offset){ var vertices = []; - var primitiveOffset = offset; + var primitiveOffset = offset.clone(); + if (element.X) primitiveOffset.X += element.X; + if (element.Y) primitiveOffset.Y += element.Y; for (var c of element.Vertices){ if (element.RotationValue){ @@ -313,7 +404,7 @@ function drawPoly(element, offset){ if (element.ForegroundColor) g.setColor(element.ForegroundColor); - if (element.Filled ){ + if (element.Filled){ g.fillPoly(vertices,true); } @@ -337,6 +428,8 @@ numbers.Second = () => { return new Date().getSeconds(); }; numbers.SecondAnalog = () => { var date = new Date(); return date.getSeconds() + (date.getMilliseconds()/999); }; numbers.SecondTens = () => { return Math.floor(new Date().getSeconds()/10); }; numbers.SecondOnes = () => { return Math.floor(new Date().getSeconds()%10); }; +numbers.WeekDay = () => { return new Date().getDay(); }; +numbers.WeekDayMondayFirst = () => { var day = (new Date().getDay() - 1); if (day < 0) day = 7 + day; return day; }; numbers.Day = () => { return new Date().getDate(); }; numbers.DayTens = () => { return Math.floor(new Date().getDate()/10); }; numbers.DayOnes = () => { return Math.floor(new Date().getDate()%10); }; @@ -345,13 +438,14 @@ numbers.MonthTens = () => { return Math.floor((new Date().getMonth() + 1)/10); } numbers.MonthOnes = () => { return Math.floor((new Date().getMonth() + 1)%10); }; numbers.Pulse = () => { return pulse; }; numbers.Steps = () => { return Bangle.getHealthStatus ? Bangle.getHealthStatus("day").steps : undefined; }; +numbers.StepsGoal = () => { return stepsgoal; }; numbers.Temperature = () => { return temp; }; numbers.Pressure = () => { return press; }; numbers.Altitude = () => { return alt; }; numbers.BatteryPercentage = E.getBattery; numbers.BatteryVoltage = NRF.getBattery; numbers.WeatherCode = getWeatherCode; -numbers.WeatherTemperature = () => { var t = getWeatherTemperature().value; return t ? t : undefined; }; +numbers.WeatherTemperature = () => { return getWeatherTemperature().value; }; var multistates = {}; multistates.Lock = () => { return Bangle.isLocked() ? "on" : "off"; }; @@ -365,7 +459,9 @@ multistates.HRM = () => { return Bangle.isHRMOn ? "on" : "off"; }; multistates.Barometer = () => { return Bangle.isBarometerOn() ? "on" : "off"; }; multistates.Compass = () => { return Bangle.isCompassOn() ? "on" : "off"; }; multistates.GPS = () => { return Bangle.isGPSOn() ? "on" : "off"; }; +multistates.WeatherTemperatureNegative = () => { return getWeatherTemperature().value ? getWeatherTemperature().value : 0 < 0; }; multistates.WeatherTemperatureUnit = () => { return getWeatherTemperature().unit; }; +multistates.StepsGoal = () => { return (numbers.Steps() >= stepsgoal) ? "on": "off"; }; function drawMultiState(element, offset){ //print("drawMultiState", element, offset); @@ -381,6 +477,9 @@ function draw(element, offset){ var initial = !element; if (initial){ element = face; + if (!offset) offset ={}; + if (!offset.X) offset.X = 0; + if (!offset.Y) offset.Y = 0; g.clear(); } @@ -461,7 +560,7 @@ function initialDraw(){ function handleHrm(e){ if (e.confidence > 70){ pulse = e.bpm; - if (!redrawEvents || redrawEvents.includes("HRM")){ + if (!redrawEvents || redrawEvents.includes("HRM") && !Bangle.isLocked()){ //print("Redrawing on HRM"); initialDraw(); } @@ -472,14 +571,14 @@ function handlePressure(e){ alt = e.altitude; temp = e.temperature; press = e.pressure; - if (!redrawEvents || redrawEvents.includes("pressure")){ + if (!redrawEvents || redrawEvents.includes("pressure") && !Bangle.isLocked()){ //print("Redrawing on pressure"); initialDraw(); } } function handleCharging(e){ - if (!redrawEvents || redrawEvents.includes("charging")){ + if (!redrawEvents || redrawEvents.includes("charging") && !Bangle.isLocked()){ //print("Redrawing on charging"); initialDraw(); } @@ -512,6 +611,8 @@ var defaultRedraw = getByPath(face, ["Properties","Redraw","Default"]) || "Alway var redrawEvents = getByPath(face, ["Properties","Redraw","Events"]); var events = getByPath(face, ["Properties","Events"]); +var stepsgoal = 2000; + //print("events", events); //print("redrawEvents", redrawEvents); diff --git a/apps/imageclock/custom.html b/apps/imageclock/custom.html index 99b89b0db..fe531ea8a 100644 --- a/apps/imageclock/custom.html +++ b/apps/imageclock/custom.html @@ -11,12 +11,22 @@
+ Select format: + + + + +
Select watchface folder:
or
Select watchface zip file:
Download Demo Watchface here: digitalretro.zip @@ -30,6 +40,468 @@ var faceJson; var handledFiles = 0; var expectedFiles = 0; + var rootZip = new JSZip(); + var resourcesZip = rootZip.folder("resources"); + + function isNativeFormat(){ + return document.getElementById("useNative").checked; + } + + function convertAmazfitTime(time){ + var result = {}; + if (time.Hours){ + result.Hours = { + Tens: convertAmazfitNumber(time.Hours.Tens, "HourTens", 0, 10), + Ones: convertAmazfitNumber(time.Hours.Ones, "HourOnes", 0, 10) + } + result.Hours.Tens.Number.Alignment = "TopLeft"; + result.Hours.Tens.Number.Spacing = 0; + result.Hours.Ones.Number.Alignment = "TopLeft"; + result.Hours.Ones.Number.Spacing = 0; + } + if (time.Minutes){ + result.Minutes = { + Tens: convertAmazfitNumber(time.Minutes.Tens, "MinuteTens", 0, 10), + Ones: convertAmazfitNumber(time.Minutes.Ones, "MinuteOnes", 0, 10) + } + result.Minutes.Tens.Number.Alignment = "TopLeft"; + result.Minutes.Tens.Number.Spacing = 0; + result.Minutes.Ones.Number.Alignment = "TopLeft"; + result.Minutes.Ones.Number.Spacing = 0; + } + return result; + } + + function convertAmazfitDate(date){ + var result = {}; + if (date.MonthAndDay.Separate.Day) result.Day = convertAmazfitNumber(date.MonthAndDay.Separate.Day, "Day"); + if (date.MonthAndDay.Separate.Month) result.Month = convertAmazfitNumber(date.MonthAndDay.Separate.Month, "Month"); + if (date.WeekDay){ + result.WeekDay = convertAmazfitNumber(date.WeekDay, "WeekDayMondayFirst"); + } + return result; + } + + var filesToMove={}; + + var zipChangePromise = Promise.resolve(); + + function performFileChanges(){ + var promise = Promise.resolve(); + //rename all files to just numbers without leading zeroes + for (var c in resultJson){ + console.log("Renaming", c, resultJson[c]); + var tmp = resultJson[c]; + delete resultJson[c]; + resultJson[Number(c)] = tmp; + + async function modZip(c){ + console.log("Async modification of ", c) + var fileRegex = new RegExp(c + ".*"); + var fileObject = resourcesZip.file(fileRegex)[0]; + var fileData = await fileObject.async("uint8array"); + console.log("Filedata is", fileData); + var extension = resourcesZip.file(fileRegex)[0].name.match(/\.[^.]*$/); + var newName = Number(c) + extension; + + console.log("Renaming to", newName); + resourcesZip.remove(c + extension); + resourcesZip.file(newName, fileData); + } + promise = promise.then(modZip(c)); + + + } + + + console.log("File moves:", filesToMove); + + for (var c in filesToMove){ + var tmp = resultJson[c]; + console.log("Handle filemove", c, filesToMove[c], tmp); + + var element = resultJson; + var path = filesToMove[c]; + + + async function modZip(c){ + console.log("Async modification of ", c) + var fileRegex = new RegExp(c + ".*"); + var fileObject = resourcesZip.file(fileRegex)[0]; + var fileData = await fileObject.async("uint8array"); + console.log("Filedata is", fileData); + var extension = resourcesZip.file(fileRegex)[0].name.match(/\.[^.]*$/); + var newName = Number(c) + extension; + + console.log("Copying to", newName); + resourcesZip.file(filesToMove[c].join("/") + extension, fileData); + } + promise = promise.then(modZip(c)); + + + for (var i = 0; i< path.length; i++){ + if (!element[path[i]]) element[path[i]] = {}; + if (i == path.length - 1){ + element[path[i]] = tmp; + } else { + element = element[path[i]]; + } + } + + } + promise.then(()=>{ + document.getElementById('btnUpload').disabled = true; + }); + console.log("After moves", resultJson); + return promise; + }; + + function convertAmazfitMultistate(multistate, value, minValue, maxValue){ + var result = { + MultiState: { + X: multistate.Coordinates.X, + Y: multistate.Coordinates.Y, + Value: value, + ImagePath: [value] + } + }; + if (minValue !== undefined) result.MultiState.MinValue = minValue; + if (maxValue !== undefined) result.MultiState.MaxValue = maxValue; + if (multistate.ImageIndexOn) filesToMove[multistate.ImageIndexOn] = ["status", value, "vibrate"]; + if (multistate.ImageIndexOff) filesToMove[multistate.ImageIndexOff] = ["status", value, "off"]; + return result; + } + + function convertAmazfitStatus(status){ + var result = {}; + + if (status.Alarm) result.Alarm = convertAmazfitMultistate(status.Alarm,"Alarm"); + if (status.Bluetooth) result.Bluetooth = convertAmazfitMultistate(status.Bluetooth,"Bluetooth"); + if (status.DoNotDisturb) result.DoNotDisturb = convertAmazfitMultistate(status.DoNotDisturb,"Notifications"); + if (status.Lock) result.Lock = convertAmazfitMultistate(status.Lock,"Lock"); + + return result; + } + + function convertAmazfitNumber(element, value, minValue, maxValue){ + var number = {}; + var result = { + Number: number + }; + if (element.Alignment == "BottomRight"){ + number.X = element.BottomRightX; + number.Y = element.BottomRightY; + } else if (element.Alignment == "TopLeft"){ + number.X = element.TopLeftX; + number.Y = element.TopLeftY; + } else if (element.Alignment == "TopRight"){ + number.X = element.BottomRightX; + number.Y = element.TopLeftY; + } else if (element.Alignment == "BottomLeft"){ + number.X = element.TopLeftX; + number.Y = element.BottomLeftY; + } else if (element.Alignment == "Center"){ + number.X = (element.TopLeftX + (element.BottomRightX - element.TopLeftX)/2), + number.Y = (element.TopLeftY + (element.BottomRightY - element.TopLeftY)/2) + } else { + number.X = element.X, + number.Y = element.Y + } + number.Alignment = element.Alignment; + number.Spacing = element.Spacing; + number.ImageIndex = element.ImageIndex; + number.ImagePath = []; + number.Value = value; + if (minValue !== undefined) number.MaxValue = maxValue; + if (maxValue !== undefined) number.MinValue = minValue; + return result; + } + + function moveWeatherIcons(icon){ + filesToMove[icon.ImageIndex + 0] = ["weather", "fallback"]; + + // Light clouds + filesToMove[icon.ImageIndex + 1] = ["weather", 801]; + // Cloudy, possible rain + filesToMove[icon.ImageIndex + 2] = ["weather", 500]; + // Cloudy, possible snow + filesToMove[icon.ImageIndex + 3] = ["weather", 600]; + // Clear + filesToMove[icon.ImageIndex + 4] = ["weather", 800]; + // Clouds + filesToMove[icon.ImageIndex + 5] = ["weather", 803]; + // Light Rain + filesToMove[icon.ImageIndex + 6] = ["weather", 501]; + // Light Snow + filesToMove[icon.ImageIndex + 7] = ["weather", 601]; + // Rain + filesToMove[icon.ImageIndex + 8] = ["weather", 502]; + // Snow + filesToMove[icon.ImageIndex + 9] = ["weather", 602]; + // Heavy Snow + filesToMove[icon.ImageIndex + 10] = ["weather", 621]; + // Heavy Rain + filesToMove[icon.ImageIndex + 11] = ["weather", 503]; + // Sandstorm + filesToMove[icon.ImageIndex + 12] = ["weather", 751]; + // Snow and Rain + filesToMove[icon.ImageIndex + 13] = ["weather", 616]; + // Fog + filesToMove[icon.ImageIndex + 14] = ["weather", 741]; + // Mist + filesToMove[icon.ImageIndex + 15] = ["weather", 701]; + // Shower + filesToMove[icon.ImageIndex + 16] = ["weather", 521]; + // Hail + filesToMove[icon.ImageIndex + 17] = ["weather", 611]; + // Hailstorm + filesToMove[icon.ImageIndex + 18] = ["weather", 613]; + // Heavy Shower + filesToMove[icon.ImageIndex + 19] = ["weather", 522]; + // Dust whirls + filesToMove[icon.ImageIndex + 20] = ["weather", 731]; + // Tornado + filesToMove[icon.ImageIndex + 21] = ["weather", 781]; + // Very heavy shower + filesToMove[icon.ImageIndex + 22] = ["weather", 531]; + } + + function convertAmazfitTemperature(temp){ + var result = {}; + result = convertAmazfitNumber(temp.Number, "WeatherTemperature"); + if (temp.MinusImageIndex){ + result.Number.ImageIndexMinus = temp.MinusImageIndex; + } + if (temp.DegreesImageIndex){ + result.Number.ImageIndexUnit = temp.DegreesImageIndex; + } + return result; + } + + function convertAmazfitWeather(weather){ + var result = {}; + + if (weather.Temperature && weather.Temperature.Current){ + if (!result.Temperature) result.Temperature = {}; + result.Temperature.Current = convertAmazfitTemperature(weather.Temperature.Current); + } + + if (weather.Temperature && weather.Temperature.Today){ + if (!result.Temperature) result.Temperature = {}; + if (weather.Temperature.Today.Separate){ + if (weather.Temperature.Today.Separate.Day){ + result.Temperature.Day = convertAmazfitTemperature(weather.Temperature.Today.Separate.Day); + } + if (weather.Temperature.Today.Separate.Night){ + result.Temperature.Night = convertAmazfitTemperature(weather.Temperature.Today.Separate.Night); + } + } + } + if (weather.Icon){ + result.WeatherIcon = { + CodedImage: { + X: weather.Icon.CustomIcon.X, + Y: weather.Icon.CustomIcon.Y, + Value: "WeatherCode", + ImagePath: ["weather"] + } + } + moveWeatherIcons(weather.Icon.CustomIcon); + } + return result; + } + + function convertAmazfitActivity(activity){ + var result = {}; + + if (activity.Steps){ + result.Steps = convertAmazfitNumber(activity.Steps, "Steps"); + } + if (activity.Pulse){ + result.Pulse = convertAmazfitNumber(activity.Pulse, "Pulse"); + } + return result; + } + + function convertAmazfitScale(scale, value, minValue, maxValue){ + var result = {}; + result.Scale = { + ImageIndex: scale.StartImageIndex, + ImagePath: [], + Value: value, + MaxValue: maxValue, + MinValue: minValue + }; + result.Scale.Segments = []; + for (var c of scale.Segments){ + result.Scale.Segments.push({ + X: c.X, + Y: c.Y + }); + } + + return result; + } + + function convertAmazfitStepsProgress(steps){ + var result = {}; + if (steps.GoalImage){ + result.Goal = { + MultiState: { + X: steps.GoalImage.X, + Y: steps.GoalImage.Y, + Value: "StepsGoal", + ImagePath: ["StepsGoal"] + } + } + filesToMove[steps.GoalImage.ImageIndex] = ["StepsGoal", "on"]; + } + if (steps.Linear){ + result.Scale = convertAmazfitScale(steps.Linear, "Steps", 0, "StepsGoal").Scale; + } + return result; + } + + function convertAmazfitBattery(battery){ + var result = {}; + if (battery.Scale){ + result.Scale = convertAmazfitScale(battery.Scale, "BatteryPercentage", 0, 100).Scale; + } + if (battery.Text){ + result.Number = convertAmazfitNumber(battery.Text, "BatteryPercentage", 0, 100).Number; + } + return result; + } + + function convertAmazfitImage(image){ + var result = { + Image: { + X: image.X, + Y: image.Y, + ImagePath: [], + ImageIndex: image.ImageIndex + } + }; + return result; + } + + function convertAmazfitColor(color){ + return "#" + color.substring(2); + } + + function convertAmazfitHand(hand, rotationValue, minRotationValue, maxRotationValue){ + var result = { + Filled: !hand.OnlyBorder, + X: hand.Center.X, + Y: hand.Center.Y, + ForegroundColor: convertAmazfitColor(hand.Color), + BackgroundColor: convertAmazfitColor(hand.Color), + RotationValue: rotationValue, + RotationOffset: 0.25, + MaxRotationValue: maxRotationValue, + MinRotationValue: minRotationValue + }; + + result.Vertices = [] + for (var c of hand.Shape){ + result.Vertices.push(c); + } + return { Poly: result }; + } + + function convertAmazfitAnalog(analog){ + var result = { + }; + + if (analog.Hours){ + result.Hours = {}; + result.Hours.Hand = convertAmazfitHand(analog.Hours, "Hour12Analog", 0, 12); + if (analog.Hours.CenterImage){ + result.Hours.Center = convertAmazfitImage(analog.Hours.CenterImage); + } + } + if (analog.Minutes){ + result.Minutes = {}; + result.Minutes.Hand = convertAmazfitHand(analog.Minutes, "Minute", 0, 60); + if (analog.Minutes.CenterImage){ + result.Minutes.Center = convertAmazfitImage(analog.Minutes.CenterImage); + } + } + if (analog.Seconds){ + result.Seconds = {}; + result.Seconds = convertAmazfitHand(analog.Seconds, "Second", 0, 60); + if (!faceJson.Properties) faceJson.Properties = {}; + if (!faceJson.Properties.Redraw) faceJson.Properties.Redraw = {}; + if (!faceJson.Properties.Redraw.Unlocked) faceJson.Properties.Redraw.Unlocked = 1000; + if (!faceJson.Properties.Redraw.Unlocked) faceJson.Properties.Redraw.Locked = 15000; + if (analog.Seconds.CenterImage){ + result.Seconds.Center = convertAmazfitImage(analog.Seconds.CenterImage); + } + } + return result; + } + + function restructureAmazfitFormat(dataString){ + console.log("Amazfit data:", dataString); + + + var json = JSON.parse(dataString); + faceJson = json; + + if (!json.Properties) faceJson.Properties = {}; + if (!json.Properties.Redraw) faceJson.Properties.Redraw = {}; + if (!json.Properties.Redraw.Unlocked) faceJson.Properties.Redraw.Unlocked = 10000; + if (!json.Properties.Redraw.Unlocked) faceJson.Properties.Redraw.Locked = 60000; + + var result = {}; + + if (json.Background){ + result.Background = json.Background; + result.Background.Image.ImagePath = []; + delete result.Background.Preview; + } + if (json.Time){ + result.Time = convertAmazfitTime(json.Time); + } + + if (json.Date){ + result.Date = convertAmazfitDate(json.Date); + } + + if (json.Status){ + result.Status = convertAmazfitStatus(json.Status); + } + + if (json.Weather){ + result.Weather = convertAmazfitWeather(json.Weather); + } + + if (json.Activity){ + result.Activity = convertAmazfitActivity(json.Activity); + } + + if (json.StepsProgress){ + result.StepsProgress = convertAmazfitStepsProgress(json.StepsProgress); + } + + if (json.Battery){ + result.Battery = convertAmazfitBattery(json.Battery); + } + if (json.AnalogDialFace){ + result.Analog = convertAmazfitAnalog(json.AnalogDialFace); + } + console.log("Converted to native:", result); + return result; + + } + + function parseFaceJson(jsonString){ + if (isNativeFormat()){ + return JSON.parse(jsonString); + } else { + return restructureAmazfitFormat(jsonString); + } + } function imageLoaded() { var options = {}; @@ -37,7 +509,7 @@ options.diffusion = infoJson.diffusion ? infoJson.diffusion : "none"; options.compression = infoJson.compression ? infoJson.compression : false; options.alphaToColor = false; - options.transparent = false; + options.transparent = infoJson.transparent ? infoJson.transparent : false; options.inverted = false; options.autoCrop = false; options.brightness = 0; @@ -45,7 +517,6 @@ options.mode = infoJson.color ? infoJson.color : "1bit"; options.output = "jsonobject"; - var faceJson; console.log("Loaded image has path", this.path); var jsonPath = this.path.split("/"); @@ -111,26 +582,45 @@ console.log("Expected:", expectedFiles, " handled:", handledFiles); if (handledFiles == expectedFiles){ - document.getElementById('btnSave').disabled = false; - document.getElementById('btnUpload').disabled = false; + if (!isNativeFormat()) { + performFileChanges().then(()=>{ + rootZip.file("face.json", JSON.stringify(faceJson, null, 2)); + rootZip.file("info.json", JSON.stringify(infoJson, null, 2)); + document.getElementById('btnSave').disabled = false; + document.getElementById('btnSaveFace').disabled = false; + document.getElementById('btnSaveZip').disabled = false; + document.getElementById('btnUpload').disabled = false; + }); + } else { + document.getElementById('btnSave').disabled = false; + document.getElementById('btnSaveFace').disabled = false; + document.getElementById('btnUpload').disabled = false; + } } } function handleWatchFace(infoFile, faceFile, resourceFiles){ - var reader = new FileReader(); - reader.path = infoFile.webkitRelativePath; - reader.onload = function(event) { - infoJson = JSON.parse(reader.result); + if (isNativeFormat()){ + var reader = new FileReader(); + reader.path = infoFile.webkitRelativePath; + reader.onload = function(event) { + infoJson = JSON.parse(reader.result); + + handleFaceJson(faceFile, resourceFiles); + }; + reader.readAsText(infoFile); + } else { + console.log("Handling amazfit watch face"); handleFaceJson(faceFile, resourceFiles); - }; - reader.readAsText(infoFile); + } } function handleFaceJson(faceFile, resourceFiles){ var reader = new FileReader(); reader.path = faceFile.webkitRelativePath; reader.onload = function(event) { - faceJson = JSON.parse(reader.result); + faceJson = parseFaceJson(reader.result); + handleResourceFiles(resourceFiles); }; reader.readAsText(faceFile); @@ -141,8 +631,14 @@ console.log('Handle resource file ', current); var reader = new FileReader(); console.log("Handling ", current.name, " with path ", current.webkitRelativePath); - var filteredPath = current.webkitRelativePath.replace(/.*\/resources\//,"") + var filteredPath; + if (isNativeFormat()){ + filteredPath = current.webkitRelativePath.replace(/.*\/resources\//,""); + } else { + filteredPath = current.webkitRelativePath.replace(/.*\//,""); + } reader.path = filteredPath; + resourcesZip.file(filteredPath, current); reader.onload = function(event) { var img = new Image(); img.path = this.path; @@ -158,6 +654,8 @@ expectedFiles = undefined; document.getElementById('btnSave').disabled = true; + document.getElementById('btnSaveZip').disabled = true; + document.getElementById('btnSaveFace').disabled = true; document.getElementById('btnUpload').disabled = true; console.log("File select event", event); @@ -171,18 +669,36 @@ for (var current of event.target.files){ console.log('Handle file ', current); - if (current.webkitRelativePath.split("/")[1].startsWith("resources")){ - console.log('Found resource file', current.name); - resourceFiles.push(current); - expectedFiles = resourceFiles.length; - } else if (current.name == "face.json"){ - console.log('Found face file', current.name); - faceFile = current; - } else if (current.name == "info.json"){ - console.log('Found info file', current.name); - infoFile = current; + if (isNativeFormat()){ + if (current.webkitRelativePath.split("/")[1].startsWith("resources")){ + console.log('Found resource file', current.name); + resourceFiles.push(current); + expectedFiles = resourceFiles.length; + } else if (current.name == "face.json"){ + console.log('Found face file', current.name); + faceFile = current; + } else if (current.name == "info.json"){ + console.log('Found info file', current.name); + infoFile = current; + } else { + console.log('Found unsupported file', current.name); + } } else { - console.log('Found unsupported file', current.name); + infoJson = { + "color": "3bit", + "compression": true, + "transparent": true + }; + if (current.name.includes(".png")){ + console.log('Found resource file', current.name); + resourceFiles.push(current); + expectedFiles = resourceFiles.length; + } else if (current.name.includes(".json")){ + console.log('Found amazfit file', current.name); + faceFile = current; + } else { + console.log('Found unsupported file', current.name); + } } } handleWatchFace(infoFile, faceFile, resourceFiles); @@ -207,6 +723,8 @@ {name:"imageclock.img", url:"app-icon.js", evaluate:true}, ] }; + + console.log("Uploading app:", appDef); sendCustomizedApp(appDef); }); @@ -217,6 +735,8 @@ expectedFiles = 0; handledFiles = 0; document.getElementById('btnSave').disabled = true; + document.getElementById('btnSaveFace').disabled = true; + document.getElementById('btnSaveZip').disabled = true; document.getElementById('btnUpload').disabled = true; JSZip.loadAsync(f).then(function(zip) { @@ -228,13 +748,22 @@ var promise = zip.file("face.json").async("string").then((data)=>{ console.log("face.json data", data); - faceJson = JSON.parse(data); + faceJson = parseFaceJson(data); }); - promise = promise.then(zip.file("info.json").async("string").then((data)=>{ - console.log("info.json data", data); - infoJson = JSON.parse(data); - })); + if (isNativeFormat()){ + promise = promise.then(zip.file("info.json").async("string").then((data)=>{ + console.log("info.json data", data); + infoJson = JSON.parse(data); + })); + } else { + infoJson = { + "color": "3bit", + "compression": true, + "transparent": true + }; + + } zip.folder("resources").forEach(function (relativePath, file){ console.log("iterating over", relativePath); @@ -274,10 +803,30 @@ handleFile(files[0]); } + function handleZipExport(){ + rootZip.generateAsync({type:"base64"}).then(function (base64) { + var h = document.createElement('a'); + h.href="data:application/zip;base64," + base64; + h.target = '_blank'; + h.download = "watchface.zip"; + h.click(); + }); + } + + document.getElementById("btnSaveFace").addEventListener("click", function() { + var h = document.createElement('a'); + h.href = 'data:text/json;charset=utf-8,' + encodeURI(JSON.stringify(faceJson)); + h.target = '_blank'; + h.download = "face.json"; + h.click(); + }); document.getElementById('zipLoader').addEventListener('change', handleZipSelect, false); + document.getElementById('btnSaveZip').addEventListener('click', handleZipExport, false); document.getElementById('btnSave').disabled = true; + document.getElementById('btnSaveFace').disabled = true; + document.getElementById('btnSaveZip').disabled = true; document.getElementById('btnUpload').disabled = true; diff --git a/apps/imageclock/metadata.json b/apps/imageclock/metadata.json index 93c87e842..cfbe19657 100644 --- a/apps/imageclock/metadata.json +++ b/apps/imageclock/metadata.json @@ -2,7 +2,7 @@ "id": "imageclock", "name": "Imageclock", "shortName": "imageclock", - "version": "0.02", + "version": "0.03", "type": "clock", "description": "BETA!!! File formats still subject to change --- This app is a highly customizable watchface. To use it, you need to select a watchface. You can build the watchfaces yourself without programming anything. All you need to do is write some json and create image files.", "icon": "app.png",