summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHyungKyu Song <hk76.song@samsung.com>2013-02-15 15:11:56 +0900
committerHyungKyu Song <hk76.song@samsung.com>2013-02-15 15:11:56 +0900
commite57be6b17bee9e3939dce6a63dd641966b553a1e (patch)
tree70e1363f0431f391e7214651c00fcd35f8b1d705
parent21de49ef41802bf9c43dfc543534cfeac0b6093b (diff)
downloadExercisePlanner-2.0_release.tar.gz
ExercisePlanner-2.0_release.tar.bz2
ExercisePlanner-2.0_release.zip
Tizen 2.0 Release2.0_release
-rw-r--r--AUTHORS5
-rw-r--r--LICENSE.Flora206
-rw-r--r--NOTICE4
-rw-r--r--config.xml13
-rw-r--r--css/GraphSchedule.css122
-rw-r--r--css/jquery.ui.layout.css33
-rw-r--r--css/style.css279
-rwxr-xr-xicon.pngbin0 -> 17581 bytes
-rw-r--r--images/background.pngbin0 -> 201 bytes
-rw-r--r--images/longBothHorizonGradient.pngbin0 -> 773 bytes
-rw-r--r--images/longBothHorizonGradient2.pngbin0 -> 1540 bytes
-rw-r--r--images/markers.pngbin0 -> 437 bytes
-rw-r--r--images/state_lazy.pngbin0 -> 62060 bytes
-rw-r--r--images/state_run.pngbin0 -> 27376 bytes
-rw-r--r--index.html229
-rw-r--r--js/GraphSchedule.js186
-rw-r--r--js/UI.js415
-rw-r--r--js/UI.simpleTemplate.js25
-rw-r--r--js/app.alarms.js55
-rw-r--r--js/app.alarmsGenerating.js153
-rw-r--r--js/app.js450
-rw-r--r--js/app.onAlarm.js66
-rw-r--r--js/app.timeRange.js386
-rw-r--r--js/ext.jqMobile.js17
-rw-r--r--js/main.js21
-rw-r--r--music/Runner.mp3bin0 -> 495598 bytes
-rw-r--r--signature1.xml160
-rw-r--r--templates/GraphSchedule.tmpl91
28 files changed, 2916 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..3d165e3
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,5 @@
+Tomasz Lukawski <t.lukawski at samsung dot com>
+Artur Kobylinski <a.kobylinski at samsung dot com>
+Piotr Wronski <p.wronski at samsung dot com>
+Pawel Sierszen <p.sierszen at samsung dot com>
+Tomasz Paciorek <t.paciorek at samsung dot com>
diff --git a/LICENSE.Flora b/LICENSE.Flora
new file mode 100644
index 0000000..9c95663
--- /dev/null
+++ b/LICENSE.Flora
@@ -0,0 +1,206 @@
+Flora License
+
+Version 1.0, May, 2012
+
+http://floralicense.org/license/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction,
+and distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by
+the copyright owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and
+all other entities that control, are controlled by, or are
+under common control with that entity. For the purposes of
+this definition, "control" means (i) the power, direct or indirect,
+to cause the direction or management of such entity,
+whether by contract or otherwise, or (ii) ownership of fifty percent (50%)
+or more of the outstanding shares, or (iii) beneficial ownership of
+such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity
+exercising permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications,
+including but not limited to software source code, documentation source,
+and configuration files.
+
+"Object" form shall mean any form resulting from mechanical
+transformation or translation of a Source form, including but
+not limited to compiled object code, generated documentation,
+and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form,
+made available under the License, as indicated by a copyright notice
+that is included in or attached to the work (an example is provided
+in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form,
+that is based on (or derived from) the Work and for which the editorial
+revisions, annotations, elaborations, or other modifications represent,
+as a whole, an original work of authorship. For the purposes of this License,
+Derivative Works shall not include works that remain separable from,
+or merely link (or bind by name) to the interfaces of, the Work and
+Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original
+version of the Work and any modifications or additions to that Work or
+Derivative Works thereof, that is intentionally submitted to Licensor
+for inclusion in the Work by the copyright owner or by an individual or
+Legal Entity authorized to submit on behalf of the copyright owner.
+For the purposes of this definition, "submitted" means any form of
+electronic, verbal, or written communication sent to the Licensor or
+its representatives, including but not limited to communication on
+electronic mailing lists, source code control systems, and issue
+tracking systems that are managed by, or on behalf of, the Licensor
+for the purpose of discussing and improving the Work, but excluding
+communication that is conspicuously marked or otherwise designated
+in writing by the copyright owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity
+on behalf of whom a Contribution has been received by Licensor and
+subsequently incorporated within the Work.
+
+"Tizen Certified Platform" shall mean a software platform that complies
+with the standards set forth in the Compatibility Definition Document
+and passes the Compatibility Test Suite as defined from time to time
+by the Tizen Technical Steering Group and certified by the Tizen
+Association or its designated agent.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+this License, each Contributor hereby grants to You a perpetual,
+worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the
+Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+this License, each Contributor hereby grants to You a perpetual,
+worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+(except as stated in this section) patent license to make, have made,
+use, offer to sell, sell, import, and otherwise transfer the Work
+solely as incorporated into a Tizen Certified Platform, where such
+license applies only to those patent claims licensable by such
+Contributor that are necessarily infringed by their Contribution(s)
+alone or by combination of their Contribution(s) with the Work solely
+as incorporated into a Tizen Certified Platform to which such
+Contribution(s) was submitted. If You institute patent litigation
+against any entity (including a cross-claim or counterclaim
+in a lawsuit) alleging that the Work or a Contribution incorporated
+within the Work constitutes direct or contributory patent infringement,
+then any patent licenses granted to You under this License for that
+Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+Work or Derivative Works thereof pursuant to the copyright license
+above, in any medium, with or without modifications, and in Source or
+Object form, provided that You meet the following conditions:
+
+ 1. You must give any other recipients of the Work or Derivative Works
+ a copy of this License; and
+ 2. You must cause any modified files to carry prominent notices stating
+ that You changed the files; and
+ 3. You must retain, in the Source form of any Derivative Works that
+ You distribute, all copyright, patent, trademark, and attribution
+ notices from the Source form of the Work, excluding those notices
+ that do not pertain to any part of the Derivative Works; and
+ 4. If the Work includes a "NOTICE" text file as part of its distribution,
+ then any Derivative Works that You distribute must include a readable
+ copy of the attribution notices contained within such NOTICE file,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works, in at least one of the following places:
+ within a NOTICE text file distributed as part of the Derivative Works;
+ within the Source form or documentation, if provided along with the
+ Derivative Works; or, within a display generated by the Derivative Works,
+ if and wherever such third-party notices normally appear.
+ The contents of the NOTICE file are for informational purposes only
+ and do not modify the License.
+
+You may add Your own attribution notices within Derivative Works
+that You distribute, alongside or as an addendum to the NOTICE text
+from the Work, provided that such additional attribution notices
+cannot be construed as modifying the License. You may add Your own
+copyright statement to Your modifications and may provide additional or
+different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works
+as a whole, provided Your use, reproduction, and distribution of
+the Work otherwise complies with the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+any Contribution intentionally submitted for inclusion in the Work
+by You to the Licensor shall be under the terms and conditions of
+this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify
+the terms of any separate license agreement you may have executed
+with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+names, trademarks, service marks, or product names of the Licensor,
+except as required for reasonable and customary use in describing the
+origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+agreed to in writing, Licensor provides the Work (and each
+Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied, including, without limitation, any warranties or conditions
+of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+PARTICULAR PURPOSE. You are solely responsible for determining the
+appropriateness of using or redistributing the Work and assume any
+risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+whether in tort (including negligence), contract, or otherwise,
+unless required by applicable law (such as deliberate and grossly
+negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special,
+incidental, or consequential damages of any character arising as a
+result of this License or out of the use or inability to use the
+Work (including but not limited to damages for loss of goodwill,
+work stoppage, computer failure or malfunction, or any and all
+other commercial damages or losses), even if such Contributor
+has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+the Work or Derivative Works thereof, You may choose to offer,
+and charge a fee for, acceptance of support, warranty, indemnity,
+or other liability obligations and/or rights consistent with this
+License. However, in accepting such obligations, You may act only
+on Your own behalf and on Your sole responsibility, not on behalf
+of any other Contributor, and only if You agree to indemnify,
+defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason
+of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Flora License to your work
+
+To apply the Flora License to your work, attach the following
+boilerplate notice, with the fields enclosed by brackets "[]"
+replaced with your own identifying information. (Don't include
+the brackets!) The text should be enclosed in the appropriate
+comment syntax for the file format. We also recommend that a
+file or class name and description of purpose be included on the
+same "printed page" as the copyright notice for easier
+identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Flora License, Version 1.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://floralicense.org/license/
+
+ 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.
+
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..85044e4
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,4 @@
+Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved.
+Except as noted, this software is licensed under Flora License, Version 1.
+Please, see the LICENSE.Flora file for Flora License terms and conditions.
+
diff --git a/config.xml b/config.xml
new file mode 100644
index 0000000..d28b78c
--- /dev/null
+++ b/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns="http://www.w3.org/ns/widgets" xmlns:tizen="http://tizen.org/ns/widgets" id="http://sample-web-application.tizen.org/ExercisePlanner" version="2.0.0" viewmodes="maximized">
+ <tizen:application id="3YbLJNtYMR" required_version="1.0"/>
+ <content src="index.html"/>
+ <tizen:privilege name="http://tizen.org/privilege/alarm"/>
+ <tizen:privilege name="http://tizen.org/privilege/application.launch"/>
+ <tizen:privilege name="http://tizen.org/privilege/application.read"/>
+ <tizen:privilege name="http://tizen.org/privilege/time"/>
+ <icon src="icon.png"/>
+ <name>ExercisePlanner</name>
+ <tizen:setting screen-orientation="portrait" context-menu="disable"/>
+</widget>
+
diff --git a/css/GraphSchedule.css b/css/GraphSchedule.css
new file mode 100644
index 0000000..61da77d
--- /dev/null
+++ b/css/GraphSchedule.css
@@ -0,0 +1,122 @@
+.GraphSchedule {
+ position: relative;
+ overflow-x: scroll;
+ width: 100%;
+ height: 100px;
+ background-color: transparent;
+ z-index: 1000;
+}
+
+.GraphSchedule .container {
+ width: 600px;
+ bottom: 5px;
+ position: absolute;
+}
+
+.GraphSchedule table {
+ pointer-events: none;
+}
+
+.GraphSchedule td {
+ width: 70px;
+ text-align: left;
+ color: gray;
+ height: 10px;
+}
+
+.GraphSchedule table .grid td {
+ border-width: 1px 0 0px 1px;
+ border-style: solid;
+ border-color: white;
+ font-size: 13px;
+ font-family: Helvetica;
+ position: relative;
+}
+
+.GraphSchedule table .ranges td {
+ border-width: 1px 0 0 1px;
+ border-style: solid;
+ border-color: gray;
+ position: relative;
+}
+
+.GraphSchedule table .ranges .th {
+ border-width: 0px;
+ background-color: rgba(50, 50, 50, 0.2);
+ position: relative;
+ padding: 0;
+}
+
+.GraphSchedule table .rangesWeekend td {
+ border-width: 1px 0 0 1px;
+ border-style: solid;
+ border-color: gray;
+ position: relative;
+}
+
+.GraphSchedule table .rangesWeekend .th {
+ border-width: 0px;
+ background-color: rgba(50, 50, 250, 0.2);
+ position: relative;
+ padding: 0;
+}
+
+.GraphSchedule .flag {
+ position: absolute;
+ width: 10px;
+ height: 60px;
+ background-color: transparent;
+ bottom: 19px;
+}
+
+.GraphSchedule .flag .container {
+ position: relative;
+ width: 10px;
+ bottom: -1px;
+ height: 60px;
+}
+
+.GraphSchedule .flag .rod {
+ position: absolute;
+ width: 2px;
+ height: 55px;
+ left: 0;
+ background-color: brown;
+ bottom: 0;
+ border-width: 0 1px 0 0;
+ border-style: solid;
+ border-color: #aaa;
+}
+
+.GraphSchedule .flag p {
+ position: absolute;
+ width: 15px;
+ left: 3px;
+ background-color: #f88;
+ height: 15px;
+ top: -10px;
+ background-image: -webkit-linear-gradient(left, #F88 18%, #E31448 59%);
+}
+
+.GraphSchedule .flag .hint {
+ display: none;
+ position: absolute;
+ width: 42px;
+ height: 18px;
+ top: 8px;
+ left: 2px;
+ background-color: #FF9;
+ -webkit-border-radius: 5px 5px 5px;
+ position: absolute;
+}
+
+.currentTimeBar {
+ position: absolute;
+ height: 100px;
+ background-color: yellow;
+ width: 2px;
+ bottom: -20px;
+ border-width: 0 1px 0 0;
+ border-style: solid;
+ border-color: #FC0;
+} \ No newline at end of file
diff --git a/css/jquery.ui.layout.css b/css/jquery.ui.layout.css
new file mode 100644
index 0000000..bcecced
--- /dev/null
+++ b/css/jquery.ui.layout.css
@@ -0,0 +1,33 @@
+.ui-myHeader {
+ background-color: #293d5e;
+ height: 70px;
+}
+
+.ui-myContent {
+ overflow: auto;
+ -webkit-box-flex: 1;
+ height: 0px;
+}
+
+.ui-myFooter {
+ background-color: #192333;
+ height: 70px;
+}
+
+.ui-myExit {
+ float: right;
+ background-color: #555;
+ width: 70px;
+ height: 50px;
+ margin-top: 10px;
+ margin-right: 10px;
+ border-radius: 5px;
+ background-image: url('../images/00_winset_Back.png');
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-size: 50%;
+}
+
+.ui-myHeader {
+ text-align: center;
+} \ No newline at end of file
diff --git a/css/style.css b/css/style.css
new file mode 100644
index 0000000..e1eff8f
--- /dev/null
+++ b/css/style.css
@@ -0,0 +1,279 @@
+#one {
+ background-image: url('../images/background.png');
+}
+
+.screen {
+ font-family: serif;
+ color: white;
+ font-size: 17pt;
+}
+
+#sentence {
+ margin: 10px 30px 0 30px;
+ position: relative;
+ float: right;
+ text-indent: 30px;
+}
+
+#signature {
+ margin: 0px 30px 0 auto;
+ position: relative;
+ float: right;
+}
+
+#logo {
+ font-family: courier;
+ font-size: 36px;
+ width: 100%;
+ background-repeat: no-repeat;
+ margin: 0px 40px 0 auto;
+ color: white;
+ border-bottom: 1px solid white;
+ font-weight: bolder;
+ text-align: right;
+ line-height: 43px;
+}
+
+#status {
+ width: 400px;
+ height: 350px;
+ margin: 0 auto;
+ background-repeat: no-repeat;
+}
+
+#status.lazy {
+ background-image: url('../images/state_lazy.png');
+}
+
+#status.run {
+ background-image: url('../images/state_run.png');
+}
+
+#communicate {
+ font-size: 17pt;
+ text-align: center;
+ min-height: 70px;
+}
+
+#communicate.onAlert {
+ font-size: 60px;
+ text-align: center;
+ text-shadow: 5px 5px 5px black;
+}
+
+#communicate b {
+ font-size: 25px;
+}
+
+#communicate div {
+ font-size: 32px;
+ font-weight: bold;
+}
+
+.schedule {
+ height: 100px;
+}
+
+.scheduleOptions {
+ height: 100px;
+}
+
+.increasingStrength {
+ display: none;
+}
+
+.strength {
+ margin: 0 0 20px 0;
+}
+
+.timeRangeLabel {
+ height: 50px;
+}
+
+.activeStatus {
+ display: inline;
+ float: right;
+ color: blue;
+}
+
+.activeStatusDisable {
+ color: red;
+}
+
+.times li {
+ float: left;
+ width: 200px;
+}
+
+.times {
+ width: 100%;
+ float: left;
+ margin: 0;
+ padding: 10px;
+}
+
+.times .ui-datefield {
+ font-size: 60px;
+}
+
+/**
+
+old styles;
+
+*/
+.exerciseView {
+ width: 80%;
+ height: 200px;
+ border-width: 1px;
+ border-color: #888 #DDD #DDD #888;
+ border-style: solid;
+ background-color: #578;
+ -webkit-border-radius: 0.6em;
+ -webkit-box-align: center;
+ margin: 0 auto;
+ box-shadow: 0px 0px 20px #000;
+ position: relative;
+}
+
+.optionsExercises label {
+ line-height: 44px;
+}
+
+#exercises {
+ margin-bottom: 10px;
+}
+
+.myHeader {
+
+}
+
+.ui-controlgroup .ui-radio {
+ width: 20%;
+}
+
+.myContent {
+ margin: auto 10px 10px 10px;
+}
+
+.footerContent {
+ margin: 0 auto;
+ text-align: center;
+ padding-top: 20px;
+}
+
+.newExerciseName label {
+ width: 100%;
+}
+
+.newExerciseName input { /*background-color:white;*/
+
+}
+
+.ui-footer {
+ z-index: 1000;
+}
+
+#availableTime {
+ margin-top: 12px;
+ overflow: hidden;
+ margin-left: 0;
+ margin-right: 0;
+}
+
+#availableTime li {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+.ui-swipelist-item .ui-btn {
+ padding: 4px;
+ margin-right: 12px;
+}
+
+.ui-li-text-sub-right {
+ font-size: 18px;
+ margin-right: 17px;
+ float: right;
+ margin-top: 2px;
+ color: #08f;
+}
+
+.disabled {
+ color: #f00;
+}
+
+ul {
+ list-style-type: none;
+}
+
+.goToOptionsPack {
+ width: 100%;
+}
+
+.goToOptions {
+ width: 80%;
+ margin-left: 10%;
+ margin-right: 10%;
+}
+
+.buttonOptions {
+ float: right;
+ box-shadow: 0px 0px 10px #000;
+}
+
+.buttonStop {
+ background-color: #F40;
+ box-shadow: 0px 0px 10px #000;
+}
+
+.selectPeriodType .ui-radio {
+ width: 100%;
+}
+
+#formEnablePeriod {
+ margin-bottom: 20px;
+}
+
+#ok_wait {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+}
+
+#ok_wait .ok {
+ left: 50px;
+}
+
+#ok_wait .wait {
+ right: 50px;
+ position: absolute;
+}
+
+span.meterDesc {
+ position: absolute;
+ z-index: 100;
+ display: block;
+ text-align: center;
+ width: inherit;
+ padding-top: 8px;
+ color: #333;
+}
+
+.enableOption {
+ padding-bottom: 20px;
+}
+
+.enableOption>div {
+ display: table-cell;
+}
+
+.enableOption>label {
+ float: left;
+ padding-right: 20px;
+ padding-top: 8px;
+ vertical-align: middle;
+}
+
+div.period {
+ padding-left: 10px;
+}
diff --git a/icon.png b/icon.png
new file mode 100755
index 0000000..983c883
--- /dev/null
+++ b/icon.png
Binary files differ
diff --git a/images/background.png b/images/background.png
new file mode 100644
index 0000000..532ca02
--- /dev/null
+++ b/images/background.png
Binary files differ
diff --git a/images/longBothHorizonGradient.png b/images/longBothHorizonGradient.png
new file mode 100644
index 0000000..946d3cd
--- /dev/null
+++ b/images/longBothHorizonGradient.png
Binary files differ
diff --git a/images/longBothHorizonGradient2.png b/images/longBothHorizonGradient2.png
new file mode 100644
index 0000000..5ff092a
--- /dev/null
+++ b/images/longBothHorizonGradient2.png
Binary files differ
diff --git a/images/markers.png b/images/markers.png
new file mode 100644
index 0000000..23feef7
--- /dev/null
+++ b/images/markers.png
Binary files differ
diff --git a/images/state_lazy.png b/images/state_lazy.png
new file mode 100644
index 0000000..c2e67dd
--- /dev/null
+++ b/images/state_lazy.png
Binary files differ
diff --git a/images/state_run.png b/images/state_run.png
new file mode 100644
index 0000000..a72d60e
--- /dev/null
+++ b/images/state_run.png
Binary files differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..631602b
--- /dev/null
+++ b/index.html
@@ -0,0 +1,229 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<meta charset="utf-8" />
+<meta name="description" content="Alerts" />
+<meta name="viewport" content="width=480, user-scalable=no" />
+
+<title>ExercisePlanner</title>
+
+<script src="/usr/share/tizen-web-ui-fw/latest/js/jquery.js"></script>
+<script src="/usr/share/tizen-web-ui-fw/latest/js/tizen-web-ui-fw-libs.js"></script>
+<script src="/usr/share/tizen-web-ui-fw/latest/js/tizen-web-ui-fw.js"
+ data-framework-theme="tizen-white"></script>
+
+<script src="./js/UI.js"></script>
+<script src="./js/UI.simpleTemplate.js"></script>
+<script src="./js/ext.jqMobile.js"></script>
+<script src="./js/GraphSchedule.js"></script>
+
+<script src="./js/app.js"></script>
+<script src="./js/app.alarms.js"></script>
+<script src="./js/app.timeRange.js"></script>
+<script src="./js/app.alarmsGenerating.js"></script>
+<script src="./js/app.onAlarm.js"></script>
+<script type="text/javascript" src="./js/main.js"></script>
+
+<link rel="stylesheet" type="text/css" href="./css/style.css" />
+<link rel="stylesheet" type="text/css" href="./css/GraphSchedule.css" />
+</head>
+
+<body>
+ <div data-role="page" id="one" data-add-back-btn="footer" data-footer-exist="true">
+ <div data-role="content" data-scroll="none">
+ <div class="screen">
+ <div id="logo">Exercise Planner</div>
+ <div id="status"></div>
+ <div class="schedule"></div>
+ <div id="communicate"></div>
+ <div id="sentence"></div>
+ <div id="signature"></div>
+ </div>
+ </div>
+
+ <div data-role="footer" data-position="fixed">
+ <div data-role="tabbar" id="mainControl" data-style="tabbar">
+ <ul>
+ <li><a href="#" id="startStop">stop training</a></li>
+ <li><a href="#two" id="options">options</a></li>
+ </ul>
+ </div>
+
+ <div id="onAlertControl" data-style="tabbar"
+ style="display: none;">
+ <ul>
+ <li><a href="#" id="ok">OK</a></li>
+ <li><a href="#" id="wait">Wait</a></li>
+ <li><a href="#" id="todayOffAll">Today off all</a></li>
+ </ul>
+ </div>
+ </div>
+ </div>
+
+ <div data-role="page" id="two" data-add-back-btn="footer">
+ <div data-role="header" data-position="fixed">
+ <h1>ExercisePlanner &gt; options</h1>
+ </div>
+
+ <div data-role="content">
+ <div class="myContent">
+
+ <div data-role="fieldcontain" class="frequency">
+ <div>Frequency</div>
+ <fieldset data-role="controlgroup" data-type="horizontal" id="frequency">
+ <input type="radio" name="radioFrequency"
+ data-icon="segment-titlestyle-segonly" id="freq1" value="1" />
+ <label for="freq1">1</label>
+ <input type="radio" name="radioFrequency"
+ data-icon="segment-titlestyle-segonly" id="freq2" value="2" />
+ <label for="freq2">2</label>
+ <input type="radio" name="radioFrequency"
+ data-icon="segment-titlestyle-segonly" id="freq3" value="3" />
+ <label for="freq3">3</label>
+ <input type="radio" name="radioFrequency"
+ data-icon="segment-titlestyle-segonly" id="freq4" value="4" />
+ <label for="freq4">4</label>
+ <input type="radio" name="radioFrequency"
+ data-icon="segment-titlestyle-segonly" id="freq5" value="5" />
+ <label for="freq5">5</label>
+ </fieldset>
+ </div>
+
+ <div class="scheduleOptions"></div>
+
+ <div data-role="tabbar" data-style="tabbar" class="typeOfPeriods">
+ <ul>
+ <li id="workdaysType"><a>workday</a></li>
+ <li id="weekendType"><a>weekend</a></li>
+ </ul>
+ </div>
+
+ <div data-role="fieldcontain" class="strength">
+ <div>Strength</div>
+ <fieldset data-role="controlgroup" data-type="horizontal"
+ id="strength">
+ <input type="radio" name="radioStrength"
+ data-icon="segment-titlestyle-segonly" id="stre1" value="1" />
+ <label for="stre1">1</label>
+ <input type="radio" name="radioStrength"
+ data-icon="segment-titlestyle-segonly" id="stre2" value="2" />
+ <label for="stre2">2</label>
+ <input type="radio" name="radioStrength"
+ data-icon="segment-titlestyle-segonly" id="stre3" value="3" />
+ <label for="stre3">3</label>
+ <input type="radio" name="radioStrength"
+ data-icon="segment-titlestyle-segonly" id="stre4" value="4" />
+ <label for="stre4">4</label>
+ <input type="radio" name="radioStrength"
+ data-icon="segment-titlestyle-segonly" id="stre5" value="5" />
+ <label for="stre5">5</label>
+ </fieldset>
+ </div>
+
+ <div class="increasingStrength">
+ <input type="checkbox" name="tizen-check1-1"
+ id="increasingStrength" />
+ <label for="increasingStrength">progressive effort</label>
+ </div>
+
+ <a data-role="button" data-icon="plus" id="addTimeRange" data-inline="false">add time</a>
+ <ul data-role="swipelist" id="availableTime"></ul>
+
+ </div>
+ </div>
+
+ <div data-role="footer" data-position="fixed">
+ <div data-role="tabbar" data-style="toolbar">
+ <ul>
+ <li><a href="#selectExercises">select workouts</a></li>
+ </ul>
+ </div>
+ </div>
+ </div>
+
+ <div data-role="page" id="selectExercises" data-add-back-btn="footer">
+ <div data-role="header" data-position="fixed">
+ <h2>ExercisePlanner &gt; select workouts</h2>
+ </div>
+
+ <div data-role="content">
+ <div class="optionsExercises">
+ <div>Select workouts</div>
+ <ul data-role="listview" id="exercises"></ul>
+ <a data-role="button" data-icon="plus" href="#customExercises">add custom workout</a>
+ </div>
+ </div>
+
+ <div data-role="footer" data-position="fixed"></div>
+ </div>
+
+ <div data-role="page" id="customExercises" data-add-back-btn="footer">
+ <div data-role="header" data-position="fixed">
+ <h1>ExercisePlanner &gt; add custom workout</h1>
+ </div>
+
+ <div data-role="content">
+ <div class="newExerciseName">
+ <div>Enter workout name:</div>
+ <input data-role="input" type="text" name="newExercise"
+ id="newExerciseName" maxlength="20"/>
+ <input data-role="button" id="btnNewExercise" data-inline="true" type="button"
+ value="add"/>
+ </div>
+ </div>
+
+ <div data-role="footer" data-position="fixed"></div>
+ </div>
+
+ <div data-role="page" id="rangesOfTimes" data-add-back-btn="footer">
+ <div data-role="header">
+ <h1>add time</h1>
+ </div>
+
+ <div data-role="content">
+ <div>
+ <ul class="times">
+ <li class="ui-li-3-2-2">Start: <span class="ui-li-text-main">
+ <input type="time" name="dateStart" id="startTime"
+ data-format="H" />
+ </span>hour
+ </li>
+ <li class="ui-li-3-2-2">Duration: <span class="ui-li-text-main">
+ <input type="time" name="duration" id="duration" data-format="H" />
+ </span> hours
+ </li>
+ </ul>
+
+ <fieldset data-role="controlgroup" class="selectPeriodType">
+ <legend>Choose a type of period:</legend>
+ <input type="radio" name="periodType" id="periodType1"
+ value="everyday" checked="checked" />
+ <label for="periodType1">everyday</label>
+
+ <input type="radio" name="periodType" id="periodType2"
+ value="weekend" />
+ <label for="periodType2">weekend</label>
+
+ <input type="radio" name="periodType" id="periodType3"
+ value="workday" />
+ <label for="periodType3">workday</label>
+ </fieldset>
+ <div class="enableOption">
+ <span>Enabled</span>
+ <select id="formEnablePeriod" data-role="slider">
+ <option value="off"></option>
+ <option value="on"></option>
+ </select>
+ </div>
+ </div>
+
+ <input data-role="button" data-inline="true" type="button"
+ id="updateTime" value="add" />
+ </div>
+
+ <div data-role="footer"></div>
+ </div>
+
+</body>
+</html>
diff --git a/js/GraphSchedule.js b/js/GraphSchedule.js
new file mode 100644
index 0000000..72df6f8
--- /dev/null
+++ b/js/GraphSchedule.js
@@ -0,0 +1,186 @@
+/*jslint devel: true*/
+/*global $ */
+/**
+ * Constructor
+ *
+ * @param {object} params
+ * @returns
+ */
+function GraphSchedule(params) {
+ "use strict";
+ this.init(params);
+}
+
+(function () {
+ "use strict";
+ GraphSchedule.prototype = {
+ template: '',
+ ui: null,
+ $flag: null,
+ $graph: null,
+ timeRanges: {
+ workday: [],
+ weekend: []
+ },
+ flags: []
+ };
+
+ GraphSchedule.prototype.createUI = function createUI() {
+ var $tmp = $('<div></div>');
+
+ $tmp.html(this.template);
+ this.$flag = $tmp.find('.flag');
+ this.$graph = $tmp.find('.GraphSchedule');
+ this.ui = this.$graph;
+
+ this.addCurrentTimeBar();
+ this.showFlags();
+
+ this.center();
+ };
+
+ GraphSchedule.prototype.center = function center() {
+ // set scroll position;
+ this.$graph[0].scrollLeft = 1000 * ((new Date().getHours() - 4) / 24);
+ };
+
+ GraphSchedule.prototype.refresh = function refresh() {
+ this.updateTimeRanges();
+ this.showFlags();
+ this.center();
+ };
+
+ GraphSchedule.prototype.onTemplateLoad = function onTemplateLoad() {
+ };
+
+ GraphSchedule.prototype.init = function init(params) {
+ var $loader = $('<div></div>');
+
+ if (params && params.onSuccess) {
+ this.onTemplateLoad = params.onSuccess;
+ }
+
+ this.flags = [];
+ $loader.load('templates/GraphSchedule.tmpl', null, function (data) {
+ this.template = data;
+ this.createUI();
+ this.onTemplateLoad();
+ }.bind(this));
+ };
+
+ /**
+ *
+ * @param {Array} times
+ * @returns
+ */
+ GraphSchedule.prototype.pushTimeOfFlags = function pushTimeOfFlags(times) {
+ var i, count;
+
+ // clear previous times;
+ this.flags = [];
+
+ if (times instanceof Array) {
+ count = times.length;
+ for (i = 0; i < count; i += 1) {
+ if (times[i] instanceof Date) {
+ this.flags.push({ time: times[i] });
+ } else {
+ throw {message: 'Bag argument at [' + i + '] element of Array. Expected {Date}'};
+ }
+ }
+ } else {
+ throw {message: 'Bad argument. Expected {Array} of {Date}'};
+ }
+ };
+
+ GraphSchedule.prototype.addCurrentTimeBar = function addCurrentTimeBar() {
+ // remove old time bar;
+ var $currentTimeBar = this.$graph.find('.currentTimeBar'),
+ currentTime = new Date(),
+ hours = currentTime.getHours();
+
+ if ($currentTimeBar.length === 0) {
+ // add new;
+ $currentTimeBar = $('<div class="currentTimeBar"></div>');
+ }
+
+ if (hours < 10) {
+ hours = '0' + hours;
+ }
+
+ this.$graph.find('.ranges .h' + hours).append($currentTimeBar);
+ $currentTimeBar.css('left', 100 * currentTime.getMinutes() / 60 + '%');
+
+ setTimeout(this.addCurrentTimeBar.bind(this), 5 * 60 * 1000);
+ };
+
+ GraphSchedule.prototype.addFlag = function addFlag(newFlag) {
+ var $flagClone, hours = newFlag.time.getHours();
+ if (hours < 10) {
+ hours = '0' + hours;
+ }
+ $flagClone = this.$flag.clone();
+ this.$graph.find('.grid td:contains(' + hours + ')').append($flagClone);
+ $flagClone.css('left', 100 * newFlag.time.getMinutes() / 60 + '%');
+ };
+
+ GraphSchedule.prototype.showFlags = function showFlags() {
+ var i, len = this.flags.length;
+ // remove old flags;
+ this.removeFlags();
+
+ // add all flags to view;
+ for (i = 0; i < len; i += 1) {
+ this.addFlag(this.flags[i]);
+ }
+
+ this.center();
+ };
+
+ GraphSchedule.prototype.removeFlags = function removeFlags() {
+ this.$graph.find('.flag').remove();
+ };
+
+ GraphSchedule.prototype.setTimeRanges = function setTimeRanges(ranges) {
+ this.timeRanges = ranges;
+ };
+
+ GraphSchedule.prototype.setVisibleWeekend = function (bool) {
+ var row = this.ui.find('.rangesWeekend');
+ return bool ? row.show() : row.hide();
+ };
+
+ GraphSchedule.prototype.setVisibleWorkdays = function (bool) {
+ var row = this.ui.find('.ranges');
+ return bool ? row.show() : row.hide();
+ };
+
+ /**
+ * Update time ranges on graph
+ * @param ranges {array} array of boolen, keys are hours, example: [false, false, false, true, true]
+ */
+ GraphSchedule.prototype.updateTimeRanges = function updateTimeRanges() {
+ var i, len, hours;
+
+ this.$graph.find('.th').removeClass('th');
+
+ // workdays;
+ hours = this.timeRanges.workday;
+ len = hours.length;
+ for (i = 0; i < len; i += 1) {
+ if (hours[i]) {
+ this.$graph.find('.ranges .h' + ((i < 10) ? '0' + i : i)).addClass('th');
+ }
+ }
+
+ //weekends;
+ hours = this.timeRanges.weekend;
+ len = hours.length;
+ for (i = 0; i < len; i += 1) {
+ if (hours[i]) {
+ this.$graph.find('.rangesWeekend .h' + ((i < 10) ? '0' + i : i)).addClass('th');
+ }
+ }
+ };
+
+}());
diff --git a/js/UI.js b/js/UI.js
new file mode 100644
index 0000000..7d3d334
--- /dev/null
+++ b/js/UI.js
@@ -0,0 +1,415 @@
+/*jslint nomen: true*/
+/*global $, GraphSchedule, confirm, range, history, setTimeout */
+function UI() {
+ "use strict";
+}
+
+(function () {
+ "use strict";
+ UI.prototype = {
+ sentence : {
+ 'lazy' : {
+ text : 'He does not seem to me to be a free man who does not sometimes do nothing.',
+ signature : 'Marcus T. Cicero'
+ },
+ 'run' : {
+ text : 'A journey of a thousand miles begins with a single step.',
+ signature : 'Lao-tzu'
+ }
+ },
+ graphSchedule : null,
+ app : null
+ };
+
+ UI.prototype.fillExercises = function (exercisesData) {
+ var i, len, self = this;
+
+ $('#exercises').replaceWith(
+ $('<ul data-role="listview" id="exercises"></ul>')
+ );
+ for (i = 0, len = exercisesData.length; i < len; i += 1) {
+ $('#exercises').append(
+ $(this.getExercisesTemplate(exercisesData[i], i))
+ );
+ }
+ $('#exercises').listview();
+ $('#exercises :jqmData(role="slider")').slider();
+ $('#exercises li').on('tap', function () {
+ var $toggle = $(this).find(':jqmData(role="slider")');
+ $toggle.val(($toggle.val() === 'off') ? 'on' : 'off');
+ $toggle.slider('refresh');
+ self.app.saveExercises(self.getExercisesList());
+ });
+ $('#exercises :jqmData(role="slider")').on('change', function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ self.app.saveExercises(self.getExercisesList());
+ });
+ };
+
+ UI.prototype.fillTimesRanges = function (timesData) {
+ var self = this, len, i;
+
+ $('#availableTime').replaceWith(
+ $('<ul data-role="listview" id="availableTime"></ul>')
+ );
+ for (i = 0, len = timesData.length; i < len; i += 1) {
+ $('#availableTime')
+ .append($(this.getAvailableTimeTemplate(timesData[i])));
+ }
+ $('#availableTime').trigger('create');
+ $('#availableTime').listview().listview('refresh');
+ $('#availableTime :jqmData(name=edit)').on('tap', function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ self.editTimeRange($(this).data('val'));
+ });
+ $('#availableTime :jqmData(name=disable)').on('tap', function (e) {
+ e.stopPropagation();
+ self.app.disableTimeRange($(this).data('val'));
+ });
+ $('#availableTime :jqmData(name=delete)').on('tap', function (e) {
+ e.stopPropagation();
+ if (confirm('Are you sure?')) {
+ self.app.deleteTimeRange($(this).data('val'));
+ }
+ });
+ };
+
+ UI.prototype.fillTimeRangeForm = function fillTimeRangeForm(timeRange) {
+ var tmpData = new Date();
+ // Filling form;
+ $('#startTime').attr('data-val',
+ new Date(tmpData.setHours(timeRange.start)));
+ $('#duration').attr('data-val', timeRange.duration);
+
+ if ($('#startTime').data('datetimepicker')) {
+ $('#startTime').data('datetimepicker').options.date
+ .setHours(timeRange.start);
+ $('#startTime').data('datetimepicker').ui
+ .find('.ui-datefield-hour').html(
+ (timeRange.start < 10) ? '0' + timeRange.start : timeRange.start
+ );
+ }
+ if ($('#duration').data('datetimepicker')) {
+ $('#duration').data('datetimepicker').options.date
+ .setHours(timeRange.duration);
+ $('#duration').data('datetimepicker').ui.find('.ui-datefield-hour')
+ .html(
+ (timeRange.duration < 10) ? '0'
+ + timeRange.duration : timeRange.duration
+ );
+ $('#duration').data('datetimepicker')._populateDataSelector = function (field, pat) {
+ var result = $.tizen.datetimepicker.prototype._populateDataSelector
+ .call(this, field, pat);
+ result.values = range(1, 20);
+ result.data = range(1, 20);
+ result.current -= 1;
+ return result;
+ };
+ }
+
+ $('#formEnablePeriod')[0].value = timeRange.enabled ? 'on' : 'off';
+ $('#formEnablePeriod').slider('refresh');
+ };
+
+ UI.prototype.editTimeRange = function (nr, event) {
+ if (event && typeof event.stopPropagation === 'function') {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ if (this.app.editTimeRange(nr) >= 0) {
+ $('#updateTime').val('modify');
+ } else {
+ $('#updateTime').val('add');
+ }
+
+ // change page to form;
+ $.mobile.changePage("#rangesOfTimes");
+ };
+
+ UI.prototype.getExercisesList = function () {
+ return $('#exercises :jqmData(role=slider)').map(function (o, v) {
+ return ({
+ index : $(v).attr('data-index'),
+ checked : ($(v).val() === 'on') ? true : false
+ });
+ });
+ };
+
+ UI.prototype.addExercise = function () {
+ if ($("#newExerciseName").val()) {
+ if (this.app.addExercise($("#newExerciseName").val())) {
+ $("#newExerciseName").trigger('blur');
+ setTimeout(history.back.bind(history), 50);
+ }
+ } else {
+ this.showErrors([ {
+ name : 'Name of exercise is not correct.',
+ code : 100
+ } ]);
+ }
+ };
+
+ UI.prototype.configToUI = function () {
+ };
+
+ UI.prototype.updateMainUI = function () {
+ this.setStatusRun(this.app.config.trainingEnabled);
+ this.graphSchedule.setTimeRanges(this.app.periodsWeekToBoolArray());
+ };
+
+ UI.prototype.getTimeRangeFromForm = function () {
+ return {
+ start : $('#startTime').data('datetimepicker').options.date
+ .getHours(),
+ duration : $('#duration').data('datetimepicker').options.date
+ .getHours(),
+ stop : $('#startTime').data('datetimepicker').options.date
+ .getHours()
+ + $('#duration').data('datetimepicker').options.date
+ .getHours(),
+ style : $('.selectPeriodType :radio:checked').val(),
+ enabled : ($('#formEnablePeriod')[0].value === 'on' ? true : false)
+ };
+ };
+
+ UI.prototype.editTimeRangeAction = function (nr) {
+ if (this.app.saveTimeRange(nr, this.getTimeRangeFromForm())) {
+ history.back();
+ } else {
+ throw ({
+ message : 'Time start and stop is not present.',
+ code : 1
+ });
+ }
+ };
+
+ UI.prototype.showNoticeInMonitor = function (notice, alarm) {
+ $('#communicate').html(notice);
+ $('#communicate').toggleClass('onAlert', alarm);
+ };
+
+ UI.prototype.changeButtonAddTime = function (text) {
+ $('#addTime').html(text);
+ };
+
+ UI.prototype.showErrors = function (errors) {
+ var i; // count;
+ for (i = 0; i < errors.length; i += 1) {
+ alert(errors[i].name);
+ }
+ };
+
+ UI.prototype.showAlarmInMonitor = function (data) {
+ var notice = '';
+ function formatNN(val) {
+ return (val < 10) ? ('0' + val) : val;
+ }
+
+ if (data && data.alarm && this.app.config.trainingEnabled) {
+ this.app.currentAlarm = this.app.findCurrentAlarm();
+ if (this.app.currentAlarm.length > 0) {
+ notice += 'Go... go... go...!<br>' + data.exerciseName + ' x '
+ + data.numberOfTimes;
+ } else {
+ notice += 'Next exercises set at: '
+ + formatNN(data.alarm.getHours()) + ':'
+ + formatNN(data.alarm.getMinutes()) + '<br>'
+ + data.exerciseName + ' x ' + data.numberOfTimes;
+ }
+ } else {
+ notice += '<br/>You have no workouts scheduled for now.<br/>';
+ }
+ this.showNoticeInMonitor(notice, false);
+ };
+
+ UI.prototype.getSentence = function UI_getSentence(type) {
+ return (this.sentence[type] || {
+ text : 'No sentence',
+ signature : 'anonymous'
+ });
+ };
+
+ UI.prototype.setSentence = function (type) {
+ var sentence = this.getSentence(type);
+ $('#sentence').html('"' + sentence.text + '"');
+ $('#signature').html('- ' + sentence.signature);
+ };
+
+ UI.prototype.showWaitOk = function () {
+ $('#mainControl').hide();
+ $('#one .ui-btn-back').hide();
+
+ $('#onAlertControl').tabbar();
+ $('#onAlertControl').show();
+ $('#onAlertControl').css('width', '');
+ };
+
+ UI.prototype.setStatusRun = function (bool) {
+ if (bool) {
+ // icon;
+ $('#status').removeClass('lazy').addClass('run');
+ // sentence;
+ this.setSentence('run');
+ // button in control bar;
+ $('#startStop .ui-btn-text').html('stop training');
+ } else {
+ $('#status').removeClass('run').addClass('lazy');
+ this.setSentence('lazy');
+ $('#communicate').html('');
+ $('#startStop .ui-btn-text').html('start training');
+ }
+ };
+
+ UI.prototype.bindEvents = function bindEvents() {
+ var self = this;
+
+ // bind events;
+ $('#one .ui-btn-back').on('tap', this.app.exit.bind(this.app));
+
+ $('#ok').on('tap', self.app.ok.bind(self.app));
+ $('#wait').on('tap', self.app.wait.bind(self.app));
+ $('#todayOffAll').on('tap', self.app.todayOffAll.bind(self.app));
+
+ $('#startStop').on('tap', function () {
+ self.app.appStartStop();
+ });
+
+ $('#one').on(
+ 'pageshow',
+ function (page, options) {
+ if (self.graphSchedule.ui) {
+ $('#one .schedule').append(self.graphSchedule.ui);
+ self.app.updateGraph();
+ self.graphSchedule.refresh();
+ self.graphSchedule.setVisibleWeekend(!self.app
+ .todayIsWorkday());
+ self.graphSchedule.setVisibleWorkdays(self.app
+ .todayIsWorkday());
+ }
+ $('#one .schedule').on('touchstart', function (ev) {
+ ev.stopPropagation();
+ });
+
+ // workaround for scroll lock;
+ $.mobile.activePage.css('position', 'fixed');
+ }
+ );
+
+ $('#two').on('pageshow', function (page, options) {
+
+ //FIXME (two scrollbar workaround)
+ setTimeout(
+ function () {
+ var newHeight = $('#two').find('[data-role="content"]').prop('style').height;
+ $('#two').css({'min-height': newHeight, 'height': newHeight});
+ },
+ 0
+ );
+
+ if (self.graphSchedule.ui) {
+ $('#two .scheduleOptions').append(self.graphSchedule.ui);
+ self.graphSchedule.refresh();
+ self.graphSchedule.setVisibleWeekend(true);
+ self.graphSchedule.setVisibleWorkdays(true);
+ }
+ });
+
+ $('#two').on('pageinit', function (page, options) {
+ $('.ui-radio input', $('#frequency')).change(function (ev) {
+ self.app.setFrequency(this.value);
+ self.updateMainUI();
+ });
+
+ $('.ui-radio input', $('#strength')).change(function (ev) {
+ self.app.setStrength(this.value);
+ self.updateMainUI();
+ });
+
+ $('#frequency')[0].select(self.app.config.frequency);
+ $('#strength')[0].select(self.app.config.strength);
+
+ $('#two .scheduleOptions').append(self.graphSchedule.ui);
+ $('#two .scheduleOptions').on('touchstart', function (ev) {
+ ev.stopPropagation();
+ });
+
+ $('#workdaysType').on('tap', function (ev) {
+ self.app.changeTypeOfPeriods('workday');
+ });
+
+ $('#weekendType').on('tap', function (ev) {
+ self.app.changeTypeOfPeriods('weekend');
+ });
+
+ $('#addTimeRange').on('tap', self.editTimeRange.bind(self, -1));
+
+ self.app.updateTimesRanges();
+ self.configToUI();
+ });
+
+ $('#selectExercises').on('pageinit', function (page, options) {
+ self.app.updateExercises();
+ self.configToUI();
+ });
+
+ $('#customExercises').on('pageinit', function (page, options) {
+ $('#btnNewExercise').on('click', self.addExercise.bind(self));
+ });
+
+ $('#customExercises').on('pageshow', function (page, options) {
+ $('#newExerciseName').val('');
+ $('#newExerciseName').trigger('focus');
+ });
+
+ $('#rangesOfTimes').on('pageinit', function (page, options) {
+ $("#updateTime").on("tap", function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ self.editTimeRangeAction(self.app.currentEditingTimePeriodId);
+ });
+ });
+
+ $('#rangesOfTimes').on('pageshow', function (page, options) {
+ $('#updateTime').data('button').refresh();
+ self.fillTimeRangeForm(self.app.currentEditingTimePeriod);
+ });
+
+ $('#increasingStrength').on('change', function () {
+ self.app.config.increasingStrength = this.checked;
+ self.app.saveConfig();
+
+ self.configToUI();
+ });
+ };
+
+ UI.prototype.onGraphSchedule = function onGraphSchedule(onInitEnd) {
+ this.updateMainUI();
+
+ $('#one .schedule').append(this.graphSchedule.ui);
+ this.app.updateGraph();
+ this.graphSchedule.refresh();
+ this.graphSchedule.setVisibleWeekend(!this.app.todayIsWorkday());
+ this.graphSchedule.setVisibleWorkdays(this.app.todayIsWorkday());
+
+ if (typeof onInitEnd === 'function') {
+ onInitEnd();
+ }
+ };
+
+ UI.prototype.initialize = function (onInitEnd) {
+ $.mobile.tizen.disableSelection(document);
+
+ this.bindEvents();
+
+ $('html').css('font-size', '');
+ $('body').css('font-size', '');
+
+ this.graphSchedule = new GraphSchedule({
+ onSuccess : this.onGraphSchedule.bind(this, onInitEnd)
+ });
+ };
+
+}());
diff --git a/js/UI.simpleTemplate.js b/js/UI.simpleTemplate.js
new file mode 100644
index 0000000..8ce5874
--- /dev/null
+++ b/js/UI.simpleTemplate.js
@@ -0,0 +1,25 @@
+/*global UI */
+(function () {
+ "use strict";
+ UI.prototype.getAvailableTimeTemplate = function getAvailableTimeTemplate(obj) {
+ return '<li>' + '<div class="timeRangeLabel">start: ' + obj.start
+ + ' (duration: ' + obj.duration + 'h) ' + obj.style + '</div>'
+ + '<div class="timeRangeButtons">'
+ + '<div data-role="button" data-inline="true" data-name="edit" data-val="' + obj.nr + '">Edit</div>'
+ + '<div data-role="button" data-inline="true" data-name="disable" data-val="' + obj.nr + '">'
+ + ((obj.enabled) ? 'Dis' : 'En') + 'able</div>'
+ + '<div data-role="button" data-inline="true" data-name="delete" data-val="' + obj.nr + '">Delete</div>'
+ + '<div class="activeStatus' + ((obj.enabled) ? '' : ' activeStatusDisable') + '" data-inline="true">' + ((obj.enabled) ? 'En' : 'Dis') + 'abled</div>'
+ + '</div>' + '</li>';
+ };
+
+ UI.prototype.getExercisesTemplate = function (obj, i) {
+ return '<li>'
+ + '<span>' + obj.name + '</span>'
+ + '<select data-role="slider" data-index="' + i + '">'
+ + '<option value="off" ' + (obj.enabled ? '' : 'selected="selected"') + '></option>'
+ + '<option value="on" ' + (obj.enabled ? 'selected="selected"' : '') + '></option>'
+ + '</select>' + '</li>';
+ };
+}());
+
diff --git a/js/app.alarms.js b/js/app.alarms.js
new file mode 100644
index 0000000..3c6c17e
--- /dev/null
+++ b/js/app.alarms.js
@@ -0,0 +1,55 @@
+/*jslint devel:true*/
+/*global ExercisePlanner:false, tizen:false*/
+/**
+ * Methods for add / remove alarms by API;
+ */
+(function () {
+ "use strict";
+ /**
+ * Wrapper on remove all alarms joined with app
+ */
+ ExercisePlanner.prototype.removeAllAlarms = function () {
+ tizen.alarm.removeAll();
+ };
+
+ ExercisePlanner.prototype.WORKDAYS = ["MO", "TU", "WE", "TH", "FR"];
+ ExercisePlanner.prototype.WEEKEND = ["SA", "SU"];
+
+ /**
+ * Add alarms from Array
+ *
+ * @param tabOfAlarm
+ * @param defOfPeriod
+ */
+ ExercisePlanner.prototype.addAlarmFromArray = function addAlarmFromArray(tabOfAlarm, defOfPeriod) {
+ var i, len = tabOfAlarm.length, alarm;
+
+ for (i = 0; i < len; i += 1) {
+ alarm = new tizen.AlarmAbsolute(tabOfAlarm[i], defOfPeriod);
+ try {
+ tizen.alarm.add(alarm, this.selfId);
+ } catch (e) {
+ console.error(e.message);
+ }
+ }
+ };
+
+ /**
+ * Add alarms to API DataBase
+ *
+ * @param {object} alarms
+ */
+ ExercisePlanner.prototype.addAlarmsAllWeek = function addAlarmsAllWeek(alarms) {
+ if (alarms.everyday.length > 0) {
+ this.addAlarmFromArray(alarms.everyday, tizen.alarm.PERIOD_DAY);
+ }
+ if (alarms.workday.length > 0) {
+ this.addAlarmFromArray(alarms.workday, this.WORKDAYS);
+ }
+ if (alarms.weekend.length > 0) {
+ this.addAlarmFromArray(alarms.weekend, this.WEEKEND);
+ }
+ };
+
+}());
+
diff --git a/js/app.alarmsGenerating.js b/js/app.alarmsGenerating.js
new file mode 100644
index 0000000..37ae7b0
--- /dev/null
+++ b/js/app.alarmsGenerating.js
@@ -0,0 +1,153 @@
+/*jslint devel: true*/
+/*global ExercisePlanner: true, ONE_DAY:false, TIME_OF_SLEEP:false */
+/**
+ *
+ */
+(function () {
+ "use strict";
+ /**
+ *
+ *
+ * @param availableTime
+ * @param typeOfPeriod
+ * @returns {object}
+ */
+ ExercisePlanner.prototype.calculateFactor = function calculateFactors(availableTime, typeOfPeriod) {
+ var factor,
+ result = {
+ count: 0,
+ period: 0
+ };
+
+ if (availableTime === 0) {
+ return result;
+ }
+
+ factor = availableTime / (this.ONE_DAY - this.TIME_OF_SLEEP);
+ result.proportionalFrequency = this.getNumberOfWorkoutsByFrequency(this.config.frequency[typeOfPeriod]) * factor;
+
+ if ((Math.round(result.proportionalFrequency) - 1) > 0) {
+ result.count = (Math.round(result.proportionalFrequency));
+ result.period = availableTime / (result.count - 1);
+ } else {
+ result.count = 1;
+ result.period = 0;
+ }
+
+ return result;
+ };
+
+ /**
+ *
+ *
+ * @param availableTime
+ * @returns {object}
+ */
+ ExercisePlanner.prototype.calculateFactors = function calculateFactors(availableTime) {
+ return {
+ weekend: this.calculateFactor(availableTime.weekend, 'weekend'),
+ workday: this.calculateFactor(availableTime.workday, 'workday'),
+ everyday: this.calculateFactor(availableTime.everyday, 'workday')
+ };
+ };
+
+ /**
+ *
+ *
+ * @param factor
+ * @param tmpPeriods
+ * @returns {Array}
+ */
+ ExercisePlanner.prototype.generateAlarmsByFactor = function (factor, tmpPeriods) {
+ var i, numberOfPeriods = tmpPeriods.length,
+ period,
+ begin,
+ end = 0,
+ tableOfAlarms = [],
+ optimalTime,
+ deltaTime = 0,
+ dayTime = this.beginDate || new Date();
+
+ if (numberOfPeriods === 0) {
+ return [];
+ }
+
+ begin = tmpPeriods[0].start;
+
+ for (i = 0; i < numberOfPeriods; i += 1) {
+ if (tmpPeriods[i].stop > end) {
+ end = tmpPeriods[i].stop;
+ }
+ }
+
+ dayTime.setSeconds(0);
+
+ if (factor.count === 1) {
+ // One alarm per day, default placed in middle of available time;
+ optimalTime = this.findNearestTimeRange((end + begin) / 2, tmpPeriods);
+
+ dayTime.setHours(parseInt(optimalTime.optimalHour, 10));
+ dayTime.setMinutes(60 * (optimalTime.optimalHour - parseInt(optimalTime.optimalHour, 10)));
+ tableOfAlarms.push(new Date(dayTime.getTime()));
+ } else {
+ // set time for begin;
+ dayTime.setHours(tmpPeriods[0].start);
+ dayTime.setMinutes(0);
+ tableOfAlarms.push(new Date(dayTime.getTime()));
+
+ // set time for next hop;
+ for (i = 0; i < numberOfPeriods; i += 1) {
+ period = tmpPeriods[i];
+ deltaTime += period.duration;
+ // if available period is too small, then accumulate time
+ // and continue to next period;
+ while (deltaTime >= factor.period * 0.999) {
+ deltaTime -= factor.period;
+
+ dayTime.setHours(parseInt(period.stop - deltaTime, 10));
+ dayTime.setMinutes(60 * (period.stop - deltaTime - parseInt(period.stop - deltaTime, 10)));
+
+ tableOfAlarms.push(new Date(dayTime.getTime()));
+ }
+ }
+ }
+
+ return tableOfAlarms;
+ };
+
+ /**
+ * Generate table of alarms => this.alarms
+ * @param {Date} customDate;
+ */
+ ExercisePlanner.prototype.generateAlarms = function () {
+ var factors,
+ alarms = this.alarms,
+ periodsWeek = {
+ everyday: [],
+ workday: [],
+ weekend: []
+ };
+
+ // some periods may overlap, must be merged
+ periodsWeek = this.mergePeriods();
+ // store in cache for later reuse
+ this.cache.periodsWeek = periodsWeek;
+
+ // factors to correct how often may workouts per day
+ factors = this.calculateFactors(this.getSummaryAvailableTime());
+
+ // Set new alarms;
+ if (periodsWeek.everyday.length > 0) {
+ alarms.everyday = this.generateAlarmsByFactor(factors.everyday, periodsWeek.everyday);
+ } else {
+ alarms.workday = this.generateAlarmsByFactor(factors.workday, periodsWeek.workday);
+ alarms.weekend = this.generateAlarmsByFactor(factors.weekend, periodsWeek.weekend);
+ }
+
+ // if trainig is in run then resinstall alarm imediately
+ if (this.config.trainingEnabled) {
+ this.startAlarms();
+ }
+ };
+}());
+
diff --git a/js/app.js b/js/app.js
new file mode 100644
index 0000000..3ab9bd8
--- /dev/null
+++ b/js/app.js
@@ -0,0 +1,450 @@
+/*jslint devel:true*/
+/*global tizen, $, app, localStorage, Audio, document, unlockScreen, UI */
+var ExercisePlanner = function () {
+ "use strict";
+};
+
+(function () {
+ "use strict";
+
+ ExercisePlanner.prototype = {
+ /**
+ * Definition of time for sleep
+ */
+ TIME_OF_SLEEP: 8,
+
+ /**
+ * Definition one day in hours
+ */
+ ONE_DAY: 24,
+
+ /**
+ * Stored time of application start
+ */
+ applicationStartTime: new Date(),
+
+ /**
+ * Cofiguration data will saved for next launch;
+ * There are default values after install;
+ */
+ config: {
+
+ frequency: {
+ workday: 3, // 6 for test
+ weekend: 3
+ },
+
+ strength: {
+ workday: 1,
+ weekend: 1
+ },
+
+ /**
+ * List of workouts;
+ * - timeRanges:style [ everyday, weekend, workday ]; ( workday : mon-fri )
+ */
+ exercises: [{
+ name: 'bends',
+ enabled: true
+ }, {
+ name: 'squats',
+ enabled: true
+ }, {
+ name: 'push-ups',
+ enabled: false
+ }],
+
+ // deprecated for this version;
+ increasingStrength: true,
+
+ /**
+ * Default time ranges
+ */
+ timesRanges: [{
+ nr: 0,
+ start: 8,
+ stop: 16,
+ duration: 8,
+ enabled: true,
+ style: 'everyday'
+ }, {
+ nr: 1,
+ start: 18,
+ stop: 22,
+ duration: 4,
+ enabled: true,
+ style: 'weekend'
+ }],
+
+ nearestExercise: -1,
+
+ count: 0,
+
+ trainingEnabled: false
+ },
+ alarms: {
+ everyday: [],
+ workday: [],
+ weekend: []
+ },
+
+ /**
+ * Used for update GraphSchedule;
+ * [ workday / weekend ]
+ */
+ currentTypeOfPeriods: 'workday',
+ /**
+ * Use on form to edit time period;
+ */
+ currentEditingTimePeriod: null,
+ currentEditingTimePeriodId: -1,
+
+ /**
+ * Date when alarm will start generating
+ */
+ beginDate: null,
+
+ /**
+ * use store temporary data for alarms;
+ */
+ cache: {},
+
+ /**
+ * HTML5 audio element for play audio when alarm is called
+ */
+ audioOfAlert: null,
+
+ /**
+ * Instance of User Interface
+ */
+ ui: null
+ };
+
+ /**
+ * Load configuration of application
+ * (use localStorage)
+ */
+ ExercisePlanner.prototype.loadConfig = function () {
+ var configStr = localStorage.getItem('config');
+ if (configStr) {
+ this.config = JSON.parse(configStr);
+ } else {
+ this.removeAllAlarms();
+ this.sortTimeRanges();
+ }
+ };
+
+ /**
+ * Save configuration of application
+ * (use localStorage)
+ */
+ ExercisePlanner.prototype.saveConfig = function () {
+ localStorage.setItem('config', JSON.stringify(this.config));
+ };
+
+ ExercisePlanner.prototype.stopTraining = function () {
+ this.removeAllAlarms();
+ this.ui.setStatusRun(this.config.trainingEnabled);
+ };
+
+ ExercisePlanner.prototype.startTraining = function () {
+ this.ui.setStatusRun(this.config.trainingEnabled);
+ this.startAlarms();
+ };
+
+ /**
+ * Toggle start/stop alarms for workouts
+ */
+ ExercisePlanner.prototype.appStartStop = function () {
+ this.config.trainingEnabled = !this.config.trainingEnabled;
+ if (this.config.trainingEnabled) {
+ this.startTraining();
+ } else {
+ this.stopTraining();
+ }
+ this.saveConfig();
+ };
+
+ /**
+ * Closing application with the configuration data saving
+ */
+ ExercisePlanner.prototype.exit = function () {
+ this.saveConfig();
+ this.stopMusic();
+ tizen.application.getCurrentApplication().exit();
+ };
+
+ /**
+ * Sets frequency value and calculates new alarms
+ * @param value
+ */
+ ExercisePlanner.prototype.setFrequency = function (value) {
+ this.config.frequency[this.config.currentTypeOfPeriods] = parseInt(value, 10);
+
+ this.saveConfig();
+ this.generateAlarms();
+ this.updateGraph(this.config.currentTypeOfPeriods);
+ this.showNextAlarm();
+ };
+
+ /**
+ * Set Strength value
+ * @param value
+ */
+ ExercisePlanner.prototype.setStrength = function (value) {
+ this.config.strength[this.config.currentTypeOfPeriods] = parseInt(value, 10);
+ this.saveConfig();
+ };
+
+ /**
+ * Sending array of exercises to UI for update
+ */
+ ExercisePlanner.prototype.updateExercises = function () {
+ this.ui.fillExercises(this.config.exercises);
+ };
+
+ /**
+ * Sending array of time ranges to UI for update
+ * & update graph schedule
+ */
+ ExercisePlanner.prototype.updateTimesRanges = function () {
+
+ this.ui.fillTimesRanges(this.config.timesRanges);
+ this.ui.graphSchedule.updateTimeRanges();
+ };
+
+ /**
+ * Store exercises in config (and save)
+ * @param newData
+ */
+ ExercisePlanner.prototype.saveExercises = function (newData) {
+ var i, l;
+
+ if (newData) {
+ for (i = 0, l = newData.length; i < l; i += 1) {
+ this.config.exercises[i].enabled = newData[i].checked;
+ }
+ this.generateNearestExercise();
+ this.saveConfig();
+ }
+ };
+
+ /**
+ * When will earliest workout
+ * and show in UI
+ */
+ ExercisePlanner.prototype.showNextAlarm = function showNextAlarm() {
+ var alarms,
+ currentDate = new Date();
+
+ if (this.alarms.everyday.length > 0) {
+ alarms = this.alarms.everyday;
+ } else {
+ alarms = (this.todayIsWorkday()) ? this.alarms.workday : this.alarms.weekend;
+ }
+
+ alarms = alarms.filter(function (item) {
+ return (item.getTime() > currentDate.getTime());
+ }).sort(function (a, b) {
+ return a.date - b.date;
+ });
+
+ if (this.config.nearestExercise > -1) {
+ this.ui.showAlarmInMonitor({
+ alarm: alarms[0],
+ exerciseName: this.config.exercises[this.config.nearestExercise].name,
+ numberOfTimes: this.getStrength(this.config.strength.workday, this.config.count)
+ });
+
+ this.config.count += 1;
+ }
+ this.saveConfig();
+ };
+
+ /**
+ * Change type of periods [workday/weekend] and update graph
+ * @param type
+ */
+ ExercisePlanner.prototype.changeTypeOfPeriods = function changeTypeOfPeriods(type) {
+ if (this.config.currentTypeOfPeriods !== type) {
+ this.config.currentTypeOfPeriods = type;
+ this.updateGraph(this.config.currentTypeOfPeriods);
+ }
+ };
+
+ /**
+ * Check new exercise name for duplication in existings;
+ * @param name
+ * @returns
+ */
+ ExercisePlanner.prototype.checkExerciseName = function (name) {
+ var i, l;
+
+ if (name) {
+ for (i = 0, l = this.config.exercises.length; i < l; i += 1) {
+ if (this.config.exercises[i].name === name) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ return undefined;
+ };
+
+ /**
+ * Add new exercise sent from UI
+ * @param name
+ * @returns {Boolean}
+ */
+ ExercisePlanner.prototype.addExercise = function (name) {
+ if (this.checkExerciseName(name) < 0) {
+ this.config.exercises.push({
+ name: name,
+ enabled: false
+ });
+ this.saveConfig();
+ this.ui.fillExercises(this.config.exercises);
+ return true;
+ }
+ this.ui.showErrors([{name: 'Element exists!'}]);
+ return false;
+ };
+
+ /**
+ * Get number of workouts by frequency
+ * @param value
+ * @returns {number}
+ */
+ ExercisePlanner.prototype.getNumberOfWorkoutsByFrequency = function getNumberOfWorkoutsByFrequency(value) {
+ var iMap = [1, 2, 4, 8, 16, 24, 150],
+ // -- times per 24h; proportion to set periods of available time;
+ numberOfWorkouts = iMap[value];
+
+ return numberOfWorkouts || 2;
+ };
+
+ /**
+ * Get number of exercises in workout by strength value and optional ;
+ * @param value
+ * @param count
+ * @returns {number}
+ */
+ ExercisePlanner.prototype.getStrength = function strengthMap(value, count) {
+ var sMap = [1, 1, 2, 4, 10, 20],
+ base = sMap[value] || 2;
+
+ count = count || 1;
+ return Math.round(base * (count / 10 + 1));
+ };
+
+ /**
+ * Generate name of exercise for nearest workout
+ */
+ ExercisePlanner.prototype.generateNearestExercise = function () {
+ var tmp = this.config.exercises.filter(function (item) {
+ return item.enabled;
+ });
+ this.config.nearestExercise = parseInt(Math.random() * tmp.length, 10);
+ };
+
+
+ /**
+ * If user want change work days this method will changing;
+ * @returns {Boolean}
+ */
+ ExercisePlanner.prototype.todayIsWorkday = function todayIsWorkday() {
+ var day = (new Date()).getDay();
+ return (day >= 1 && day <= 5);
+ };
+
+ /**
+ * Activate alarms in API.
+ */
+ ExercisePlanner.prototype.startAlarms = function startAlarms() {
+ // clear old alarms;
+ this.removeAllAlarms();
+
+ // add new alarms
+ this.addAlarmsAllWeek(this.alarms);
+
+ this.generateNearestExercise();
+ this.showNextAlarm();
+ };
+
+ /**
+ * Update Graph object
+ * @param {String} typeOfPeriods ['workday'|'weekend']
+ */
+ ExercisePlanner.prototype.updateGraph = function updateGraph(typeOfPeriods) {
+ var alarms;
+ if (!this.ui.graphSchedule) {
+ throw {
+ message: 'graph schedule not exists.'
+ };
+ }
+
+ typeOfPeriods = typeOfPeriods || ((this.todayIsWorkday()) ? 'workday' : 'weekend');
+
+ if (typeOfPeriods === 'workday') {
+ alarms = this.alarms.workday;
+ } else {
+ alarms = this.alarms.weekend;
+ }
+ if (alarms.length === 0) {
+ alarms = this.alarms.everyday;
+ }
+ this.ui.graphSchedule.setTimeRanges(this.periodsWeekToBoolArray());
+ this.ui.graphSchedule.pushTimeOfFlags(alarms);
+ this.ui.graphSchedule.showFlags();
+ };
+
+ /**
+ * Callback function on visibility change;
+ */
+ ExercisePlanner.prototype.onVisibilityChange = function () {
+
+ switch (document.webkitVisibilityState) {
+ case 'visible':
+ this.applicationStartTime = new Date();
+ this.currentAlarm = this.findCurrentAlarm();
+ if (this.currentAlarm.length > 0) {
+ this.ui.showWaitOk();
+ this.startMusic();
+ }
+ break;
+ }
+ };
+
+ /**
+ * Turn off all alarms today
+ */
+ ExercisePlanner.prototype.todayOffAll = function todayOffAll() {
+ // set begin date to tomorrow;
+ this.beginDate = new Date();
+ this.beginDate.setDate(this.beginDate.getDate() + 1);
+ // recreate alarms;
+ this.generateAlarms();
+ this.exit();
+ };
+
+ // Initialize function
+ ExercisePlanner.prototype.init = function init() {
+ var onUiInitialize = function onUiInitialize() {
+ // register watcher on visibility change;
+ document.addEventListener('webkitvisibilitychange', this.onVisibilityChange.bind(this));
+ this.showNextAlarm();
+ this.onVisibilityChange();
+ }.bind(this);
+
+ this.selfId = tizen.application.getAppContext().appId;
+ this.ui = new UI();
+ this.ui.app = this;
+
+ this.loadConfig();
+ this.config.currentTypeOfPeriods = (this.todayIsWorkday()) ? 'workday' : 'weekend';
+
+ this.generateAlarms();
+
+ this.ui.initialize(onUiInitialize);
+ };
+}());
diff --git a/js/app.onAlarm.js b/js/app.onAlarm.js
new file mode 100644
index 0000000..14f734d
--- /dev/null
+++ b/js/app.onAlarm.js
@@ -0,0 +1,66 @@
+/*jslint devel:true*/
+/*global ExercisePlanner:false, tizen:false, Audio:false*/
+/**
+ * These method are using when alarm is call.
+ */
+(function () {
+ "use strict";
+ ExercisePlanner.prototype.findCurrentAlarm = function () {
+ var currentTimeInMinutes = parseInt(this.applicationStartTime.getTime() / 1000, 10),
+ listOfAlarms = tizen.alarm.getAll();
+
+ return listOfAlarms.filter(function (item) {
+ // alarm relative has not date property;
+ if (!item.date) {
+ return false;
+ }
+
+ // +40/-10 seconds tolerance;
+ if (parseInt(item.date.getTime() / 1000, 10) < (currentTimeInMinutes + 40)
+ && parseInt(item.date.getTime() / 1000, 10) > (currentTimeInMinutes - 10)) {
+ return true;
+ }
+ });
+ };
+
+ ExercisePlanner.prototype.wait = function () {
+ // lastAlert -> change +1min
+ var snozeTime = 1, currentAlarm = this.currentAlarm, newDate = new Date(), alarm;
+
+ if (currentAlarm) {
+ newDate.setMinutes(newDate.getMinutes() + snozeTime);
+ // period value must be set so application started by alert will know current alert;
+ alarm = new tizen.AlarmAbsolute(newDate, tizen.alarm.PERIOD_WEEK * 10);
+ tizen.alarm.add(alarm, this.selfId);
+ // -- remove old snooze alarm
+ if (currentAlarm.period === tizen.alarm.PERIOD_WEEK * 10) {
+ tizen.alarm.remove(currentAlarm.id);
+ }
+ this.stopMusic();
+ }
+
+ this.exit();
+ // or tizen.application.hide();
+ };
+
+ ExercisePlanner.prototype.ok = function () {
+ this.exit();
+ };
+
+ ExercisePlanner.prototype.startMusic = function () {
+
+ if (!this.audioOfAlert) {
+ this.audioOfAlert = new Audio();
+ }
+ this.audioOfAlert.src = 'WebContent/Runner.mp3';
+ this.audioOfAlert.load();
+ this.audioOfAlert.play();
+ };
+
+ ExercisePlanner.prototype.stopMusic = function () {
+ if (this.audioOfAlert) {
+ this.audioOfAlert.pause();
+ }
+ };
+}());
+
diff --git a/js/app.timeRange.js b/js/app.timeRange.js
new file mode 100644
index 0000000..374fde6
--- /dev/null
+++ b/js/app.timeRange.js
@@ -0,0 +1,386 @@
+/*jslint devel: true*/
+/*global $, ExercisePlanner: true*/
+/**
+ *
+ */
+(function () {
+ "use strict";
+ ExercisePlanner.prototype.checkNewTimeRange = function (start, duration, style) {
+ var result = [];
+ if (duration < 1 || duration > 24) {
+ result.push({
+ name: 'Duration is not set properly.',
+ code: 2
+ });
+ }
+ return result;
+ };
+
+ /**
+ * Sort method for time ranges;
+ */
+ ExercisePlanner.prototype.sortTimeRanges = function () {
+ this.config.timesRanges.sort(function (a, b) {
+ return a.start - b.start;
+ });
+ };
+
+ /**
+ * Find and return max value of nr in time ranges array;
+ * @returns {Number}
+ */
+ ExercisePlanner.prototype.getMaxNrOfTimeRange = function getMaxNrOfTimeRange() {
+ var maxNr = -1, i, len = this.config.timesRanges.length;
+ for (i = 0; i < len; i += 1) {
+ if (maxNr < this.config.timesRanges[i].nr) {
+ maxNr = this.config.timesRanges[i].nr;
+ }
+ }
+ return maxNr;
+ };
+
+ /**
+ *
+ * @param nr
+ * @returns {Boolean}
+ */
+ ExercisePlanner.prototype.getTimeRangeByNr = function getTimeRangeByNr(nr) {
+ var result = this.config.timesRanges.filter(function (item) {
+ return (item.nr === nr);
+ });
+ return result[0];
+ };
+
+ /**
+ * Save time range
+ *
+ * @param nr
+ * @param timeRange
+ * @returns {Boolean}
+ */
+ ExercisePlanner.prototype.saveTimeRange = function (nr, timeRange) {
+ var index = -1,
+ errors = this.checkNewTimeRange(timeRange.start, timeRange.duration, timeRange.style);
+
+ // new timeRanges has nr === -1; this mean we must get max number from config
+ if (nr === -1) {
+ nr = this.getMaxNrOfTimeRange() + 1;
+ } else {
+ index = this.config.timesRanges.indexOf(this.getTimeRangeByNr(nr));
+ }
+
+ timeRange.nr = nr;
+
+ if (errors.length > 0) {
+ this.ui.showErrors(errors);
+ return false;
+ }
+
+ if (index !== -1) {
+ this.config.timesRanges[index] = timeRange;
+ } else {
+ this.config.timesRanges.push(timeRange);
+ }
+
+ this.sortTimeRanges();
+ this.saveConfig();
+ this.ui.fillTimesRanges(this.config.timesRanges);
+ this.generateAlarms();
+ this.updateGraph();
+
+ return true;
+ };
+
+ /**
+ *
+ * @param {nymber}
+ * @returns {number}
+ */
+ ExercisePlanner.prototype.editTimeRange = function editTimeRange(nr) {
+ var timeRange = this.getTimeRangeByNr(nr);
+
+ if (timeRange !== undefined) {
+ this.currentEditingTimePeriod = timeRange;
+ this.currentEditingTimePeriodId = timeRange.nr;
+ } else {
+ this.currentEditingTimePeriod = {
+ nr: -1,
+ start: 10,
+ duration: 1,
+ stop: 11,
+ enabled: true,
+ style: 'everyday'
+ };
+ this.currentEditingTimePeriodId = -1;
+ }
+ return this.currentEditingTimePeriodId;
+ };
+
+ /**
+ * Delete time range by number on list
+ *
+ * @param nr
+ * @returns {Boolean}
+ */
+ ExercisePlanner.prototype.deleteTimeRange = function (nr) {
+
+ var index,
+ timeRange = this.getTimeRangeByNr(nr);
+
+ if (timeRange === undefined) {
+ return false;
+ }
+
+ index = this.config.timesRanges.indexOf(timeRange);
+ if (index === -1) {
+ return false;
+ }
+
+ // delete time range from array;
+ this.config.timesRanges.splice(index, 1);
+
+ this.saveConfig();
+ this.ui.fillTimesRanges(this.config.timesRanges);
+ this.generateAlarms();
+ // update time periods on graph;
+ this.ui.graphSchedule.setTimeRanges(this.periodsWeekToBoolArray());
+ this.ui.graphSchedule.refresh();
+
+ this.updateGraph();
+ this.showNextAlarm();
+ return true;
+ };
+
+ /**
+ * Disable time range by number on list
+ *
+ * @param nr
+ * @returns {Boolean}
+ */
+ ExercisePlanner.prototype.disableTimeRange = function (nr) {
+ var timeRange = this.getTimeRangeByNr(nr);
+
+ if (timeRange === undefined) {
+ return false;
+ }
+
+ timeRange.enabled = !timeRange.enabled;
+
+ this.saveConfig();
+ this.ui.fillTimesRanges(this.config.timesRanges);
+ this.generateAlarms();
+ // update time periods on graph;
+ this.ui.graphSchedule.setTimeRanges(this.periodsWeekToBoolArray());
+ this.ui.graphSchedule.refresh();
+
+ this.updateGraph(this.config.currentTypeOfPeriods);
+ this.showNextAlarm();
+ return true;
+ };
+
+ /**
+ * Combines a overlapped time periods & delete not necesary
+ * This method modifies exisiting array in cache.
+ *
+ * @param periods
+ * @returns
+ */
+ ExercisePlanner.prototype.mergeOverlapPeriods = function mergeOverlapPeriods(periods) {
+ var i, len = periods.length, wasOverlap = true, mergePeriod;
+
+ periods.sort(function (a, b) {
+ return a - b;
+ });
+
+ if (len < 2) {
+ return periods;
+ }
+
+ while (wasOverlap) {
+ wasOverlap = false;
+ len = periods.length;
+ for (i = 0; i < len - 1; i += 1) {
+ if (periods[i].stop > periods[i + 1].start) {
+ mergePeriod = $.extend({}, periods[i]);
+ if (mergePeriod.stop < periods[i + 1].stop) {
+ mergePeriod.stop = periods[i + 1].stop;
+ mergePeriod.duration = mergePeriod.stop - mergePeriod.start;
+ }
+ mergePeriod.nr = -1;
+ periods.splice(i, 2, mergePeriod);
+ wasOverlap = true;
+ break;
+ }
+ }
+ }
+
+ return periods;
+ };
+
+ ExercisePlanner.prototype.mergePeriods = function mergePeriods() {
+ var i, len, onlyEveryDay = true,
+ ranges = this.config.timesRanges,
+ result = {
+ everyday : [],
+ weekend: [],
+ workday: []
+ };
+
+ // checking time ranges for different to the "everyday"
+ for (i = 0, len = ranges.length; i < len; i += 1) {
+ if (ranges[i].style !== 'everyday') {
+ onlyEveryDay = false;
+ break;
+ }
+ }
+
+ if (onlyEveryDay) {
+ for (i = 0, len = this.config.timesRanges.length; i < len; i += 1) {
+ if (this.config.timesRanges[i].enabled) {
+ result.everyday.push(this.config.timesRanges[i]);
+ }
+ }
+ } else {
+ // divide 'everyday' periods at workday/weekend
+ for (i = 0, len = this.config.timesRanges.length; i < len; i += 1) {
+ // if time range is disabled do not append to cache;
+ if (this.config.timesRanges[i].enabled) {
+ switch (this.config.timesRanges[i].style) {
+ case 'everyday':
+ result.workday.push(this.config.timesRanges[i]);
+ result.weekend.push(this.config.timesRanges[i]);
+ break;
+ case 'workday':
+ result.workday.push(this.config.timesRanges[i]);
+ break;
+ case 'weekend':
+ result.weekend.push(this.config.timesRanges[i]);
+ break;
+ }
+ }
+ }
+ }
+
+ // check and correct overlaped time periods
+ this.mergeOverlapPeriods(result.everyday);
+ this.mergeOverlapPeriods(result.workday);
+ this.mergeOverlapPeriods(result.weekend);
+
+ return result;
+ };
+
+ ExercisePlanner.prototype.getSummaryAvailableTime = function getSummaryAvailableTime() {
+ var i, len,
+ periods = this.cache.periodsWeek,
+ sum = {
+ weekend: 0,
+ workday: 0,
+ everyday: 0
+ };
+
+ for (i = 0, len = periods.everyday.length; i < len; i += 1) {
+ if (periods.everyday[i].enabled) {
+ sum.everyday += periods.everyday[i].duration;
+ }
+ }
+ for (i = 0, len = periods.workday.length; i < len; i += 1) {
+ if (periods.workday[i].enabled) {
+ sum.workday += periods.workday[i].duration;
+ }
+ }
+ for (i = 0, len = periods.weekend.length; i < len; i += 1) {
+ if (periods.weekend[i].enabled) {
+ sum.weekend += periods.weekend[i].duration;
+ }
+ }
+
+ return sum;
+ };
+
+ ExercisePlanner.prototype.findNearestTimeRange = function (hour, ranges) {
+ var nearResult,
+ result = {
+ requestedHour: hour,
+ optimalHour: -1
+ };
+
+ if (!ranges.length) {
+ return result;
+ }
+
+ /**
+ * Function search ranges of time for nearest to a hour
+ */
+ nearResult = ranges.reduce(function (previous, element, index) {
+ var delta = 0;
+
+ if (element.start < hour && element.stop < hour) {
+ delta = hour - element.stop;
+ }
+
+ if (element.start > hour && element.stop > hour) {
+ delta = element.start - hour;
+ }
+
+ return (delta < previous.delta) ? { index: index, delta: delta } : previous;
+ }, { index: -1, delta: 100 });
+
+ if (ranges[nearResult.index].start <= hour && ranges[nearResult.index].stop >= hour) {
+ result.optimalHour = Math.round((ranges[nearResult.index].start + ranges[nearResult.index].stop) / 2);
+ } else {
+ result.optimalHour = (ranges[nearResult.index].start > hour) ? ranges[nearResult.index].start : ranges[nearResult.index].stop;
+ }
+
+ return result;
+ };
+
+ /**
+ * Export time period to array of boolen [boolean x 24]
+ * @returns {object}
+ */
+ ExercisePlanner.prototype.periodsWeekToBoolArray = function periodsWeekToBoolArray() {
+ var i, j, len, periods,
+ result = {
+ workday: [],
+ weekend: []
+ };
+
+ // fill default result;
+ for (i = 0; i < 24; i += 1) {
+ result.workday[i] = false;
+ result.weekend[i] = false;
+ }
+
+ // set values;
+ periods = this.cache.periodsWeek.everyday;
+ len = periods.length;
+ for (i = 0; i < len; i += 1) {
+ for (j = periods[i].start; j < periods[i].start + periods[i].duration; j += 1) {
+ result.workday[j] = true;
+ result.weekend[j] = true;
+ }
+ }
+
+ // set values;
+ periods = this.cache.periodsWeek.workday;
+ len = periods.length;
+ for (i = 0; i < len; i += 1) {
+ for (j = periods[i].start; j < periods[i].start + periods[i].duration; j += 1) {
+ result.workday[j] = true;
+ }
+ }
+
+ // set values;
+ periods = this.cache.periodsWeek.weekend;
+ len = periods.length;
+ for (i = 0; i < len; i += 1) {
+ for (j = periods[i].start; j < periods[i].start + periods[i].duration; j += 1) {
+ result.weekend[j] = true;
+ }
+ }
+ return result;
+ };
+
+
+}());
+
diff --git a/js/ext.jqMobile.js b/js/ext.jqMobile.js
new file mode 100644
index 0000000..9de50b5
--- /dev/null
+++ b/js/ext.jqMobile.js
@@ -0,0 +1,17 @@
+/*global $*/
+// customize JQueryMobile controlgroup
+$.fn.oldControlgroup = $.fn.controlgroup;
+$.fn.controlgroup = function (options) {
+ "use strict";
+ return this.oldControlgroup(options).each(function () {
+ this.deselectAll = function () {
+ return $('input', this).attr('checked', false).checkboxradio('refresh');
+ };
+
+ this.select = function (value) {
+ this.deselectAll();
+ return $('input[value$="' + value + '"]', this).attr('checked', true).checkboxradio('refresh');
+ };
+ });
+};
+
diff --git a/js/main.js b/js/main.js
new file mode 100644
index 0000000..c3d8a85
--- /dev/null
+++ b/js/main.js
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012 Samsung Electronics Co., Ltd
+ *
+ * Licensed under the Flora License, Version 1.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://floralicense.org/license/
+ *
+ * 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.
+ */
+/*global ExercisePlanner, tizen, $, document*/
+var c = null, exercisePlanner = null, appService = null, ui = null;
+
+exercisePlanner = new ExercisePlanner();
+$(document).ready(exercisePlanner.init.bind(exercisePlanner));
+
diff --git a/music/Runner.mp3 b/music/Runner.mp3
new file mode 100644
index 0000000..620aba5
--- /dev/null
+++ b/music/Runner.mp3
Binary files differ
diff --git a/signature1.xml b/signature1.xml
new file mode 100644
index 0000000..bee47b5
--- /dev/null
+++ b/signature1.xml
@@ -0,0 +1,160 @@
+<Signature xmlns="http://www.w3.org/2000/09/xmldsig#" Id="DistributorSignature">
+<SignedInfo>
+<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod>
+<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></SignatureMethod>
+<Reference URI="templates/GraphSchedule.tmpl">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>deYSqUgxc88hVjk5a7xUn3Oy2THk0VIYt5J51C/luIY=</DigestValue>
+</Reference>
+<Reference URI="music/Runner.mp3">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>sN0gBCSppwupIISCWk9SKbhuih2BlUxwcFgys6SIzHE=</DigestValue>
+</Reference>
+<Reference URI="AUTHORS">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>FANWRSdDi4T/f2CqQe56fL8NMZfewK2azWEBAfnjbQg=</DigestValue>
+</Reference>
+<Reference URI="images/background.png">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>Y4XligVqi8vVA74GUDSAUawxYuomV/h4ozXffrH+hhU=</DigestValue>
+</Reference>
+<Reference URI="images/longBothHorizonGradient2.png">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>Dq5u5V1W1FL9N+KMTKfBJTEiW0aTVHUWx0utuciKSIo=</DigestValue>
+</Reference>
+<Reference URI="images/markers.png">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>FX2aaCklIguMzPy9rZPS0vY7qWsZ2idyx6ZPUUzQzrU=</DigestValue>
+</Reference>
+<Reference URI="images/state_run.png">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>Z7lFDN5i8vTxs/CnSisNoLvcKDtQEqd6K0E5KUjNgHg=</DigestValue>
+</Reference>
+<Reference URI="images/state_lazy.png">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>s8Dc9i4P69eEp8ULwUL36ZpcSLzjQ02DDL8ozuB6HJw=</DigestValue>
+</Reference>
+<Reference URI="images/longBothHorizonGradient.png">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>1WwlyV7JaL1IyVaPS14yN3wxOngbmuvC2OgVMM9RvpQ=</DigestValue>
+</Reference>
+<Reference URI="LICENSE.Flora">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>fskt8W7B8EMb3DQsvwT98x4fPKKIjY9sS5E0eSGenFA=</DigestValue>
+</Reference>
+<Reference URI="js/app.alarms.js">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>7t0Zyyn9Cu0/HXEXaxEinFCFYpmDDgP7vdyN5UVWkQk=</DigestValue>
+</Reference>
+<Reference URI="js/app.js">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>MtoxmfFtOOUWSBu/kfMTnCeClw9RLIjMS77IWrMG7wg=</DigestValue>
+</Reference>
+<Reference URI="js/ext.jqMobile.js">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>7iRwb6yZgM18/ZU4M8pBpsYb0xXylyPJhsqzbV1eLyY=</DigestValue>
+</Reference>
+<Reference URI="js/main.js">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>lwMHcPXzvcu+hIVItWfT7RIQgrJvRi9mCrq/i38fRGc=</DigestValue>
+</Reference>
+<Reference URI="js/app.onAlarm.js">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>A52TbnmMcr8G0QDY86BmznRMEAVLMrBShC2E51RITb4=</DigestValue>
+</Reference>
+<Reference URI="js/app.timeRange.js">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>4cLd4pP/oi83KOcF8LVUJB6gxUWBsE8ZiDBCoJzjE/o=</DigestValue>
+</Reference>
+<Reference URI="js/UI.simpleTemplate.js">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>xcxpAXCb5SX9K2yPYnYXSg/lFPbD5exPgfKQcvSSqr4=</DigestValue>
+</Reference>
+<Reference URI="js/UI.js">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>oQLj5QS+lsqDZxd0xEQFMBQ4spZ7J0qVvd6OaEiMgQQ=</DigestValue>
+</Reference>
+<Reference URI="js/GraphSchedule.js">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>ZpmuwAVONsdanK1S4KQPaHj1EYC8Ezs1QDZQbaFWm4M=</DigestValue>
+</Reference>
+<Reference URI="js/app.alarmsGenerating.js">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>1i4kpyIdtiDzToEKnuhiGitX2hVtPRVto9KixvY1B+s=</DigestValue>
+</Reference>
+<Reference URI="config.xml">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>XTYWoMBP0dm54DvvQmpdv0uG7UuU8wt06y9PfLGVI8E=</DigestValue>
+</Reference>
+<Reference URI="icon.png">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>Nz+SecsjmNuidhKNvmQ5+3nasrw8vI4q/bjPgbfx5fI=</DigestValue>
+</Reference>
+<Reference URI="index.html">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>xX14SRqSRUj4eElU8av1uUFNdNCNFlCUxZcrs8q4xUw=</DigestValue>
+</Reference>
+<Reference URI="css/jquery.ui.layout.css">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>4v30rSrxvLzjKEHb3fjwwzJHfYs4SuPd2opc+CDv77g=</DigestValue>
+</Reference>
+<Reference URI="css/style.css">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>miNScnFdCe9HXHU/gs87RJdJDHJahAKjhCNH6w6MhFE=</DigestValue>
+</Reference>
+<Reference URI="css/GraphSchedule.css">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>FMRxMozySMCFzVvfumNqZOHA+/qDtj84zddroztYWME=</DigestValue>
+</Reference>
+<Reference URI="NOTICE.Flora">
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>M7oEsiEdLNeaSAYdtR7uR5WGeAELG/V70u7Huzl42Xs=</DigestValue>
+</Reference>
+<Reference URI="#prop">
+<Transforms>
+<Transform Algorithm="http://www.w3.org/2006/12/xml-c14n11"></Transform>
+</Transforms>
+<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
+<DigestValue>u/jU3U4Zm5ihTMSjKGlGYbWzDfRkGphPPHx3gJIYEJ4=</DigestValue>
+</Reference>
+</SignedInfo>
+<SignatureValue>
+BOQ2cEF2grAU8uRj4s7asgejDpRN26vGa4TJxe6xdBULJ1th7w6mB9czrghnsNZu/yGCCMCep9I0
+cu1Wdyly3mSd3Z/HlR5meFHYekMaY1e2goXatc+jjayWuIxFFIG6FG0UvjY8nc/Ft1vgiwdI0e/Y
+KgsF6y2N3mnliXB9rOU=
+</SignatureValue>
+<KeyInfo>
+<X509Data>
+<X509Certificate>
+MIICnTCCAgYCCQDE9MbMmJ/yCzANBgkqhkiG9w0BAQUFADCBkDELMAkGA1UEBhMCS1IxDjAMBgNV
+BAgMBVN1d29uMQ4wDAYDVQQHDAVTdXdvbjEWMBQGA1UECgwNVGl6ZW4gVGVzdCBDQTEiMCAGA1UE
+CwwZVGl6ZW4gRGlzdHJpYnV0b3IgVGVzdCBDQTElMCMGA1UEAwwcVGl6ZW4gUGFydG5lciBEaXN0
+cmlidXRvciBDQTAeFw0xMjEwMjcwNzQ4MzNaFw0yMjEwMjUwNzQ4MzNaMIGUMQswCQYDVQQGEwJL
+UjEOMAwGA1UECAwFU3V3b24xDjAMBgNVBAcMBVN1d29uMRYwFAYDVQQKDA1UaXplbiBUZXN0IENB
+MSIwIAYDVQQLDBlUaXplbiBEaXN0cmlidXRvciBUZXN0IENBMSkwJwYDVQQDDCBUaXplbiBQYXJ0
+bmVyIERpc3RyaWJ1dG9yIFNpZ25lcjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAy9mg2x4B
+zxlK3LJL81GsLq/pJfK1evdCKG/IOBpdoRO0rLhYnsL5+KvToPFa5g9GTZo32LikpW1NZ7++3EHE
+fnO2IGLUau4kquvhmz1LNg5xBTx7IbucmwLMRGo1BPGdsAQQLyXeQKJ5PCERmVg4MIoiL2zT/JsL
+sZ9UPT6GEB8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQAw5xPBFR1XKuZ8QpsCtSE0zXVHvwIa+Ha4
+YBdRtGwEoZmiKGZV/wAhPRdmR0kISkTz20kIGz/ZwRZCVGhsr5hkkpFknYlKeKkEJ/tJfZl4D7ec
+GFAnynOzlWZqSIPz+yxX8ah9E6lTv4Vs9DhNb08nxVvxLqlpyVdk9RUsCx/yIA==
+</X509Certificate>
+<X509Certificate>
+MIICtTCCAh6gAwIBAgIJAKORBcIiXygIMA0GCSqGSIb3DQEBBQUAMIGVMQswCQYDVQQGEwJLUjEO
+MAwGA1UECAwFU3V3b24xDjAMBgNVBAcMBVN1d29uMRYwFAYDVQQKDA1UaXplbiBUZXN0IENBMSIw
+IAYDVQQLDBlUaXplbiBEaXN0cmlidXRvciBUZXN0IENBMSowKAYDVQQDDCFUaXplbiBQYXJ0bmVy
+IERpc3RyaWJ1dG9yIFJvb3QgQ0EwHhcNMTIxMDI3MDc0NTIwWhcNMjIxMDI1MDc0NTIwWjCBkDEL
+MAkGA1UEBhMCS1IxDjAMBgNVBAgMBVN1d29uMQ4wDAYDVQQHDAVTdXdvbjEWMBQGA1UECgwNVGl6
+ZW4gVGVzdCBDQTEiMCAGA1UECwwZVGl6ZW4gRGlzdHJpYnV0b3IgVGVzdCBDQTElMCMGA1UEAwwc
+VGl6ZW4gUGFydG5lciBEaXN0cmlidXRvciBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
+2ZQrdEowjqxUmB8FX8ej19VKY6jGHKNIRE5wrhBkuZ1b0FLRPiN3/Cl9wMkCnyJui4QhC28g1aBg
+w/JnaObcDqW1NgFVH3006+gZvCTDlw1nIEjvZa6P+uWOOi05xPPAE0feKPkO1POnOjnapfkkEVNU
+8TXsLbLYBylWT8rxZC8CAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBJ
+yJ7p6qs0JI+1iKOk/sYWVP6dMueY72qOc/wVj5c3ejOlgJNNXDMAQ14QcRRexffc68ipTwybU/3m
+tcNwydzKJe+GFa4b2zyKOvOgrfs4MKSR0T9XEPmTKeR+NDT2CbA6/kQoRYm0fSORzD2UXJzNZWe/
+WjwSA66hv4q+0QZQFQ==
+</X509Certificate>
+</X509Data>
+</KeyInfo>
+<Object Id="prop"><SignatureProperties xmlns:dsp="http://www.w3.org/2009/xmldsig-properties"><SignatureProperty Id="profile" Target="#DistributorSignature"><dsp:Profile URI="http://www.w3.org/ns/widgets-digsig#profile"></dsp:Profile></SignatureProperty><SignatureProperty Id="role" Target="#DistributorSignature"><dsp:Role URI="http://www.w3.org/ns/widgets-digsig#role-distributor"></dsp:Role></SignatureProperty><SignatureProperty Id="identifier" Target="#DistributorSignature"><dsp:Identifier></dsp:Identifier></SignatureProperty></SignatureProperties></Object>
+</Signature> \ No newline at end of file
diff --git a/templates/GraphSchedule.tmpl b/templates/GraphSchedule.tmpl
new file mode 100644
index 0000000..0dc59d7
--- /dev/null
+++ b/templates/GraphSchedule.tmpl
@@ -0,0 +1,91 @@
+<div class="GraphSchedule">
+ <div class="container">
+ <table>
+ <tr class="rangesWeekend">
+ <td class="h00"></td>
+ <td class="h01"></td>
+ <td class="h02"></td>
+ <td class="h03"></td>
+ <td class="h04"></td>
+ <td class="h05 th"></td>
+ <td class="h06 th"></td>
+ <td class="h07"></td>
+ <td class="h08"></td>
+ <td class="h09"></td>
+ <td class="h10"></td>
+ <td class="h11"></td>
+ <td class="h12"></td>
+ <td class="h13"></td>
+ <td class="h14"></td>
+ <td class="h15"></td>
+ <td class="h16"></td>
+ <td class="h17"></td>
+ <td class="h18"></td>
+ <td class="h19"></td>
+ <td class="h20"></td>
+ <td class="h21"></td>
+ <td class="h22"></td>
+ <td class="h23"></td>
+ </tr>
+ <tr class="ranges">
+ <td class="h00"></td>
+ <td class="h01"></td>
+ <td class="h02"></td>
+ <td class="h03"></td>
+ <td class="h04"></td>
+ <td class="h05"></td>
+ <td class="h06"></td>
+ <td class="h07"></td>
+ <td class="h08"></td>
+ <td class="h09"></td>
+ <td class="h10"></td>
+ <td class="h11"></td>
+ <td class="h12"></td>
+ <td class="h13"></td>
+ <td class="h14"></td>
+ <td class="h15"></td>
+ <td class="h16"></td>
+ <td class="h17"></td>
+ <td class="h18"></td>
+ <td class="h19"></td>
+ <td class="h20"></td>
+ <td class="h21"></td>
+ <td class="h22"></td>
+ <td class="h23"></td>
+ </tr>
+ <tr class="grid">
+ <td>00</td>
+ <td>01</td>
+ <td>02</td>
+ <td>03</td>
+ <td>04</td>
+ <td>05</td>
+ <td>06</td>
+ <td>07</td>
+ <td>08</td>
+ <td>09</td>
+ <td>10</td>
+ <td>11</td>
+ <td>12</td>
+ <td>13</td>
+ <td>14</td>
+ <td>15</td>
+ <td>16</td>
+ <td>17</td>
+ <td>18</td>
+ <td>19</td>
+ <td>20</td>
+ <td>21</td>
+ <td>22</td>
+ <td>23</td>
+ </tr>
+ </table>
+ </div>
+</div>
+<div class="flag">
+ <div class="container">
+ <div class="rod"></div>
+ <p></p>
+ <div class="hint"></div>
+ </div>
+</div>