/* * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* 'downloads' for debugging, * 'wgt-private' for any release */ var STORAGE_SPACE = "downloads"; var SHORT_WAIT = 10000; var LONG_WAIT = 30000; // 600000 var DESIGN = 0; // 0 - longitude, 1 - mission control var wait_time = LONG_WAIT; var events = null; var weather = null; var deviceFingerprint = null; var eventsTimeStamp = 0; var navigatorTimeStamp = 0; var flagDigital = true; (function() { var canvasLayout, canvasContent, ctxLayout, ctxContent, center, watchRadius; var flagConsole = true; function deleteFile(name, callback) { /* successful resolution of wgt-private */ function onsuccess(dir) { var dirfile = null; try { dirfile = dir.resolve(name); } catch (exc) { console.log(exc.message) } // if file not existed, call callback with null if (dirfile == null) { callback(); return; } else { // if file was found, delete it dir.deleteFile(dirfile.fullPath, function() { console.log("deleted"); callback(); }, function(e) { console.log(e) }); } } function onerror(e) { console.log("error" + e); } function onsuccessPermission() { tizen.filesystem.resolve(STORAGE_SPACE, onsuccess, onerror, "rw"); } function onErrorPermission(e) { console.log("error " + JSON.stringify(e)); } tizen.ppm.requestPermission("http://tizen.org/privilege/mediastorage", onsuccessPermission, onErrorPermission); } function clearDownloads() { var onError = function(e) { console.log('Error!' + e.message); }; var onResolveSuccess = function(dir) { var onListFilesSuccess = function(files) { files .forEach(function(file) { if (!file.isDirectory) { dir.deleteFile(file.fullPath, onDeleteSuccess, onError); } }); }; dir.listFiles(onListFilesSuccess, onError); }; var onDeleteSuccess = function() { }; tizen.filesystem.resolve('/opt/usr/media/Downloads', onResolveSuccess, onError); } function getJsonFile(name, callback) { console.log("searching for File"); /* successful resolution of wgt-private */ function onsuccess(dir) { var dirfile = null; try { dirfile = dir.resolve(name); } catch (exc) { console.log(exc.message) } // if file not existed, call callback with null if (dirfile == null) { callback(null) return; } else { // if file was found, call callback with json dirfile.openStream("r", function(fs) { callback(JSON.parse(fs.read(dirfile.fileSize))); fs.close(); }); } } function onerror(e) { console.log("error" + e); } function onsuccessPermission() { tizen.filesystem.resolve(STORAGE_SPACE, onsuccess, onerror, "rw"); } function onErrorPermission(e) { console.log("error " + JSON.stringify(e)); } tizen.ppm.requestPermission("http://tizen.org/privilege/mediastorage", onsuccessPermission, onErrorPermission); } function readWeatherJSON() { /* * - requests permission to view media storage - resolves the file * 'calendarevents' - opens stream and reads entire file as json */ getJsonFile("weather", function(newweather) { weather = newweather; console.log(weather); }); } function readCalendarJSON() { /* * - requests permission to view media storage - resolves the file * 'calendarevents' - opens stream and reads entire file as json */ getJsonFile("calendarevents", function(eventlist) { events = eventlist; }); } function getNewFingerprintFromServer() { getFileFromServer("devicefingerprint.json", "https://longitudecalendar.com/", function() { console.log("getting device fingerprint from server"); getJsonFile("devicefingerprint", function(df) { deviceFingerprint = df; console.log(deviceFingerprint); }); }); } function getDeviceFingerprint() { // check if device id set already as global, if it is return if (deviceFingerprint !== null) { return; } // otherwise, check if there is a devicefingerprint file in wgt-private // if there is, open it up and get your device ID from it getJsonFile("devicefingerprint", function(df) { if (df !== null) { deviceFingerprint = df; } else { // otherwise, ask the longitude server for a device fingerprint // file getNewFingerprintFromServer(); } }); } function getFileFromServer(route, server, callback) { console.log("getting file"); var server = server; var downloadRequest = new tizen.DownloadRequest(server + route, STORAGE_SPACE); tizen.systeminfo .getPropertyValue( 'NETWORK', function(networkInfo) { if (networkInfo.networkType === 'NONE') { console .log('Network connection is not available.Download is not possible.'); downloadRequest = null; } }); var listener = { /* When the download progresses (interval is platform-dependent) */ onprogress : function(id, receivedSize, totalSize) { console.log('progress for id: ' + id); }, /* When the user pauses the download */ onpaused : function(id) { console.log('Paused with id: ' + id); }, /* When the user cancels the download */ oncanceled : function(id) { console.log('Canceled with id: ' + id); }, /* When the download is completed */ oncompleted : function(id, fullPath) { console.log('Completed with id: ' + id + ', full path: ' + fullPath); callback(); }, /* When the download fails */ onfailed : function(id, error) { console.log('Failed with id: ' + id + ', error name: ' + error.name); } }; tizen.download.start(downloadRequest, listener); } async function updateCalendar() { if (deviceFingerprint === null) { console.log("no fingerprint, loading from file or server"); getDeviceFingerprint(); wait_time = SHORT_WAIT; return false; } function deleteCallback() { console.log("done"); console.log("getting new calendar events"); getFileFromServer("device/" + deviceFingerprint.deviceName + "/calendarevents.json", "https://longitudecalendar.com/", readCalendarJSON); } console.log("deleting calendarevents"); deleteFile("calendarevents", deleteCallback); return true; } /* * Deletes existing weather file gets current geo location gets weather * information of current geo location */ function updateLocation(position) { console.log("succes"); currentGpsPosLat = position.coords.latitude; currentGpsPosLong = position.coords.longitude; apiKey = "22ef6bf26472a340e8091f42fca3a14a" console.log(currentGpsPosLat + " -- " + currentGpsPosLong); console.log("deleting weather"); deleteFile("weather", deleteCallback); function deleteCallback() { getFileFromServer("data/2.5/weather?lat=" + currentGpsPosLat + "&lon=" + currentGpsPosLong + "&appid=" + apiKey, "https://api.openweathermap.org/", readWeatherJSON) } } /** * Renders a circle with specific center, radius, and color * * @private * @param {object} * context - the context for the circle to be placed in * @param {number} * radius - the radius of the circle * @param {string} * color - the color of the circle */ function renderCircle(context, center, radius, color) { context.save(); context.beginPath(); context.fillStyle = color; context.arc(center.x, center.y, radius, 0, 2 * Math.PI); context.fill(); context.closePath(); context.restore(); } /** * Renders a circle with specific center, radius, and color * * @private * @param {object} * context - the context for the circle to be placed in * @param {number} * radius - the radius of the circle * @param {string} * color - the color of the circle */ function renderRing(context, center, radius, width, color) { context.save(); context.beginPath(); context.arc(center.x, center.y, radius, 0, 2 * Math.PI); context.strokeStyle = color; context.lineWidth = width; context.stroke(); context.closePath(); context.restore(); } function renderSimpleArc(context, center, radius, thickness, color, startAngle, endAngle) { context.save(); context.beginPath(); context.arc(center.x, center.y, radius, startAngle * Math.PI / 180., endAngle * Math.PI / 180.); context.fillStyle = color; context.strokeStyle = color; context.lineWidth = thickness; context.stroke(); context.restore(); } /** * Renders a partial with specific center, radius, and color * * @private * @param {object} * context - the context for the circle to be placed in * @param {number} * radius - the radius of the circle * @param {string} * color - the color of the circle */ function renderArc(context, center, radius, thickness, color, startAngle, endAngle, hardStart, hardStop) { if (hardStart == undefined) { hardStart = false; } if (hardStop == undefined) { hardStop = false; } var seperation = 3; // if arc size smaller than separation for circles or circle, // draw a circle var arcsize = 0; if (endAngle < startAngle) { arcsize = 360 - startAngle + endAngle; } else { arcsize = endAngle - startAngle; } if (arcsize < 2 * seperation) { renderCircle(context, polToCart(radius, startAngle + arcsize / 2), 9, color); return; } if (!hardStart) { startAngle += seperation; } if (startAngle >= 360) { startAngle -= 360; } if (!hardStop) { endAngle -= seperation; } if (endAngle < 0) { endAngle += 360; } // otherwise draw an arc and two circles context.save(); context.beginPath(); context.arc(center.x, center.y, radius, startAngle * Math.PI / 180., endAngle * Math.PI / 180.); context.fillStyle = color; context.strokeStyle = color; context.lineWidth = thickness; context.stroke(); context.restore(); if (!hardStart) renderCircle(context, polToCart(radius, startAngle), 9, color); if (!hardStop) renderCircle(context, polToCart(radius, endAngle), 9, color); } function polToCart(radius, angle) { pos = { x : center.x + radius * Math.cos(angle * Math.PI / 180), y : center.y + radius * Math.sin(angle * Math.PI / 180) }; return pos; } function hourToAngle(hour) { if (hour >= 18) hour -= 18; else hour += 6; var angle = (hour) * 15; return angle; } function minuteToAngle(minute) { if (minute >= 15) minute -= 15; else minute += 45; angle = (minute / 60) * 360; return angle; } /** * Renders a needle with specific center, angle, start point, end point, * width and color * * @private * @param {object} * context - the context for the needle to be placed in * @param {number} * angle - the angle of the needle (0 ~ 360) * @param {number} * startPoint - the start point of the needle (-1.0 ~ 1.0) * @param {number} * startPoint - the end point of the needle (-1.0 ~ 1.0) * @param {number} * width - the width of the needle * @param {string} * color - the color of the needle */ function renderNeedle(context, angle, startPoint, endPoint, width, color) { var radius = context.canvas.width / 2, centerX = context.canvas.width / 2, centerY = context.canvas.height / 2, dxi = radius * Math.cos(angle) * startPoint, dyi = radius * Math.sin(angle) * startPoint, dxf = radius * Math.cos(angle) * endPoint, dyf = radius * Math.sin(angle) * endPoint; context.save(); context.beginPath(); context.lineWidth = width; context.strokeStyle = color; context.moveTo(centerX + dxi, centerY + dyi); context.lineTo(centerX + dxf, centerY + dyf); context.stroke(); context.closePath(); context.restore(); } /** * Renders the 24h sun around the watch */ function renderSun(date, hour, minute, second) { if (hour > 6 && hour < 18) sunColor = "#FFD700"; else { sunColor = "#C0C0C0" } sunDistance = document.body.clientWidth / 2 - 80; renderCircle(ctxContent, polToCart(sunDistance, hourToAngle(hour + minute / 60)), 22, sunColor); } function renderEarth(date, minute, second) { var earthColor = "#0077BE"; var earthDistance = document.body.clientWidth / 2 - 120; renderCircle(ctxContent, polToCart(earthDistance, minuteToAngle(minute + second / 60)), 14, earthColor); } /** * Renders text at a specific center, radius, and color * * @private * @param {object} * context - the context for the text to be placed in * @param {string} * text - the text to be placed * @param {number} * x - the x-coordinate of the text * @param {number} * y - the y-coordinate of the text * @param {number} * textSize - the size of the text in pixel * @param {string} * color - the color of the text */ function renderText(context, text, x, y, textSize, color) { context.save(); context.beginPath(); context.font = textSize + "px Courier"; context.textAlign = "center"; context.textBaseline = "middle"; context.fillStyle = color; context.fillText(text, x, y); context.closePath(); context.restore(); } /** * Draws the basic layout of the watch * * @private */ function drawWatchLayout() { // Clear canvas ctxLayout.clearRect(0, 0, ctxLayout.canvas.width, ctxLayout.canvas.height); // Draw the background circle /* * renderCircle(ctxLayout, center, watchRadius, "#000000"); grd = * ctxLayout.createLinearGradient(0, 0, watchRadius * 2, 0); * grd.addColorStop(0, "#000000"); grd.addColorStop(0.5, "#454545"); * grd.addColorStop(1, "#000000"); ctxLayout.fillStyle = grd; * renderCircle(ctxLayout, center, watchRadius * 0.945, grd); * renderCircle(ctxLayout, center, watchRadius * 0.7, "#000000"); */ // Draw the dividers // 60 unit divider /* * for (i = 1; i <= 60; i++) { angle = (i - 15) * (Math.PI * 2) / 60; * renderNeedle(ctxLayout, angle, 0.95, 1.0, 1, "#c4c4c4"); } // 12 unit * divider for (j = 1; j <= 12; j++) { angle = (j - 3) * (Math.PI * 2) / * 12; renderNeedle(ctxLayout, angle, 0.7, 0.945, 10, "#c4c4c4"); } */ // renderText(ctxLayout, "TIZEN WATCH", center.x, center.y - // (watchRadius * 0.4), 25, "#999999"); } /** * Draws the content of the watch * * @private */ function drawWatchContent() { var datetime = tizen.time.getCurrentDateTime(); // Clear canvas ctxContent.clearRect(0, 0, ctxContent.canvas.width, ctxContent.canvas.height); var hour = datetime.getHours(), minute = datetime.getMinutes(), second = datetime .getSeconds(), date = datetime.getDate(); if (DESIGN === 0) { renderSun(date, hour, minute, second); renderEarth(ctxContent, minute, second); } else if (DESIGN === 1) { drawMissionControl(); } /* if no return from server yet */ if (events === null) { return; } /* if device not on server anymore */ if (events.kind === "not found") { deviceFingerprint = null; events = null; deleteFile("devicefingerprint", function() { }); wait_time = SHORT_WAIT; return; } /* if device not registered */ if (events.kind === "unregistered") { wait_time = SHORT_WAIT; if (deviceFingerprint === null) { } else { renderText(ctxContent, "add new device on", center.x, center.y - 70, 25, "FFFFFF"); renderText(ctxContent, "longitudecalendar.com", center.x, center.y - 40, 25, "FFFFFF"); if (DESIGN === 0) renderText(ctxContent, deviceFingerprint.deviceName, center.x, center.y, 20, "FF0000"); else if (DESIGN === 1) { renderText(ctxContent, deviceFingerprint.deviceName, center.x, center.y + 40, 20, "FF0000") } } return; } if (DESIGN === 0) { var thickness = 18; } else if (DESIGN === 1) { var thickness = 50; } console.log("switched to long wait"); wait_time = LONG_WAIT; for ( var event in events.events) { var startedBeforeToday = false; var endsAfterToday = false; var e = events.events[event]; // check if not today if (e.startDateTime.date.year !== tizen.time.getCurrentDateTime() .getFullYear() || e.startDateTime.date.month !== tizen.time .getCurrentDateTime().getMonth() + 1 || e.startDateTime.date.day !== tizen.time .getCurrentDateTime().getDate()) { // if not today, check if it is an earlier event if (e.startDateTime.date.year < tizen.time.getCurrentDateTime() .getFullYear()) { startedBeforeToday = true; } else if (e.startDateTime.date.month < tizen.time .getCurrentDateTime().getMonth() + 1) { startedBeforeToday = true; } else if (e.startDateTime.date.day < tizen.time .getCurrentDateTime().getDate()) { startedBeforeToday = true; } else { continue; } } // check if not today if (e.stopDateTime.date.year !== tizen.time.getCurrentDateTime() .getFullYear() || e.stopDateTime.date.month !== tizen.time .getCurrentDateTime().getMonth() + 1 || e.stopDateTime.date.day !== tizen.time .getCurrentDateTime().getDate()) { // if not check if later date if (e.stopDateTime.date.year > tizen.time.getCurrentDateTime() .getFullYear()) { endsAfterToday = true; } else if (e.stopDateTime.date.month > tizen.time .getCurrentDateTime().getMonth() + 1) { endsAfterToday = true; } else if (e.stopDateTime.date.day > tizen.time .getCurrentDateTime().getDate()) { endsAfterToday = true; } else { continue; } } if (startedBeforeToday && endsAfterToday) { continue; } var startTime = 0; if (!startedBeforeToday) { startTime = e.startDateTime.time.hour + e.startDateTime.time.minute / 60; } var stopTime = 24; if (!endsAfterToday) { stopTime = e.stopDateTime.time.hour + e.stopDateTime.time.minute / 60; } if (DESIGN === 0) { var edge = document.body.clientWidth / 2 - thickness / 2 - 2; renderArc(ctxContent, center, edge, thickness, e.color, hourToAngle(startTime), hourToAngle(stopTime), startedBeforeToday, endsAfterToday); } else if (DESIGN === 1) { var edge = document.body.clientWidth / 2 - thickness / 2 - 2; renderArc(ctxContent, center, edge, thickness, e.color, hourToAngle(startTime), hourToAngle(stopTime), true, true); } } if (DESIGN === 0) { } else if (DESIGN === 1) { var inner = document.body.clientWidth / 2 - thickness; renderSimpleArc(ctxContent, center, 115, 8, "#FFFFFF", hourToAngle(hour), hourToAngle(hour + 0.999)) for (var i = 0; i < 24; i++) { renderSimpleArc(ctxContent, center, edge, thickness, "#000000", i * 15 - 0.8, i * 15 + 0.8); } } } function drawMissionControl(datetime) { var strHours = document.getElementById("str-hours"), strConsole = document .getElementById("str-console"), strMinutes = document .getElementById("str-minutes"), datetime = tizen.time .getCurrentDateTime(), hour = datetime.getHours(), minute = datetime .getMinutes(); strHours.innerHTML = hour; strMinutes.innerHTML = minute; if (hour < 10) { strHours.innerHTML = "0" + hour; } if (minute < 10) { strMinutes.innerHTML = "0" + minute; } // Each 0.5 second the visibility of flagConsole is changed. if (flagDigital) { if (flagConsole) { strConsole.style.visibility = "visible"; flagConsole = false; } else { strConsole.style.visibility = "hidden"; flagConsole = true; } } else { strConsole.style.visibility = "visible"; flagConsole = false; } } /** * Sets to background image as BACKGROUND_URL, and starts timer for normal * digital watch mode. * * @private */ function initDigitalWatch() { flagDigital = true; document.getElementById("digital-body").style.backgroundImage = BACKGROUND_URL; interval = setInterval(updateTime, 500); } function loopCalendar(offset_ms) { var d = new Date(); var currentTime = d.getTime(); if (eventsTimeStamp + offset_ms < currentTime) { updateCalendar(); eventsTimeStamp = currentTime; } } function loopNavigator(offset_ms) { var d = new Date(); var options = { enableHighAccuracy : true, timeout : 5000 }; var currentTime = d.getTime(); if (navigatorTimeStamp + offset_ms < currentTime) { navigator.geolocation.getCurrentPosition(updateLocation, errorCallback, options); navigatorTimeStamp = currentTime; } } /** * Set default variables * * @private */ function setDefaultVariables() { canvasLayout = document.querySelector("#canvas-layout"); ctxLayout = canvasLayout.getContext("2d"); canvasContent = document.querySelector("#canvas-content"); ctxContent = canvasContent.getContext("2d"); // Set the canvases square canvasLayout.width = document.body.clientWidth; canvasLayout.height = canvasLayout.width; canvasContent.width = document.body.clientWidth; canvasContent.height = canvasContent.width; center = { x : document.body.clientWidth / 2, y : document.body.clientHeight / 2 }; watchRadius = canvasLayout.width / 2; } /** * Set default event listeners * * @private */ function setDefaultEvents() { // add eventListener to update the screen immediately when the device // wakes up document.addEventListener("visibilitychange", function() { if (!document.hidden) { // Draw the content of the watch drawWatchContent(); } }); } /** * Initiates the application * * @private */ function init() { // clearDownloads(); setDefaultVariables(); setDefaultEvents(); // Draw the basic layout and the content of the watch at the beginning drawWatchLayout(); drawWatchContent(); // Update the content of the watch every second setInterval(function() { drawWatchContent(); loopCalendar(wait_time); loopNavigator(wait_time); }, 1000); } function errorCallback(error) { console.log("error"); console.log(error); } window.onload = init; }()); document.addEventListener('ambientmodechanged', function(ev) { var mode = ev.detail.ambientMode; if (mode == true) { /* Change the UI for ambient mode */ // updateAmbientWatchFaceDOM(); // function to rearange DOM for AOD mode flagDigital = false; updateTime(); } else { /* Change the UI for normal mode */ // updateNormalWatchFaceDOM(); // function to rearange DOM for AOD mode flagDigital = true; updateTime(); } }); document.addEventListener('timetick', function(ev) { /* Update the UI */ drawWatchContent(); });