--- /dev/null
+<?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
--- /dev/null
+<?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[ ]]></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>
--- /dev/null
+<?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>
--- /dev/null
+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>
--- /dev/null
+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.
+
--- /dev/null
+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.
+
--- /dev/null
+<?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>
--- /dev/null
+#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
--- /dev/null
+<!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
--- /dev/null
+/*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;
+ }
+ };
+}());
--- /dev/null
+/*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;
+ }
+ };
+}());
--- /dev/null
+/*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
+ );
+ }
+ };
+}());
--- /dev/null
+/*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
--- /dev/null
+/*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();
+ });
+ }
+ };
+}());
--- /dev/null
+/*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);
+ }
+
+ };
+
+}());
--- /dev/null
+/*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
--- /dev/null
+/*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
--- /dev/null
+/*
+ * 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
+}());
--- /dev/null
+<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
--- /dev/null
+<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
--- /dev/null
+<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
--- /dev/null
+<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>
--- /dev/null
+<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
--- /dev/null
+<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
--- /dev/null
+<?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>