/* * 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 = "wgt-private"; var SHORT_WAIT = 10000; var LONG_WAIT = 600000; var wait_time = LONG_WAIT; var events = null; var deviceFingerprint = null; var eventsTimeStamp = 0; (function() { var canvasLayout, canvasContent, ctxLayout, ctxContent, center, watchRadius; 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 readJSON() { /* - 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", 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, callback) { console.log("getting file"); var server = "https://longitudecalendar.com/" 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); } 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", readJSON); } console.log("deleting calendarevents"); deleteFile("calendarevents", deleteCallback); return true; } /** * 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 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, color, startAngle, endAngle, hardStart, hardStop) { if(hardStart == undefined) hardStart = false; if(hardStop == undefined) hardStop= false; seperation = 5; // 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; // else 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 = 18; 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; 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 - 60; renderCircle(ctxContent, polToCart(sunDistance, hourToAngle(hour + minute / 60)), 16, sunColor); } function renderEarth(date, minute, second) { var earthColor = "#0077BE"; var earthDistance = document.body.clientWidth / 2 - 120; renderCircle(ctxContent, polToCart(earthDistance, minuteToAngle(minute + second / 60)), 10, 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() { var i, j; // 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(), hour = datetime.getHours(), minute = datetime.getMinutes(), second = datetime.getSeconds(), date = datetime.getDate(); // Clear canvas ctxContent.clearRect(0, 0, ctxContent.canvas.width, ctxContent.canvas.height); // Draw the hour needle renderSun(date, hour, minute, second); // Draw the minute needle renderEarth(ctxContent, minute, second); /* 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, deviceFingerprint.deviceName, center.x, center.y, 20, "FF0000"); } return; } console.log("switched to long wait"); wait_time = LONG_WAIT; /* else: device registered and all events saved */ var thickness = 18; var edge = document.body.clientWidth / 2 - thickness / 2 - 2; 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.startDateTime.date.year > tizen.time.getCurrentDateTime().getFullYear()) endsAfterToday = true; else if(e.startDateTime.date.month > tizen.time.getCurrentDateTime().getMonth() + 1) endsAfterToday = true; else if(e.startDateTime.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 = 0; if(!endsAfterToday) stopTime = e.stopDateTime.time.hour + e.stopDateTime.time.minute / 60; renderArc(ctxContent, center, edge, e.color, hourToAngle(startTime), hourToAngle(stopTime), startedBeforeToday, endsAfterToday); } } function loopCalendar(offset_ms) { var d = new Date(); var currentTime = d.getTime(); if(eventsTimeStamp + offset_ms < currentTime){ updateCalendar(); eventsTimeStamp = 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); }, 1000); } 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 updateTime(); } else { /* Change the UI for normal mode */ // updateNormalWatchFaceDOM(); // function to rearange DOM for AOD mode updateTime(); } }); document.addEventListener('timetick', function(ev) { /* Update the UI */ drawWatchContent(); });