/* * 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 = 2; // 0 - longitude, 1 - mission control, 2 - longitude var wait_time = LONG_WAIT; var events = null; var deviceFingerprint = null; var eventsTimeStamp = 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 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; } function renderLine(context, p1, p2, thickness, color) { context.save(); context.beginPath(); context.lineCap = "round"; context.lineWidth = thickness; context.moveTo(p1.x, p1.y); context.lineTo(p2.x, p2.y); context.strokeStyle = color; context.stroke(); 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 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 - 60; renderCircle(ctxContent, polToCart(sunDistance, hourToAngle(hour + minute / 60)), 16, sunColor); } function renderSmallSun(date, hour, minute, second) { sunColor = "#FFD700"; sunDistance = document.body.clientWidth / 2 - 40; renderCircle(ctxContent, polToCart(sunDistance, hourToAngle(hour + minute / 60)), 10, 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); } function renderAnalog(hour, minute, second) { // hour var hourAngle = hourToAngle(hour + minute / 60)*2+180; renderLine(ctxContent, polToCart(document.body.clientWidth / 2 - 120, hourAngle), polToCart(document.body.clientWidth / 2 - 60, hourAngle), 18, '#ffffff'); renderLine(ctxContent, polToCart(document.body.clientWidth / 2 - 120, hourAngle), polToCart(document.body.clientWidth / 2 - 60, hourAngle), 12, '#000000'); // minute var minuteAngle = minuteToAngle(minute + second / 60); renderLine(ctxContent, polToCart(document.body.clientWidth / 2 - 120, minuteAngle), polToCart(document.body.clientWidth / 2 - 40, minuteAngle), 18, '#ffffff'); } /** * 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) { drawDigitalWatch(); } else if(DESIGN === 2) { renderSmallSun(date, hour, minute, second); drawDigitalWatch(); } /* 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; } if(DESIGN === 0) { var thickness = 18; } else if(DESIGN === 1) { var thickness = 50; } else if(DESIGN === 2) { var thickness = 18; } wait_time = LONG_WAIT; var wroteEvent = false; 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); } else if(DESIGN === 2) { var edge = document.body.clientWidth / 2 - thickness / 2 - 2; renderArc(ctxContent, center, edge, thickness, e.color, hourToAngle(startTime), hourToAngle(stopTime), startedBeforeToday, endsAfterToday); } if(eventNow(e, hour, minute)) { console.log("!!!writing name"); wroteEvent = true; drawEventName(e.name); } } if(!wroteEvent) { drawEventName(""); } if(DESIGN === 0) { } else if(DESIGN === 1) { var inner = document.body.clientWidth / 2 - thickness; renderSimpleArc(ctxContent, center, 115, 8, "#FFFFFF", hourToAngle(20), hourToAngle(20+0.999)) for(var i = 0; i < 24; i++) { renderSimpleArc(ctxContent, center, edge, thickness, "#000000", i*15-0.8, i*15+0.8); } } else if(DESIGN === 2) { } } function eventNow(event, hour, minute) { if( ((event.startDateTime.time.hour < hour) || (event.startDateTime.time.hour <= hour && event.startDateTime.time.minute <= minute)) ) { console.log("start before"); } if( ((event.stopDateTime.time.hour > hour) || (event.stopDateTime.time.hour >= hour && event.stopDateTime.time.minute >= minute)) ) { console.log("stop after"); } if( ((event.startDateTime.time.hour < hour) || (event.startDateTime.time.hour <= hour && event.startDateTime.time.minute <= minute)) && ((event.stopDateTime.time.hour > hour) || (event.stopDateTime.time.hour >= hour && event.stopDateTime.time.minute >= minute)) ){ return true; } return false; } function drawEventName(name) { var strEvent = document.getElementById("str-event"); strEvent.innerHTML = name; } function drawDigitalWatch(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; } } /** * 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 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(); });