application sources from tizen_2.2 73/10473/1 tizen
authorPiotr Dabrowski <p.dabrowski2@samsung.com>
Wed, 2 Oct 2013 08:19:26 +0000 (10:19 +0200)
committerPiotr Dabrowski <p.dabrowski2@samsung.com>
Wed, 2 Oct 2013 08:19:26 +0000 (10:19 +0200)
Change-Id: Ie37447a85d2b4fb219222c6319ab10d97a514746

29 files changed:
chatter-snapshot.png [new file with mode: 0644]
description.xml [new file with mode: 0755]
description.xsl [new file with mode: 0755]
project/.project [new file with mode: 0644]
project/AUTHORS [new file with mode: 0644]
project/LICENSE.Flora [new file with mode: 0644]
project/NOTICE [new file with mode: 0644]
project/config.xml [new file with mode: 0644]
project/css/style.css [new file with mode: 0644]
project/icon.png [new file with mode: 0644]
project/index.html [new file with mode: 0644]
project/js/app.config.js [new file with mode: 0644]
project/js/app.helpers.js [new file with mode: 0644]
project/js/app.js [new file with mode: 0644]
project/js/app.model.js [new file with mode: 0644]
project/js/app.ui.events.js [new file with mode: 0644]
project/js/app.ui.js [new file with mode: 0644]
project/js/app.ui.templateManager.js [new file with mode: 0644]
project/js/app.ui.templateManager.modifiers.js [new file with mode: 0644]
project/js/main.js [new file with mode: 0644]
project/templates/callerRow.tpl [new file with mode: 0644]
project/templates/chat.tpl [new file with mode: 0644]
project/templates/contactRow.tpl [new file with mode: 0644]
project/templates/contactSelect.tpl [new file with mode: 0644]
project/templates/main_page.tpl [new file with mode: 0644]
project/templates/normalBubble.tpl [new file with mode: 0644]
tizen-app-template.xml [new file with mode: 0755]
tizen_32.png [new file with mode: 0644]
tizen_64.png [new file with mode: 0644]

diff --git a/chatter-snapshot.png b/chatter-snapshot.png
new file mode 100644 (file)
index 0000000..7dcb779
Binary files /dev/null and b/chatter-snapshot.png differ
diff --git a/description.xml b/description.xml
new file mode 100755 (executable)
index 0000000..39776d2
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<?xml-stylesheet type="text/xsl" href="description.xsl"?>\r
+<Overview version="1.0">\r
+  <SampleName>Chatter</SampleName>\r
+  <SampleVersion>1.0.0</SampleVersion>\r
+  <Preview>chatter-snapshot.png</Preview>\r
+  <Description>\r
+         A sample application demonstrating the tizen device API usage.\r
+  </Description>\r
+</Overview>\r
diff --git a/description.xsl b/description.xsl
new file mode 100755 (executable)
index 0000000..1f4f57f
--- /dev/null
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 
+    This file provides a functionality to show template's description.xml in the project wizard.
+    Don't delete or move this file.
+ -->
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+  <xsl:template match="/">
+    <html>
+      <head>
+        <style type="text/css">
+          html,body {
+          font-family:Arial;
+          margin: 0px;
+          }
+          td
+          {
+          font-size:13px;
+          }
+          .samplename
+          {
+          font-size:16px;
+          color:#ffffff;
+          height:26px;
+          background-color:#6d96ac;
+          }
+          .category
+          {
+          font-size:16px;
+          color:#ffffff;
+          height:30px;
+          background-color:#6d96ac;
+          }
+          .contents
+          {
+          padding: 6px 10px 14px 10px;
+          }
+          table#widgets td
+          {
+          border: solid 1px #6d96ac;
+          border-collapse: collapse;
+          }
+          .widgetname
+          {
+          font-weight: bold;
+          text-align: center;
+          width: 20%;
+          word-break:break-all;
+          }
+          table#references td
+          {
+          width: 100%;
+          border: 0px;
+          border-spacing: 0px;
+          padding: 5px;
+          }
+          .refname
+          {
+          width: 100%;
+          font-weight: bold;
+          }
+        </style>
+      </head>
+      <body>
+        <table width="400px" border="0" cellspacing="0">
+          <tr>
+            <td class="samplename" align="center">
+              <xsl:value-of select="Overview/SampleName"/>
+              <xsl:text disable-output-escaping="yes"><![CDATA[&nbsp;]]></xsl:text>
+                         <!--
+              <xsl:value-of select="Overview/SampleVersion"/>
+                         -->
+            </td>
+          </tr>
+          <tr bgcolor="#FFFFFF">
+            <td class="contents">
+                         <strong>Type</strong>: JavaScript
+                         <p>
+              <xsl:value-of select="Overview/Description"/>
+                         </p>
+            </td>
+          </tr>
+          <tr>
+            <td align="center" bgcolor="#FFFFFF" height="260px">
+              <img>
+                <xsl:attribute name="src">
+                  <xsl:value-of select="Overview/Preview"/>
+                </xsl:attribute>
+              </img>
+            </td>
+          </tr>
+        </table>
+      </body>
+    </html>
+  </xsl:template>
+
+</xsl:stylesheet>
diff --git a/project/.project b/project/.project
new file mode 100644 (file)
index 0000000..d9be159
--- /dev/null
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>Chatter</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.wst.common.project.facet.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>json.validation.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.tizen.web.jslint.nature.JSLintBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.tizen.web.css.nature.CSSBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.wst.validation.validationbuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.tizen.web.project.builder.WebBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>json.validation.nature</nature>
+               <nature>org.tizen.web.jslint.nature.JSLintNature</nature>
+               <nature>org.tizen.web.css.nature.CSSNature</nature>
+               <nature>org.eclipse.wst.jsdt.core.jsNature</nature>
+               <nature>org.eclipse.wst.common.project.facet.core.nature</nature>
+               <nature>org.tizen.web.project.builder.WebNature</nature>
+       </natures>
+</projectDescription>
diff --git a/project/AUTHORS b/project/AUTHORS
new file mode 100644 (file)
index 0000000..0cf3cca
--- /dev/null
@@ -0,0 +1,5 @@
+Dariusz Paziewski <d.paziewski at samsung dot com>
+Tomasz Lukawski <t.lukawski 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/project/LICENSE.Flora b/project/LICENSE.Flora
new file mode 100644 (file)
index 0000000..4a0af40
--- /dev/null
@@ -0,0 +1,206 @@
+Flora License
+
+Version 1.1, April, 2013
+
+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 Tizen Compliance Specification
+and passes the Tizen Compliance Tests 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
+     and your own copyright statement or terms and conditions do not conflict
+     the conditions stated in the License including section 3.
+
+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.1 (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/project/NOTICE b/project/NOTICE
new file mode 100644 (file)
index 0000000..092bc04
--- /dev/null
@@ -0,0 +1,4 @@
+Copyright (c) 2013 Samsung Electronics Co., Ltd. All rights reserved.
+Except as noted, this software is licensed under Flora License, Version 1.1
+Please, see the LICENSE.Flora file for Flora License, Version 1.1 terms and conditions.
+
diff --git a/project/config.xml b/project/config.xml
new file mode 100644 (file)
index 0000000..96ef541
--- /dev/null
@@ -0,0 +1,15 @@
+<?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/Chatter" version="2.2.0" viewmodes="maximized">
+       <tizen:application id="6lIxpWKxGP.Chatter" package="6lIxpWKxGP" required_version="2.2"/>
+       <content src="index.html"/>
+       <icon src="icon.png"/>
+       <name>Chatter</name>
+       <feature name="http://tizen.org/feature/screen.size.normal.720.1280"/>
+       <tizen:privilege name="http://tizen.org/privilege/application.launch"/>
+       <tizen:privilege name="http://tizen.org/privilege/contact.read"/>
+       <tizen:privilege name="http://tizen.org/privilege/contact.write"/>
+       <tizen:privilege name="http://tizen.org/privilege/messaging.read"/>
+       <tizen:privilege name="http://tizen.org/privilege/messaging.send"/>
+       <tizen:privilege name="http://tizen.org/privilege/messaging.write"/>
+       <tizen:setting screen-orientation="portrait" context-menu="disable" background-support="disable" encryption="disable" install-location="auto"/>
+</widget>
diff --git a/project/css/style.css b/project/css/style.css
new file mode 100644 (file)
index 0000000..4b1d969
--- /dev/null
@@ -0,0 +1,174 @@
+#start > #start-content > .ui-scrollview-view .ui-li-text-sub {
+       top: 2rem;
+}
+
+#send {
+       float: left;
+       width: 85px;
+       margin-top: 10px;
+       font-size: 22px;
+}
+
+.ui-textArea {
+       position: absolute;
+       width: 100%;
+       height: 160px;
+       display: -webkit-box;
+       -webkit-box-orient: horizontal;
+}
+
+.ui-textArea-text {
+       -webkit-box-flex: 1;
+}
+
+.ui-textArea-button {
+       width: 100px;
+}
+
+.ui-textArea-text-text {
+       background-color: #fff;
+       color: #555;
+       position: absolute;
+       left: 10px;
+       right: 110px;
+       height: 140px;
+       margin: 0px;
+       border: 0px solid #000;
+       border-radius: 5px;
+       font-size: 22px;
+       overflow: auto;
+}
+
+#text {
+       top: 10px;
+       white-space: pre;
+}
+
+#counter {
+       float: right;
+       margin-top: 20px;
+       margin-right: 10px;
+       opacity: 0.5;
+}
+
+#counter p {
+       font-size: 20px;
+}
+
+#counter p span{
+       font-size: small;
+}
+
+#start-footer .ui-grid-solo .ui-block-a {
+       width: 70px;
+}
+
+.ui-popupwindow .center_basic_1btn .popup-text {
+       font-size: 26px;
+}
+
+.ui-btn .ui-btn-text.ui-btn-text-padding-left {
+       padding-left: .522222rem;
+}
+
+.ui-checkbox .ui-btn.ui-btn-icon-left .ui-btn-inner {
+       line-height: 1.9rem;
+}
+
+.ui-checkbox .ui-btn {
+       width: 100%;
+}
+
+#chat-footer {
+       height: 164px;
+}
+
+#chat .ui-li {
+       min-height: 23px;
+}
+
+#chat .ui-li span.text {
+       white-space: pre;
+}
+
+.ui-listview .ui-li-bubble-right {
+       margin-left: 50px;
+}
+
+.ui-listview .ui-li-bubble-left {
+       margin-right: 50px;
+}
+
+.ui-listview .ui-li-bubble-right label {
+       display:none;
+}
+
+.ui-listview .ui-li-bubble-left label {
+       position: absolute;
+       top: 0px;
+       left: 38px;
+       color: #6666B6;
+       padding: 0 5px;
+       background: #F8F6EF;
+       line-height: 12px;
+       font-size: 12px;
+       font-weight: bold;
+       text-shadow: 1px 1px 1px white;
+       border-radius: 0 0 7px 7px;
+}
+
+.ui-listview-workaround {
+       margin: -1px;
+}
+
+.callerName {
+       text-overflow: ellipsis;
+       white-space: nowrap;
+       overflow: hidden;
+       width: 90%;
+}
+
+.warning {
+       background: red;
+       border: 1px solid white;
+       border-radius: 5px;
+       color: white;
+       font-size: 12px !important;
+       left: -17px;
+       position: absolute;
+       text-align: center;
+       top: 0;
+       width: 13px;
+}
+
+.warning:before {
+       content: "!";
+}
+
+.statusSENT {
+       display: none;
+}
+
+.statusDRAFT {
+       width: auto;
+       background: none;
+       border: none;
+       color: #3D7BFF;
+}
+
+.statusDRAFT:before {
+       content: "\270E";
+}
+
+#enterNumberCreate .ui-btn-inner.ui-btn-hastxt,
+#sentAlert .ui-btn-inner.ui-btn-hastxt {
+       border: none;
+}
+
+#number {
+       box-sizing: border-box;
+       width: 100%;
+}
+#enterNumberCreate {
+       width: 60%;
+}
\ No newline at end of file
diff --git a/project/icon.png b/project/icon.png
new file mode 100644 (file)
index 0000000..5934757
Binary files /dev/null and b/project/icon.png differ
diff --git a/project/index.html b/project/index.html
new file mode 100644 (file)
index 0000000..f3e24be
--- /dev/null
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+       <meta charset="utf-8"/>
+       <meta name="description" content="chatter"/>
+       <meta name="viewport" content="width=360, user-scalable=no"/>
+
+       <title>Chatter</title>
+
+       <script src="tizen-web-ui-fw/latest/js/jquery.min.js"></script>
+       <script src="tizen-web-ui-fw/latest/js/tizen-web-ui-fw-libs.min.js"></script>
+       <script src="tizen-web-ui-fw/latest/js/tizen-web-ui-fw.min.js" data-framework-theme="tizen-white" data-framework-viewport-scale="false"></script>
+
+       <script type="text/javascript" src="./js/main.js"></script>
+       <link rel="stylesheet" type="text/css" href="./css/style.css"/>
+</head>
+<body>
+       <div id="main" data-role="page" data-footer-exist="true"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/project/js/app.config.js b/project/js/app.config.js
new file mode 100644 (file)
index 0000000..2800e45
--- /dev/null
@@ -0,0 +1,29 @@
+/*global $, tizen, app */
+/**
+ * @class Config
+ */
+function Config() {
+       'use strict';
+}
+
+(function () { // strict mode wrapper
+       'use strict';
+       Config.prototype = {
+
+               properties: {
+                       'templateDir': 'templates',
+                       'templateExtension': '.tpl'
+               },
+
+               /**
+                * Returns config value
+                */
+               get: function (value, defaultValue) {
+
+                       if (this.properties.hasOwnProperty(value)) {
+                               return this.properties[value];
+                       }
+                       return defaultValue;
+               }
+       };
+}());
diff --git a/project/js/app.helpers.js b/project/js/app.helpers.js
new file mode 100644 (file)
index 0000000..438fc60
--- /dev/null
@@ -0,0 +1,103 @@
+/*global $, tizen, app */
+/**
+ * @class Helpers
+ */
+function Helpers() {
+       'use strict';
+       return;
+}
+
+(function () { // strict mode wrapper
+       'use strict';
+       Helpers.prototype = {
+
+               /**
+                * Checks the length of the string and returns true if its length is greater than 0
+                * @param {string} value The value of the string.
+                * @returns {boolean}
+                */
+               checkStringLength: function Helpers_checkStringLength(value) {
+                       return value.length > 0 ? true : false;
+               },
+
+               objectLength: function (obj) {
+                       var result = 0, prop;
+                       for (prop in obj) {
+                               if (obj.hasOwnProperty(prop)) {
+                                       result += 1;
+                               }
+                       }
+                       return result;
+               },
+
+               /**
+                * Checks the length of the number and returns true if its length is greater than 0
+                * @param {string} value The value of the number.
+                * @returns {boolean}
+                */
+               validateNumberLength: function Helpers_validateNumberLength(value) {
+                       return value.length > 0 ? true : false;
+               },
+
+               /**
+                * Checks the value of the number and returns true if its value is greater than 0
+                * @param {number} value The value of the number.
+                * @returns {boolean}
+                */
+               isNumberGreaterThanZero: function Helpers_isNumberGreaterThanZero(value) {
+                       return value > 0 ? true : false;
+               },
+
+               /**
+                * Checks the value of the number and adds "0" before if its value is lower than 10
+                * @param {number} value The value of the number.
+                * @returns {string|number}
+                */
+               addZeroBefore: function Helpers_addZeroBefore(value) {
+                       return value < 10 ? "0" + value : value;
+               },
+
+               getDate: function (date){
+                       var today = new Date(), yesterday;
+                       if (today.toDateString() === date.toDateString()) {
+                               return 'Today';
+                       } else {
+                               yesterday = new Date()
+                               yesterday.setDate(today.getDate() - 1);
+                               if (yesterday.toDateString() === date.toDateString()) {
+                                       return 'Yesterday';
+                               } else if (today.getTime() < date.getTime()){
+                                       return 'from Future';
+                               } else {
+                                       return date.getDate() + '.'
+                                               + (date.getMonth() + 1) + '.'
+                                               + date.getFullYear();
+                               }
+                       }
+               },
+
+               /**
+                * Checks if the date passed as an argument is today's date
+                * @param {date} date The value of the date to compare.
+                * @returns {string} Empty string if it is not today's date or "(today)" if it is today's date.
+                */
+               checkToday: function Helpers_checkToday(date) {
+                       var today = new Date(), todayString = '';
+                       if (today.getDate() === date.getDate() && today.getMonth() === date.getMonth() && today.getFullYear() === date.getFullYear()) {
+                               todayString = '(today)';
+                       }
+                       return todayString;
+               },
+
+               /**
+                * Extracts full name from contact object
+                * @param {contact} contact The contact object.
+                * @param {string} option The optional value for full name if the firstName and lastName are empty.
+                * @returns {string} Full contact name.
+                */
+               getCallerName: function Helpers_getCallerName(contact, option) {
+                       var name = contact.name.displayName;
+                       return (name && name.length > 0) ? name : option;
+               }
+       };
+}());
diff --git a/project/js/app.js b/project/js/app.js
new file mode 100644 (file)
index 0000000..bc86c63
--- /dev/null
@@ -0,0 +1,168 @@
+/*jslint devel: true*/
+/*global $, tizen, app, Config, Helpers, Ui, Model */
+var App = null;
+
+(function () { // strict mode wrapper
+       'use strict';
+
+       /**
+        * Creates a new application object
+        *
+        * @class Application
+        */
+       App = function App() {
+               this.currentNumber = null;
+               this.currentCaller = null;
+
+               this.gsmEncoding = new RegExp('^[\\\w@ZÄÖÑÜà£$¥èùìòÇØøÅåΔΦΓΛΩΠΨΣΘΞÆæßÉ'
+                       +'_ !#¤%&\\\(\\\)\\\*=,-.\\\/:;<=>?'
+                       +'¡\\\'\\\"¿|^€{}\\\[\\\]~\\\n]*$', 'i');
+               this.doubleChars = /[|^€{}\[\]~]/g;
+               this.notNumberChars = /([^0-9+*#])/g;
+
+               this.maxMessageLength = {'gsm': 160, 'utf': 70};
+               this.totalMessageLength = {'gsm': 1530, 'utf': 670};
+               this.multiMessageHeaderLength = {'gsm': 7, 'utf': 3};
+
+               this.allowKeys = [8, 37, 38, 39, 40, 46];
+       };
+
+       App.prototype = {
+               /**
+                * @type Array
+                */
+               requires: [
+                       'js/app.config.js',
+                       'js/app.helpers.js',
+                       'js/app.model.js',
+                       'js/app.ui.js',
+                       'js/app.ui.templateManager.js',
+                       'js/app.ui.templateManager.modifiers.js',
+                       'js/app.ui.events.js'
+               ],
+
+               /**
+                * @type Model
+                */
+               model: null,
+
+               /**
+                * @type Ui
+                */
+               ui: null,
+
+               /**
+                * @type Config
+                */
+               config: null,
+
+               /**
+                * @type Helpers
+                */
+               helpers: null,
+
+               /**
+                * Initialisation function
+                */
+               init: function App_init() {
+                       // instantiate the libs
+                       this.config = new Config();
+                       this.helpers = new Helpers();
+                       this.ui = new Ui();
+                       this.model = new Model();
+                       return this;
+               },
+
+               setCurrentNumber: function App_setCurrentNumber(number) {
+                       this.currentNumber = number;
+               },
+
+               getCurrentNumber: function App_getCurrentNumber() {
+                       return this.currentNumber;
+               },
+
+               setCurrentCaller: function App_setCurrentCaller(caller) {
+                       this.currentCaller = caller;
+               },
+
+               getCurrentCaller: function App_getCurrentCaller() {
+                       return this.currentCaller;
+               },
+
+               /**
+                * exit application action
+                */
+               exit: function App_exit() {
+                       var application = tizen.application.getCurrentApplication();
+                       application.exit();
+               },
+
+               fillUpMessagePage: function () {
+                       if ($.mobile.activePage.attr('id') === 'main') {
+                               this.ui.loadCallerList();
+                       } else {
+                               this.ui.showMessageChat();
+                       }
+               },
+
+               resetPages: function App_resetPages() {
+                       this.ui.resetPages();
+               },
+
+               loadContacts: function App_loadContacts() {
+                       this.model.loadContacts(this.showContactSelectPage.bind(this));
+               },
+
+               showContactsLoaded: function () {
+                       var i, len, sortedContactList = [];
+
+                       if (this.model.contactsLoaded !== null
+                                       && this.model.contactsLoaded.length) {
+                               len = this.model.contactsLoaded.length;
+                               for (i = 0; i < len; i += 1) {
+                                       if (this.model.contactsLoaded[i].phoneNumbers.length) {
+                                               sortedContactList.push({
+                                                       caller: this.helpers.getCallerName(this.model.contactsLoaded[i], 'no name'),
+                                                       number: this.model.contactsLoaded[i].primaryNumber,
+                                                       contact: this.model.contactsLoaded[i]
+                                               });
+                                       }
+                               }
+
+                               sortedContactList.sort(function (a, b) {
+                                       if (a.caller < b.caller) {
+                                               return -1;
+                                       } else if (a.caller > b.caller) {
+                                               return 1;
+                                       }
+                                       return 0;
+                               });
+                       }
+                       this.ui.fillContactList(sortedContactList);
+               },
+
+               showContactSelectPage: function () {
+                       this.currentNumber = null;
+                       this.currentCaller = null;
+                       $.mobile.changePage('#contactSelect');
+               },
+
+               sendMessage: function App_sendMessage(text, numbers) {
+                       var onError = function(e) {
+                               var message = "Unknown error.";
+                               if (e) {
+                                       if (e.type === "NetworkError") {
+                                               message = "Network error.";
+                                       } else if (e.type === "") {
+                                               message = "Invalid number.";
+                                       }
+                               }
+                               app.ui.showSendErrorPopup(message);
+                       };
+                       this.model.sendMessage(numbers, text,
+                               app.model.prepareMessages.bind(app.model, app.ui.showMessageChat),
+                               onError
+                       );
+               }
+       };
+}());
diff --git a/project/js/app.model.js b/project/js/app.model.js
new file mode 100644 (file)
index 0000000..a82858c
--- /dev/null
@@ -0,0 +1,258 @@
+/*jslint devel: true*/
+/*global $, tizen, app */
+/**
+ * @class Model
+ */
+function Model() {
+       'use strict';
+
+       this.addressBook = tizen.contact.getDefaultAddressBook();
+       this.smsService = null;
+       this.phoneNumbersFilter = new tizen.AttributeFilter('phoneNumbers.number', 'EXISTS');
+       this.messagesList = {};
+       this.contactsLoaded = null;
+}
+
+(function () { // strict mode wrapper
+       'use strict';
+       Model.prototype = {
+
+               DRAFTS_FOLDER: "3",
+
+               init: function () {
+                       this.loadContacts();
+                       this.initSmsService();
+               },
+
+               initSmsService: function () {
+                       var self = this;
+                       try {
+                               tizen.messaging.getMessageServices("messaging.sms",
+                                       function (s) {
+                                               self.smsService = s[0];
+                                               self.prepareMessages(app.fillUpMessagePage.bind(self));
+                                               self.messagesChangeListener();
+                                       }
+                               );
+                       } catch (error) {
+                               console.error(error.message);
+                       }
+               },
+
+               messagesChangeListener: function () {
+                       var self = this, config,
+                               messageChangeCallback = {
+                                       messagesupdated: function (messages) {
+                                               if (messages[0].messageStatus !== 'SENDING') {
+                                                       app.ui.changeMessageStatus(messages[0]);
+                                               }
+                                       },
+                                       messagesadded: function (messages) {
+                                               self.prepareMessages(app.ui.showMessageChat);
+                                       },
+                                       messagesremoved: function () {
+                                               self.prepareMessages(app.ui.showMessageChat);
+                                       }
+                               };
+                       this.smsService.messageStorage.addMessagesChangeListener(messageChangeCallback);
+               },
+
+               prepareMessages: function (callback) {
+                       var self = this;
+                       try {
+                               this.smsService.messageStorage.findMessages(
+                                       new tizen.AttributeFilter("type", "EXACTLY", "messaging.sms"),
+                                       function (messages) {
+                                               function compare(a, b) {
+                                                       if (a.timestamp > b.timestamp) {
+                                                               return -1;
+                                                       } else if (a.timestamp < b.timestamp) {
+                                                               return 1;
+                                                       } else {
+                                                               return 0;
+                                                       }
+                                               }
+                                               messages.sort(compare);
+                                               self.messagesList = self.groupMessages(messages);
+                                               app.ui.loadCallerList();
+                                               callback();
+                                       },
+                                       function () {
+                                               console.error('prepareMessage: error');
+                                       }
+                               );
+                       } catch (err) {
+                               console.error(err);
+                       }
+               },
+
+               groupMessages: function (messages) {
+                       var i, obj = {}, folderId;
+                       for (i in messages) {
+                               folderId = messages[i].folderId;
+                               if ((folderId !== null && folderId !== this.DRAFTS_FOLDER)
+                                               || messages[i].messageStatus === 'DRAFT') {
+                                       if (messages.hasOwnProperty(i)) {
+                                               obj = this.groupMessagesSingle(messages[i], obj);
+                                       }
+                               }
+                       }
+                       return obj;
+               },
+
+               groupMessagesSingle: function (message, obj) {
+                       var key, j;
+                       if (message.from) {
+                               key = message.from;
+                               obj[key] = this.pushData(message, obj[key]);
+                       } else {
+                               for (j in message.to) {
+                                       if (message.to.hasOwnProperty(j)) {
+                                               key = message.to[j];
+                                               obj[key] = this.pushData(message, obj[key]);
+                                       }
+                               }
+                       }
+                       return obj;
+               },
+
+               pushData: function (message, obj) {
+                       obj = obj || this.getGroupObject();
+                       obj.messages.push(message);
+                       if (app.helpers.objectLength(obj.lastMessage) === 0) {
+                               obj.lastMessage = message;
+                       }
+                       return obj;
+               },
+
+               getGroupObject: function () {
+                       return {
+                               messages: [],
+                               lastMessage: {},
+                               last: {
+                                       body: {
+                                               plainBody: null
+                                       },
+                                       timestamp: new Date()
+                               }
+                       };
+               },
+
+               getMessages: function () {
+                       return this.messagesList;
+               },
+
+               getNameByNumber: function (number) {
+                       var i, j, contact, name;
+                       for (i in this.contactsLoaded) {
+                               if (this.contactsLoaded.hasOwnProperty(i)) {
+                                       contact = this.contactsLoaded[i];
+                                       for (j in contact.phoneNumbers) {
+                                               if (contact.phoneNumbers.hasOwnProperty(j)) {
+                                                       if (contact.phoneNumbers[j].number === number) {
+                                                               name = contact.name.displayName;
+                                                               break;
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+                       return name || number;
+               },
+
+               getTextEncoding: function (text) {
+                       if (app.gsmEncoding.test(text)) {
+                               return 'gsm';
+                       } else {
+                               return 'utf';
+                       }
+               },
+
+               getTextLength: function (text, encoding) {
+                       if (encoding === 'gsm') {
+                               return text.replace(app.doubleChars, '__').length;
+                       } else {
+                               return text.length;
+                       }
+               },
+
+               removeMessage: function (message, callback) {
+                       this.smsService.messageStorage
+                               .removeMessages([message], callback, function (e) {
+                                       console.error(e);
+                               });
+               },
+
+               editDraft: function (message, li) {
+                       this.removeMessage(message, function () {
+                               $("#text").val(message.body.plainBody).trigger('input');
+                               li.remove();
+                       });
+               },
+
+               setRead: function (message) {
+                       message.isRead = true;
+                       this.smsService.messageStorage.updateMessages([message]);
+               },
+
+               sendMessage: function (number, text, callback, errorcallback) {
+                       var message;
+                       callback = callback || new Function();
+                       errorcallback = errorcallback || new Function();
+                       message = new tizen.Message("messaging.sms", {plainBody: text, to: [number]});
+                       try {
+                               this.smsService.sendMessage(
+                                       message,
+                                       callback,
+                                       function (e) {
+                                               callback();
+                                               console.error("Could not send the message. Error: " + e.name + ": " + e.message, e);
+                                               errorcallback(e);
+                                       }
+                               );
+                       } catch (e) {
+                               callback();
+                               console.error("Could not send the message. Exception: " + e.name + ": " + e.message, e);
+                               errorcallback(e);
+                       }
+               },
+
+               loadContacts: function Model_loadContacts(callback) {
+                       var contactsFoundCB, errorCB;
+
+                       this.contactsLoaded = null;
+
+                       contactsFoundCB = function (contacts) {
+                               var i;
+                               this.contactsLoaded = [];
+                               for(i in contacts) {
+                                       if (contacts.hasOwnProperty(i)
+                                               && this.getNumberFromContact(contacts[i])) {
+                                               contacts[i].primaryNumber =
+                                                       this.getNumberFromContact(contacts[i])
+                                               this.contactsLoaded.push(contacts[i]);
+                                       }
+                               }
+                               if (callback instanceof Function) {
+                                       callback();
+                               }
+                       };
+
+                       errorCB = function (error) {
+                               console.error('Model_loadContacts, problem with find() method: ' + error.message);
+                       };
+
+                       this.addressBook.find(contactsFoundCB.bind(this), errorCB);
+               },
+
+               getNumberFromContact: function(contact) {
+                       if (!contact.phoneNumbers.length) {
+                               return false;
+                       }
+                       if (app.notNumberChars.test(contact.phoneNumbers[0].number)) {
+                               return false;
+                       }
+                       return contact.phoneNumbers[0].number;
+               }
+       };
+}());
\ No newline at end of file
diff --git a/project/js/app.ui.events.js b/project/js/app.ui.events.js
new file mode 100644 (file)
index 0000000..8435a9a
--- /dev/null
@@ -0,0 +1,179 @@
+/*jslint devel: true*/
+/*global $, tizen, app, window */
+/**
+ * @class UiEvents
+ */
+function UiEvents(parent) {
+       'use strict';
+
+       this.ui = parent;
+}
+
+(function () { // strict mode wrapper
+       'use strict';
+       UiEvents.prototype = {
+
+               /**
+                * Initialization
+                */
+               init: function UiEvents_init() {
+                       this.addPageEvents();
+                       this.enableFooterOnSoftKeyboard();
+               },
+
+               /**
+                * Bind events to pages
+                */
+               addPageEvents: function UiEvents_addPageEvents() {
+                       var self = this;
+
+                       $('#main-header .ui-btn-back').on('click', function (event) {
+                               event.preventDefault();
+                               event.stopPropagation();
+                               app.exit();
+                       });
+
+                       $('#chat-header').on('click', '.ui-btn-back', function (event) {
+                               event.preventDefault();
+                               event.stopPropagation();
+                               $.mobile.changePage('#main');
+                       });
+
+                       $('#contactSelect-header').on('click', '.ui-btn-back',
+                               function (event) {
+                                       event.preventDefault();
+                                       event.stopPropagation();
+                                       $.mobile.changePage('#main');
+                               }
+                       );
+
+                       $('#contactSelect-button').on('click', function (event) {
+                               event.preventDefault();
+                               event.stopPropagation();
+                               app.loadContacts();
+                       });
+
+                       $('#chat, #main').on('pageshow', function () {
+                               app.fillUpMessagePage();
+                       });
+
+                       $('#chat').on('pagebeforeshow', function () {
+                               app.resetPages();
+                               self.ui.clearChatList();
+                       });
+
+                       $('#contactSelect').on('pageshow', function () {
+                               app.showContactsLoaded();
+                       });
+
+                       $('#text').on('input', function () {
+                               self.ui.setChatCounterValue($(this),
+                                       app.model.getTextEncoding($(this).val()));
+                               self.ui.checkChatSendButtonState();
+                       });
+
+                       $('#enterNumber').on('click', function (event) {
+                               event.preventDefault();
+                               event.stopPropagation();
+                               $("#number").val('');
+                               $("#enterNumber-popup").popup('open', {positionTo: 'window'});
+                       });
+
+                       $('#enterNumberCreate').on('click', function () {
+                               $("#enterNumber-popup").popup('close');
+                               self.ui.createChatByNumber();
+                       });
+
+                       $('#enterNumber-popup').on('popupafterclose', function () {
+                               $("#number").val('');
+                               $("#enterNumberCreate")
+                                       .addClass('ui-disabled')
+                                       .attr('tabIndex', '-1')
+                                       .blur();
+                       });
+
+                       $("#number").on('keydown', function (e) {
+                               if (e.keyCode !== 0
+                                       && $.inArray(e.keyCode, app.allowKeys) === -1
+                                       && /([^0-9])/.test(String.fromCharCode(e.keyCode))
+                               ) {
+                                       e.preventDefault();
+                               }
+                       });
+
+                       $("#number").on('keyup focusout input', function (e) {
+                               if ($.inArray(e.keyCode, app.allowKeys) === -1) {
+                                       var newvalue = $(this).val()
+                                               .replace(app.notNumberChars, '');
+                                       if (newvalue !== $(this).val()) {
+                                               $(this).val(newvalue);
+                                       }
+                               }
+                               if ($(this).val().length === 0) {
+                                       $("#enterNumberCreate")
+                                               .addClass('ui-disabled')
+                                               .attr('tabIndex', '-1')
+                                               .blur();
+                               } else {
+                                       $("#enterNumberCreate")
+                                               .removeClass('ui-disabled')
+                                               .attr('tabIndex', '0');
+                               }
+                       });
+
+                       $('#send').on('click', function (event) {
+                               var text = $('#text').blur().val();
+                               event.stopPropagation();
+                               self.ui.resetTextAreas();
+                               self.ui.setChatCounterValue();
+                               self.ui.checkChatSendButtonState();
+                               app.sendMessage(text, [app.getCurrentNumber()]);
+                       });
+
+                       $('#main-content .ui-listview').on('click',
+                                       'li.ui-li-has-multiline', function (event) {
+                               self.ui.onCallerListElementTap(event, $(this));
+                       });
+
+                       window.addEventListener('tizenhwkey', function(e) {
+                               if (e.keyName === 'back') {
+                                       if ($.mobile.popup.active) {
+                                               $.mobile.popup.active.close();
+                                       } else if ($.mobile.activePage.attr('id') === 'main') {
+                                               tizen.application.getCurrentApplication().exit();
+                                       } else if ($.mobile.activePage.attr('id') === 'chat') {
+                                               $.mobile.changePage('#main');
+                                       } else {
+                                               history.back();
+                                       }
+                               }
+                       });
+
+                       document.addEventListener("webkitvisibilitychange", function(){
+                               if (document.webkitVisibilityState === 'visible') {
+                                       app.model.loadContacts(app.fillUpMessagePage.bind(app));
+                                       app.model.prepareMessages.bind(app.model)(function () {
+                                               app.fillUpMessagePage.bind(app.model)();
+                                               app.model.prepareMessages.bind(app.model)();
+                                       });
+                               }
+                       });
+
+                       $(window).on('resize', function () {
+                               $.mobile.activePage.page('refresh');
+                       });
+
+                       /* workaround for UIFW & webkit scroll*/
+                       $('.ui-page').css('min-height', 0);
+               },
+
+               /* WORKAROUND for the footer show when the soft keyboard is displayed */
+               enableFooterOnSoftKeyboard: function enableFooterOnSoftKeyboard() {
+                       window.addEventListener('softkeyboardchange', function (event) {
+                               event.stopPropagation();
+                               app.ui.scrollToBottom();
+                               app.ui.clearSendButton();
+                       });
+               }
+       };
+}());
diff --git a/project/js/app.ui.js b/project/js/app.ui.js
new file mode 100644 (file)
index 0000000..ad1a8b4
--- /dev/null
@@ -0,0 +1,327 @@
+/*global $, document, console, tizen, app, TemplateManager, UiEvents */
+/**
+ * @class Ui
+ */
+
+function Ui() {
+       'use strict';
+       this.init();
+}
+
+(function () { // strict mode wrapper
+       'use strict';
+       Ui.prototype = {
+
+               templateManager: null,
+
+               /**
+                * UI object for UI events
+                */
+               uiEvents: null,
+
+               /**
+                * UI module initialisation
+                */
+               init: function Ui_init() {
+                       this.templateManager = new TemplateManager();
+                       this.uiEvents = new UiEvents(this);
+                       $(document).ready(this.domInit.bind(this));
+                       // Disable selection event on document;
+                       $.mobile.tizen.disableSelection(document);
+               },
+
+               /**
+                * When DOM is ready, initialise it (bind events)
+                */
+               domInit: function Ui_domInit() {
+                       var templates = ['main_page',
+                                                       'chat',
+                                                       'callerRow',
+                                                       'contactSelect',
+                                                       'contactRow',
+                                                       'normalBubble'];
+                       this.templateManager.loadToCache(templates, this.initPages.bind(this));
+               },
+
+               initPages: function Ui_initPages() {
+                       var pages = [];
+
+                       app.model.init();
+
+                       $('#main').append($(this.templateManager.get('main_page')).children()).trigger('pagecreate');
+
+                       pages.push(this.templateManager.get('contactSelect'));
+                       pages.push(this.templateManager.get('chat'));
+                       $('body').append(pages.join(''));
+
+                       this.uiEvents.init();
+
+                       this.setChatCounterValue();
+                       this.checkChatSendButtonState();
+               },
+
+               scrollToBottom: function (noCorrection) {
+                       var talk = $("#chat-content .ui-scrollview-view"),
+                               heightDiff = talk.height() - talk.parent().height();
+                       noCorrection = noCorrection || false;
+                       if (heightDiff > 0) {
+                               setTimeout(function () {
+                                       $('#chat-content').scrollview('scrollTo', 100, -99999, 0);
+                               }, 300);
+                       }
+                       if (!noCorrection) {
+                               setTimeout(function () {
+                                       app.ui.scrollToBottom(true);
+                               }, 200);
+                       }
+                       $('.ui-overflow-indicator-bottom').css('opacity', 0);
+               },
+
+               setChatTitle: function Ui_setChatTitle(title) {
+                       $('#chat-title').text(title);
+               },
+
+               resetTextAreas: function Ui_resetTextAreas() {
+                       $('#text').val('');
+                       $("input[name='number']").val('');
+                       $("#enterNumberCreate")
+                               .addClass('ui-disabled')
+                               .attr('tabIndex', '-1')
+                               .blur();
+               },
+
+               setChatCounterValue: function (obj, encoding) {
+                       encoding = encoding || 'gsm';
+                       obj = obj || $('#text');
+                       this.setCounterValue(obj, encoding);
+               },
+
+               setCounterValue: function (obj, encoding) {
+                       var current = app.model.getTextLength(obj.val(), encoding),
+                               message = app.maxMessageLength[encoding], numberOfMessages,
+                               charLeft, totalMax = app.totalMessageLength[encoding],
+                               newline = obj.val().split('\n').length - 1;
+
+                       if (current <= message) {
+                               charLeft = message - current;
+                               numberOfMessages = 1;
+                       } else {
+                               message -= app.multiMessageHeaderLength[encoding];
+                               charLeft = current % message;
+                               if (charLeft !== 0) {
+                                       charLeft = message - charLeft;
+                               }
+                               numberOfMessages = Math.ceil(current / message);
+                       }
+                       obj.attr('maxlength',  totalMax - (current - obj.val().length) + newline)
+                               .parent().next().find('.counter p')
+                               .html(charLeft + '<span>/' + numberOfMessages + '</span>');
+               },
+
+               checkChatSendButtonState: function Ui_checkChatSendButtonState() {
+                       if (app.helpers.checkStringLength($('#text').val())) {
+                               $('#send')
+                                       .css({
+                                               'pointer-events': 'auto',
+                                               'color': '#000'
+                                       })
+                                       .removeClass('ui-disabled')
+                                       .attr('tabIndex', '0');
+                       } else {
+                               $('#send').css({'pointer-events': 'none', 'color': '#bbb'})
+                                       .addClass('ui-disabled')
+                                       .attr('tabIndex', '-1')
+                                       .blur();
+                       }
+               },
+
+               clearSendButton: function () {
+                       $(".ui-btn-down-s").removeClass('ui-btn-down-s');
+               },
+
+               resetPages: function () {
+                       this.resetTextAreas();
+                       this.setChatCounterValue();
+                       this.checkChatSendButtonState();
+               },
+
+               clearChatList: function () {
+                       $('#chat-title').html('');
+                       $('#message-chat').empty();
+               },
+
+               clearCallerList: function () {
+                       return $('#main-content .ui-listview').empty();
+               },
+
+               onCallerListElementTap: function (event, element) {
+                       event.preventDefault();
+                       event.stopPropagation();
+                       app.setCurrentNumber(element.attr('phone'));
+                       app.setCurrentCaller(element.attr('caller'));
+                       $.mobile.changePage('#chat');
+               },
+
+               fillContactList: function Ui_fillContactList(sortedContactList) {
+                       var i, ul = $("#contactSelect-list").empty(),
+                               len, listElement, self = this;
+
+                       len = sortedContactList.length;
+
+                       for (i = 0; i < len; i += 1) {
+                               listElement = this.templateManager.get('contactRow', {
+                                       'number': sortedContactList[i].number,
+                                       'callerName': sortedContactList[i].caller
+                               });
+
+                               if (app.helpers.validateNumberLength(sortedContactList[i].number)) {
+                                       ul.append(listElement);
+                               }
+                       }
+
+                       $('li.ui-li-has-multiline', ul).on('click', function (event) {
+                               app.ui.onCallerListElementTap(event, $(this));
+                       });
+
+                       ul.trigger('create');
+                       ul.listview('refresh');
+               },
+
+               loadCallerList: function () {
+                       var ul, i, date, caller, message;
+                       ul = this.clearCallerList();
+                       for (i in app.model.messagesList) {
+                               if (app.model.messagesList.hasOwnProperty(i)) {
+                                       caller = '';
+                                       message = app.model.messagesList[i];
+                                       date = new Date(message.lastMessage.timestamp);
+
+                                       caller = app.model.getNameByNumber(i);
+                                       try {
+                                               ul.append(
+                                                       this.templateManager.get('callerRow', {
+                                                               'number': i,
+                                                               'callerName': caller,
+                                                               'plainBody': message.lastMessage.body.plainBody
+                                                                       .replace(/^\s+|\s+$/g, '')
+                                                                       .replace(/\s+/g, ' ')
+                                                                       .substring(0, 50),
+                                                               'date': app.helpers.getDate(date),
+                                                               'hour': date.getHours(),
+                                                               'minutes': app.helpers
+                                                                       .addZeroBefore(date.getMinutes())
+                                                       })
+                                               );
+                                       } catch (err) {
+                                               console.log(err);
+                                       }
+                               }
+                       }
+                       ul.listview('refresh');
+               },
+
+               createChatByNumber: function () {
+                       var phoneValue = $("#number").blur()
+                               .val().replace(app.notNumberChars,'');
+                       app.setCurrentNumber(phoneValue);
+                       app.setCurrentCaller(app.model.getNameByNumber(phoneValue));
+                       // workaround fix N_SE-47570 - need time to blur #number
+                       setTimeout(function () {
+                               $.mobile.changePage('#chat');
+                       }, 100);
+               },
+
+               showMessageChat: function () {
+                       var listedDay = null, ul, li, i, date, data,
+                               key = app.getCurrentNumber(), messages;
+                       if (document.webkitVisibilityState === 'visible'
+                               && $.mobile.activePage.attr('id') === 'chat') {
+                               $('#chat-title').text(app.getCurrentCaller());
+                               ul = $('#message-chat').empty();
+
+                               if (app.model.messagesList[key] !== undefined) {
+                                       messages = app.model.messagesList[key].messages;
+                                       i = messages.length;
+                                       while ((i -= 1) >= 0) {
+                                               date = new Date(messages[i].timestamp);
+
+                                               data = {
+                                                       'bubbleType': messages[i]
+                                                               .to.length === 0 ? 'left' : 'right',
+                                                       'label': app
+                                                               .model.getNameByNumber(messages[i].from),
+                                                       'caller': messages[i].caller,
+                                                       'id': messages[i].id,
+                                                       'plainBody': messages[i].body.plainBody,
+                                                       'date': app.helpers.getDate(date),
+                                                       'hour': date.getHours(),
+                                                       'minutes': app
+                                                               .helpers.addZeroBefore(date.getMinutes()),
+                                                       'status': messages[i].messageStatus
+                                               };
+
+                                               if (!messages[i].isRead) {
+                                                       app.model.setRead(messages[i]);
+                                               }
+
+                                               li = $(app.ui.templateManager
+                                                                       .get('normalBubble', data));
+                                               if (messages[i].messageStatus === 'DRAFT') {
+                                                       li.one('click', app.model.editDraft
+                                                                       .bind(app.model, messages[i], li));
+                                               }
+                                               ul.append(li);
+                                       }
+                                       ul.listview('refresh');
+                                       app.ui.scrollToBottom();
+                               }
+                       }
+               },
+
+               changeMessageStatus: function (message, loop) {
+                       var warning = $('#' + message.id + ' .warning'),
+                               classes, i, self = this;
+                       loop = loop + 1 || 0;
+                       if (warning.length === 1) {
+                               classes = warning.attr('class').split(' ');
+                               for(i in classes) {
+                                       if (classes.hasOwnProperty(i)) {
+                                               if (/status([A-Z]*)/.test(classes[i])) {
+                                                       warning.removeClass(classes[i]);
+                                               }
+                                       }
+                               }
+                               warning.addClass('status' + message.messageStatus);
+                       } else if (loop < 3) {
+                               setTimeout(function () {
+                                       self.changeMessageStatus(message, loop)
+                               }, 1000);
+                       }
+               },
+
+               showChatPage: function Ui_showChatPage() {
+                       this.setChatTitle(app.getCurrentCaller());
+                       $.mobile.changePage('#chat');
+               },
+
+               showMainPage: function Ui_showMainPage() {
+                       $.mobile.changePage('#main');
+               },
+
+               alertPopup: function Ui_alertPopup(text) {
+                       setTimeout(
+                               function() {
+                                       $("#alertPopup .text").html(text);
+                                       $("#alertPopup").popup('open', {'positionTo': 'window'});
+                               },
+                               200
+                       );
+               },
+
+               showSendErrorPopup: function Ui_showSendErrorPopup(message) {
+                       app.ui.alertPopup("Could not send the message.<br>" + message);
+               }
+
+       };
+
+}());
diff --git a/project/js/app.ui.templateManager.js b/project/js/app.ui.templateManager.js
new file mode 100644 (file)
index 0000000..59802a6
--- /dev/null
@@ -0,0 +1,142 @@
+/*global tizen, $, app, ModifierManager */
+/**
+ * @class TemplateManager
+ */
+function TemplateManager() {
+       'use strict';
+       this.init();
+}
+
+(function () { // strict mode wrapper
+       'use strict';
+       TemplateManager.prototype = {
+
+               /**
+                * Template cache
+                */
+               cache: {},
+
+               /**
+                * UI module initialisation
+                */
+               init: function init() {
+                       this.modifiers = new ModifierManager().getAll();
+               },
+
+               /**
+                * Returns template html (from cache)
+                * @param {string} tplName
+                * @param {string} tplParams
+                */
+               get: function TemplateManager_get(tplName, tplParams) {
+                       if (this.cache[tplName] !== undefined) {
+                               return this.getCompleted(this.cache[tplName], tplParams);
+                       } else {
+                               console.error('template not cached');
+                       }
+                       return '';
+               },
+
+               /**
+                * Load templates to cache
+                * @param {string} tplNames
+                * @param {function} onSuccess
+                */
+               loadToCache: function TemplateManager_loadToCache(tplNames, onSuccess) {
+                       var self = this,
+                               cachedTemplates = 0,
+                               tplName,
+                               tplPath;
+
+                       if ($.isArray(tplNames)) {
+
+                               // for each template
+                               $.each(tplNames, function (index, fileName) {
+
+                                       // cache template html
+                                       if (self.cache[fileName] === undefined) {
+                                               tplName = [fileName, app.config.get('templateExtension')].join('');
+                                               tplPath = [app.config.get('templateDir'), tplName].join('/');
+
+                                               $.ajax({
+                                                       url: tplPath,
+                                                       cache: true,
+                                                       dataType: 'html',
+                                                       async: true,
+                                                       success: function (data) {
+                                                               // increase counter
+                                                               cachedTemplates += 1;
+
+                                                               // save to cache
+                                                               self.cache[fileName] = data;
+
+                                                               // if all templates are cached launch callback
+                                                               if (cachedTemplates >= tplNames.length && typeof onSuccess === 'function') {
+                                                                       onSuccess();
+                                                               }
+                                                       },
+                                                       error: function (jqXHR, textStatus, errorThrown) {
+                                                               console.error('templateManagerError: ' + errorThrown);
+                                                       }
+                                               });
+                                       } else {
+                                               // template is already cached
+                                               cachedTemplates += 1;
+                                               // if all templates are cached launch callback
+                                               if (cachedTemplates >= tplNames.length && typeof onSuccess === 'function') {
+                                                       onSuccess();
+                                               }
+                                       }
+                               });
+
+                       }
+               },
+
+               /**
+                * Returns template completed by specified params
+               * @param {string} tplHtml
+               * @param {string} tplParams
+                */
+               getCompleted: function TemplateManager_getCompleted(tplHtml, tplParams) {
+                       var tplParam;
+
+                       for (tplParam in tplParams) {
+                               if (tplParams.hasOwnProperty(tplParam)) {
+                                       tplHtml = this.passThruModifiers(tplHtml, tplParam, tplParams[tplParam]);
+                               }
+                       }
+
+                       return tplHtml;
+               },
+
+               passThruModifiers: function (tplHtml, tplParam, content) {
+                       var regModOn = new RegExp('%' + tplParam + '(\\|(.+?)){1,}%', 'g'),
+                               regModOff = new RegExp(['%', tplParam, '%'].join(''), 'g'),
+                               regModGet = new RegExp('%' + tplParam + '\\|(.+?)%'),
+                               regModPut = new RegExp('%' + tplParam + '\\|(.+?)%', 'g'),
+                               specRegExp = new RegExp('\\$','g'),
+                               modifiers, i;
+
+                       if (content && (typeof content === 'string')) {
+                               content = content.replace(specRegExp, '$$$$');
+                       }
+
+                       if (regModOn.test(tplHtml)) {
+                               modifiers = tplHtml.match(regModGet)[1].split('|');
+                               for (i in modifiers) {
+                                       if (this.modifiers[modifiers[i]] instanceof Function){
+                                               content = this.modifiers[modifiers[i]](content);
+                                       } else {
+                                               console.error('unknown modifier: ' + modifiers[i]);
+                                       }
+                               }
+                               tplHtml = tplHtml.replace(regModPut, content);
+                       } else {
+                               tplHtml = tplHtml.replace(regModOff, content);
+                       }
+
+                       return tplHtml;
+               }
+       };
+
+}());
\ No newline at end of file
diff --git a/project/js/app.ui.templateManager.modifiers.js b/project/js/app.ui.templateManager.modifiers.js
new file mode 100644 (file)
index 0000000..7a10a75
--- /dev/null
@@ -0,0 +1,40 @@
+/*global $*/
+/**
+ * @class ModifierManager
+ */
+function ModifierManager() {
+       'use strict';
+       this.init();
+}
+
+(function () {
+       'use strict';
+       ModifierManager.prototype = {
+
+               /**
+                * UI module initialisation
+                */
+               init: function () {
+               },
+
+               /**
+                * @return modifiers object
+                */
+               getAll: function () {
+                       return this.modifiers;
+               },
+
+               /**
+                * modifiers definitions
+                */
+               modifiers: {
+                       escape: function escape(str) {
+                               return $('<span>').text(str).html();
+                       },
+
+                       nl2br: function escape(str) {
+                               return str.replace(/\n/g, '<br/>');
+                       }
+               }
+       };
+}());
\ No newline at end of file
diff --git a/project/js/main.js b/project/js/main.js
new file mode 100644 (file)
index 0000000..5169b5c
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ *      Copyright 2013  Samsung Electronics Co., Ltd
+ *
+ *      Licensed under the Flora License, Version 1.1 (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 $, tizen, App */
+var app = null;
+
+(function () { // strict mode wrapper
+       'use strict';
+
+       ({
+               /**
+                * Loader init - load the App constructor
+                */
+               init: function init() {
+                       var self = this;
+                       $.getScript('js/app.js')
+                               .done(function () {
+                                       // once the app is loaded, create the app object
+                                       // and load the libraries
+                                       app = new App();
+                                       self.loadLibs();
+                               })
+                               .fail(this.onGetScriptError);
+               },
+
+               /**
+                * Load dependencies
+                */
+               loadLibs: function loadLibs() {
+                       var loadedLibs = 0;
+                       if ($.isArray(app.requires)) {
+                               $.each(app.requires, function (index, filename) {
+                                       $.getScript(filename)
+                                               .done(function () {
+                                                       loadedLibs += 1;
+                                                       if (loadedLibs >= app.requires.length) {
+                                                               // All dependencies are loaded - initialise the app
+                                                               app.init();
+                                                       }
+                                               })
+                                               .fail(this.onGetScriptError);
+                               });
+                       }
+               },
+
+               /**
+                * Handle ajax errors
+                */
+               onGetScriptError: function onGetScriptError(e, jqxhr, setting, exception) {
+                       console.error('An error occurred: ' + e.message);
+               }
+       }).init(); // run the loader
+}());
diff --git a/project/templates/callerRow.tpl b/project/templates/callerRow.tpl
new file mode 100644 (file)
index 0000000..8fe3f34
--- /dev/null
@@ -0,0 +1,7 @@
+<li class="ui-li-has-multiline" phone="%number%" caller="%callerName|escape%">
+       <a href="#">
+               <div class="callerName">%callerName|escape%</div>
+               <span class="ui-li-text-sub body">%plainBody|escape%</span>
+               <span class="ui-li-text-sub2 hour">%date%<br/>%hour%:%minutes%</span>
+       </a>
+</li>
\ No newline at end of file
diff --git a/project/templates/chat.tpl b/project/templates/chat.tpl
new file mode 100644 (file)
index 0000000..6623cbb
--- /dev/null
@@ -0,0 +1,28 @@
+<div id="chat" data-role="page">
+       <div id="chat-header" data-role="header" data-position="fixed">
+               <h1 id="chat-title"></h1>
+       </div>
+
+       <div id="chat-content" data-role="content">
+               <ul data-role="listview" id="message-chat"></ul>
+       </div>
+
+       <div id="chat-footer" data-role="footer" data-position="fixed">
+               <div class="ui-textArea">
+                       <div class="ui-textArea-text">
+                               <textarea id="text" class="ui-textArea-text-text" placeholder="Your message" data-role="none"></textarea>
+                       </div>
+                       <div class="ui-textArea-button">
+                               <a data-role="button" id="send">Send</a>
+                               <div data-role="none" id="counter" class="counter"><p></p></div>
+                       </div>
+               </div>
+       </div>
+
+       <div data-role="popup" id="alertPopup" class="ui-corner-all">
+               <p class="text"></p>
+               <div class="alertPopup-button">
+                       <a href="#" data-role="button" data-inline="true" data-rel="back">OK</a>
+               </div>
+       </div>
+</div>
\ No newline at end of file
diff --git a/project/templates/contactRow.tpl b/project/templates/contactRow.tpl
new file mode 100644 (file)
index 0000000..b6326b3
--- /dev/null
@@ -0,0 +1,3 @@
+<li class="ui-li-has-multiline" phone="%number%" caller="%callerName|escape%">
+       <a href="#">%callerName|escape%<span class="ui-li-text-sub">%number%</span></a>
+</li>
\ No newline at end of file
diff --git a/project/templates/contactSelect.tpl b/project/templates/contactSelect.tpl
new file mode 100644 (file)
index 0000000..e5a1818
--- /dev/null
@@ -0,0 +1,24 @@
+<div id="contactSelect" data-role="page">
+       <div id="contactSelect-header" data-role="header" data-position="fixed">
+               <h1>Select contact</h1>
+       </div>
+
+       <div data-role="content">
+               <ul data-role="listview" id="contactSelect-list"></ul>
+               <a href="#" id="enterNumber" data-role="button">Enter number</a>
+       </div>
+
+       <div data-role="popup" id="enterNumber-popup">
+               <div class="ui-popup-title">
+                       <h1>Enter phone number<h1>
+               </div>
+               <div class="ui-popup-text">
+                       <form id="numberForm">
+                               <input type="tel" id="number" name="number" maxlength="20"/>
+                       </form>
+               </div>
+               <div class="ui-popup-button-bg">
+                       <a data-role="button" id="enterNumberCreate" data-rel="back" data-inline="true" class="ui-disabled">Create Chat</a>
+               </div>
+       </div>
+</div>
diff --git a/project/templates/main_page.tpl b/project/templates/main_page.tpl
new file mode 100644 (file)
index 0000000..69e083e
--- /dev/null
@@ -0,0 +1,17 @@
+<div id="main" data-role="page" data-footer-exist="true">
+       <div id="main-header" data-role="header" data-position="fixed">
+               <h1>Chatter</h1>
+       </div>
+
+       <div id="main-content" data-role="content">
+               <ul data-role="listview"></ul>
+       </div>
+
+       <div id="main-footer" data-role="footer" data-position="fixed">
+               <div data-role="tabbar" data-style="toolbar">
+                               <ul>
+                                       <li><a href="#" id="contactSelect-button">New chat</a></li>
+                               </ul>
+                       </div>
+       </div>
+</div>
\ No newline at end of file
diff --git a/project/templates/normalBubble.tpl b/project/templates/normalBubble.tpl
new file mode 100644 (file)
index 0000000..f47e4ff
--- /dev/null
@@ -0,0 +1,5 @@
+<li class="ui-li-bubble-%bubbleType%" id="%id%">
+       <span class="text">%plainBody|escape|nl2br%</span>
+       <span class="warning status%status%"></span>
+       <span class="ui-li-bubble-time">%date%<br/>%hour%:%minutes%</span>
+</li>
\ No newline at end of file
diff --git a/tizen-app-template.xml b/tizen-app-template.xml
new file mode 100755 (executable)
index 0000000..ad28d76
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<tizen-app-template xmlns="http://www.tizen.org/tizen-app-template" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" template-version="" sdk-version="" icon32="tizen_32.png" icon64="tizen_64.png" xsi:schemaLocation="http://www.tizen.org/tizen-app-template tizen-app-template.xsd ">
+    <template-name>Chatter</template-name>
+    <widget-type>TIZEN</widget-type>
+    <build-property key="usedLibraryType" value="WebUIFramework"/>
+    <description-file-name>description.xml</description-file-name>
+    <options>
+        <supportLibraries>
+          <library name="Tizen Web UI Framework"/>
+        </supportLibraries>
+    </options>
+</tizen-app-template>
diff --git a/tizen_32.png b/tizen_32.png
new file mode 100644 (file)
index 0000000..61f35c0
Binary files /dev/null and b/tizen_32.png differ
diff --git a/tizen_64.png b/tizen_64.png
new file mode 100644 (file)
index 0000000..b188083
Binary files /dev/null and b/tizen_64.png differ