From: Piotr Dabrowski Date: Wed, 2 Oct 2013 08:19:28 +0000 (+0200) Subject: application sources from tizen_2.2 X-Git-Url: http://review.tizen.org/git/?p=apps%2Fweb%2Fsample%2FCallLog.git;a=commitdiff_plain;h=d9b811b7a0184c4fdba886da26ab2862ca922128 application sources from tizen_2.2 Change-Id: I59c808dcbf2eae37a445bdfbc5464b2f41ebd8d4 --- diff --git a/calllog-snapshot.png b/calllog-snapshot.png new file mode 100644 index 0000000..ad4d77c Binary files /dev/null and b/calllog-snapshot.png differ diff --git a/description.xml b/description.xml new file mode 100755 index 0000000..f95d288 --- /dev/null +++ b/description.xml @@ -0,0 +1,10 @@ + + + + CallLog + 1.0.0 + calllog-snapshot.png + + A sample application demonstrating the tizen device API usage. + + diff --git a/description.xsl b/description.xsl new file mode 100755 index 0000000..bb92b88 --- /dev/null +++ b/description.xsl @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + +
+ + + +
+ Type: JavaScript +

+ +

+
+ + + + + +
+ + +
+ +
diff --git a/project/.project b/project/.project new file mode 100644 index 0000000..0b84754 --- /dev/null +++ b/project/.project @@ -0,0 +1,59 @@ + + + CallLog + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + json.validation.builder + + + + + org.tizen.web.jslint.nature.JSLintBuilder + + + + + org.tizen.web.css.nature.CSSBuilder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.tizen.web.project.builder.WebBuilder + + + + + org.tizen.web.privilege.nature.PrivilegeBuilder + + + + + org.tizen.web.editor.css.nature.CSSBuilder + + + + + + json.validation.nature + org.tizen.web.jslint.nature.JSLintNature + org.tizen.web.css.nature.CSSNature + org.eclipse.wst.jsdt.core.jsNature + org.eclipse.wst.common.project.facet.core.nature + org.tizen.web.project.builder.WebNature + org.tizen.web.privilege.nature.PrivilegeNature + org.tizen.web.editor.css.nature.CSSNature + + diff --git a/project/AUTHORS b/project/AUTHORS new file mode 100644 index 0000000..31f0274 --- /dev/null +++ b/project/AUTHORS @@ -0,0 +1,6 @@ +Dariusz Paziewski +Tomasz Lukawski +Piotr Wronski +Pawel Sierszen +Artur Kobylinski +Tomasz Paciorek diff --git a/project/LICENSE.Flora b/project/LICENSE.Flora new file mode 100644 index 0000000..4a0af40 --- /dev/null +++ b/project/LICENSE.Flora @@ -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 index 0000000..092bc04 --- /dev/null +++ b/project/NOTICE @@ -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 index 0000000..6442a3b --- /dev/null +++ b/project/config.xml @@ -0,0 +1,13 @@ + + + + + + Call Log + + + + + + + diff --git a/project/css/style.css b/project/css/style.css new file mode 100644 index 0000000..e613077 --- /dev/null +++ b/project/css/style.css @@ -0,0 +1,404 @@ +html,body { + margin: 0px 0px; + width: 100%; + height: 100%; + color: #fff; + font-family: Lucida Sans, Arial, Helvetica, sans-serif; + overflow: hidden; +} + +ul.calllogList li.date { + background-color: #424242; + color: #9B9B9B; + height: 22px; + font-size: 15px; + padding-left: 10px; + padding-top: 5px; +} + +ul { + list-style-type: none; + margin: 0; + padding: 0; + padding-bottom: 60px; + width: 100%; + /*float: left;*/ +} + +li { + border-bottom: 1px solid #5A99BA; + padding: 8px 8px 6px 8px; + background-color: #FFF; + /* + float: left; + width: 100%; + */ +} +li.ui-screen-hidden{ + display: none !important; +} + +#delete span { + border-bottom: 0px; +} + +.hidden { + display: none !important; +} + +.call .numberOrName { + white-space: nowrap; + text-overflow: ellipsis; + width: 95%; + overflow: hidden; + font-size: 20px; +} + +.date { + background-color: #5A99BA; + color: #FFF; +} + +.call { + -moz-border-radius-bottomleft: 0; + -webkit-border-bottom-left-radius: 0; + border-bottom-left-radius: 0; + -moz-border-radius-bottomright: 0; + -webkit-border-bottom-right-radius: 0; + border-bottom-right-radius: 0; +} + +.call .callTime { + font-size: 14px; + color: #646464; + margin-left: 20px; + /*float: left;*/ + margin-top: 2px; +} + +.call .callAllTime { + font-size: 20px; +} + +.call .callDuration { + font-size: 14px; + color: #646464; + margin-left: 20px; + /*float: left;*/ + margin-top: 2px; +} + +.footer { + background-color: #242F36; + height: 45px; +} + +.details { + background-color: #5A99BA; + height: 30px; + color: white; + text-align: center; + padding-top: 10px; + margin-top: 0px; +} + +.detailsView { + background-color: #0f0f0f; +} + +.contact { + border-bottom: 1px solid #5A99BA; + background-color: #FFF; + height: 110px; +} + +.contact .photo { + width: 50px; + height: 50px; + background-image: url('../images/no_photo.png'); + background-repeat: no-repeat; + margin: 8px; + float: left; + background-size: 50px 50px; + border: 1px solid rgba(0, 0, 0, 0.1); +} + +.contact .name { + white-space: nowrap; + text-overflow: ellipsis; + width: 75%; + overflow: hidden; + font-size: 20px; + padding-top: 10px; + color: #555; +} + +.contact .number { + font-size: 20px; + color: #948676; + clear: right; +} + +.contact .number .numberOfEntries { + font-size: 16px; +} + +.contact .infoContainer { + width: 100%; + height: 66px; +} + +.contact .options { + clear: both; + margin: 0px 0px 8px 8px; + float: left; + width: 100%; +} + +.contact .options p { + float: left; + margin: 5px 5px 5px 10px; + border: 1px solid #2F2F2F; + padding: 4px; + width: 25%; + text-align: center; + background-color: black; + border-radius: 3px; + border-color: #090909 #1F1F1F #1F1F1F #090909; + font-size: 28px; +} + +.options .actionButton { + width: 43%; +} + +#historyForCallerView .actionButton.ui-btn-down-s .ui-btn-inner { + color:inherit; +} +.trash { + background-image: url(../images/trash.png); + width: 21px; + height: 100%; + float: left; + margin: 15px; + background-size: 100%; + background-repeat: no-repeat; +} + +#detailsStandardFooter { + height: 100%; +} + +#detailsRemoveFooter { + height: 80px; +} + +.trashDisabled { + background-image: url(../images/trash.png); + width: 18px; + height: 20px; + float: left; + margin: 13px; + background-size: 18px; + opacity: 0.5; +} + +.cancel { + width: 64px; + height: 27px; + float: right; + border: 0; + margin-right: 15px; + margin-top: 8px; + background-color: #353535; + color: #fff; + border-radius: 4px; + width: 200px; + height: 80px; + font-size: 44px; + float: right; + margin-top: 18px; + margin-right: 10px; +} + +.call .iconStatus { + width: 14px; + height: 14px; + float: left; + background-size: 14px; + margin-top: 2px; +} + +.call.type_tizen-tel .iconStatus { + background-image: url('../images/logs_list_call_icon.png'); +} + +.call.dir_dialed .iconStatus { + background-image: url('../images/logs_list_dialled_icon.png'); +} + +.call.dir_missed .iconStatus { + background-image: url('../images/logs_list_missed_icon.png'); +} + +.call.dir_missednew .iconStatus { + background-image: url('../images/logs_list_missed_unchecked_icon.png'); +} + +.call.dir_received .iconStatus { + background-image: url('../images/logs_list_received_icon.png'); +} + +.call.dir_rejected .iconStatus { + background-image: url('../images/logs_list_rejected_icon.png'); +} + +.call.dir_blocked .iconStatus { + background-image: url('../images/logs_list_blocked_icon.png'); +} + +.noPhoto { + background-image: url('../images/no_photo.png'); +} + +.toRemove { + float: left; + background-size: 100%; + border-radius: 4px; + margin-right: 5px; +} + +.toRemove input { + opacity: 0; + width: 20px; + height: 20px; +} + +.toRemove .ui-checkbox { + position: relative !important; + height: auto; +} + +.checked { + background-image: url('../images/checkboxChecked.png'); +} + +.selectAllBox { + background: #5A99BA; + float: left; + height: 33px; + width: 100%; + border-bottom: solid 1px white; + padding: 5px 0; +} + +.selectAllBox .toRemove { + margin-top: 0px; + height: 30px; + width: 40px; + padding-left: 4px; +} + +.selectAllBox p { + float: left; + margin: 5px 0 0; + color: #FFF; +} + +.selectedCount { + height: 20px; + text-align: center; + background-color: #424242; + padding: 4px 0; +} + +.calllogList .toRemove { + display: none; +} + +.find { + height: 40px; + text-align: left; + background-color: #424242; + padding: 4px 0; +} + +.find input { + width: 200px; + float: left; +} + +.ui-image-search { + width: auto; + right: 0; + left: auto; +} + +.ui-checkbox, .ui-radio { + height: 0; +} + +.my-ui-checkbox { + position: relative; + float: left; + margin-top: -1rem; + margin-right: 1.5rem; + left: 0rem; +} + +.ui-listview { + margin: 0px; + padding: 0px; + border-top-width: 0px; +} + +.input-search-bar { + margin: 0px; +} + +.ui-content.ui-scrollview-clip > div.ui-scrollview-view { + margin: 0px; + padding: 0px; +} + +.ui-content .ui-listview { + margin: 0px; + padding: 0px; +} + +.ui-controlbar-s, .ui-controlbar-left, .ui-controlbar-right { + border: 0px; +} + +#header.ui-bar-s .ui-btn { + /*width: 100px; // this property changed back button width*/ +} + +.ui-popupwindow { + color: black; + padding:0.4em; +} + +#popupCancelActionBtn { + width: 80px; +} + +#popupSubmitActionBtn { + width: 80px; +} + +#popupMessage { + margin-bottom: 10px; +} + +#page-header a { + text-decoration: none; +} + +.ui-footer .ui-btn-text { + text-transform: uppercase; +} + +.date.ui-corner-top, .date.ui-corner-bottom { + border-radius: 0; +} diff --git a/project/icon.png b/project/icon.png new file mode 100644 index 0000000..5934757 Binary files /dev/null and b/project/icon.png differ diff --git a/project/images/address_minus_btn.png b/project/images/address_minus_btn.png new file mode 100644 index 0000000..96f514b Binary files /dev/null and b/project/images/address_minus_btn.png differ diff --git a/project/images/address_plus_btn.png b/project/images/address_plus_btn.png new file mode 100644 index 0000000..e23e6ff Binary files /dev/null and b/project/images/address_plus_btn.png differ diff --git a/project/images/checkboxChecked.png b/project/images/checkboxChecked.png new file mode 100644 index 0000000..3482362 Binary files /dev/null and b/project/images/checkboxChecked.png differ diff --git a/project/images/exit.png b/project/images/exit.png new file mode 100644 index 0000000..5e5b54e Binary files /dev/null and b/project/images/exit.png differ diff --git a/project/images/logs_list_block_icon.png b/project/images/logs_list_block_icon.png new file mode 100644 index 0000000..cb718a9 Binary files /dev/null and b/project/images/logs_list_block_icon.png differ diff --git a/project/images/logs_list_blocked_icon.png b/project/images/logs_list_blocked_icon.png new file mode 100644 index 0000000..7153ca2 Binary files /dev/null and b/project/images/logs_list_blocked_icon.png differ diff --git a/project/images/logs_list_call_icon.png b/project/images/logs_list_call_icon.png new file mode 100644 index 0000000..0e2444a Binary files /dev/null and b/project/images/logs_list_call_icon.png differ diff --git a/project/images/logs_list_dialled_icon.png b/project/images/logs_list_dialled_icon.png new file mode 100644 index 0000000..828fc96 Binary files /dev/null and b/project/images/logs_list_dialled_icon.png differ diff --git a/project/images/logs_list_missed_icon.png b/project/images/logs_list_missed_icon.png new file mode 100644 index 0000000..8ee1df3 Binary files /dev/null and b/project/images/logs_list_missed_icon.png differ diff --git a/project/images/logs_list_missed_unchecked_icon.png b/project/images/logs_list_missed_unchecked_icon.png new file mode 100644 index 0000000..43f4314 Binary files /dev/null and b/project/images/logs_list_missed_unchecked_icon.png differ diff --git a/project/images/logs_list_mms_icon.png b/project/images/logs_list_mms_icon.png new file mode 100644 index 0000000..86f594c Binary files /dev/null and b/project/images/logs_list_mms_icon.png differ diff --git a/project/images/logs_list_received_icon.png b/project/images/logs_list_received_icon.png new file mode 100644 index 0000000..30e397a Binary files /dev/null and b/project/images/logs_list_received_icon.png differ diff --git a/project/images/logs_list_rejected_icon.png b/project/images/logs_list_rejected_icon.png new file mode 100644 index 0000000..af1f97a Binary files /dev/null and b/project/images/logs_list_rejected_icon.png differ diff --git a/project/images/logs_list_sms_icon.png b/project/images/logs_list_sms_icon.png new file mode 100644 index 0000000..cb58c22 Binary files /dev/null and b/project/images/logs_list_sms_icon.png differ diff --git a/project/images/logs_list_vtcall_icon.png b/project/images/logs_list_vtcall_icon.png new file mode 100644 index 0000000..dcd0664 Binary files /dev/null and b/project/images/logs_list_vtcall_icon.png differ diff --git a/project/images/no_photo.png b/project/images/no_photo.png new file mode 100644 index 0000000..10a4d5b Binary files /dev/null and b/project/images/no_photo.png differ diff --git a/project/images/trash.png b/project/images/trash.png new file mode 100644 index 0000000..d85e734 Binary files /dev/null and b/project/images/trash.png differ diff --git a/project/index.html b/project/index.html new file mode 100644 index 0000000..c3c0778 --- /dev/null +++ b/project/index.html @@ -0,0 +1,20 @@ + + + + + + + + Call log + + + + + + + + + +
+ + \ No newline at end of file diff --git a/project/js/app.config.js b/project/js/app.config.js new file mode 100644 index 0000000..03eb992 --- /dev/null +++ b/project/js/app.config.js @@ -0,0 +1,30 @@ +/*global tizen*/ + +/** + * @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 index 0000000..868f551 --- /dev/null +++ b/project/js/app.helpers.js @@ -0,0 +1,112 @@ +/*jslint devel: true*/ +/*global $ */ + +/** + * @class Helpers + */ +function Helpers() { + 'use strict'; +} + +(function () { // strict mode wrapper + 'use strict'; + Helpers.prototype = { + + /** + * Returns date in format YYYY/MM/DD + * @param dateObj + * @returns {String} formatted + */ + getShortDate: function Helpers_getShortDate(dateObj) { + var dd, mm, yyyy; + + try { + yyyy = dateObj.getFullYear().toString(); + mm = (dateObj.getMonth() + 1).toString(); // getMonth() is zero-based + dd = dateObj.getDate().toString(); + } catch (e) { + console.error('error', e); + } + + return yyyy + '/' + (mm[1] ? mm : '0' + mm[0]) + '/' + (dd[1] ? dd : '0' + dd[0]); + }, + /** + * Returns date in format DD short_month YYYY + * @param dateObj + * @returns {String} formatted + */ + toNativeDate: function Helpers_toNativeDate(dateObj) { + var date = dateObj.toDateString().split(" "); + + return date[2] + ' ' + date[1] + ' ' + date[3]; + }, + /** + * Returns time in format HH:mm or hh:mm ap + * @param dateObj + * @returns {String} formatted + */ + toNativeTime: function Helpers_toNativeDate(dateObj) { + var hours, apHours; + + if (tizen.time.getTimeFormat().indexOf('ap') != -1) { + hours = dateObj.getHours(); + apHours = hours % 12 < 10 ? '0' + hours % 12 : hours % 12; + + return apHours.toString().replace("00", "12") + ':' + + (dateObj.getMinutes() < 10 ? "0" + dateObj.getMinutes() : + dateObj.getMinutes()) + (hours > 11 ? ' PM' : ' AM'); + } + + return dateObj.toTimeString().substring(0, 5); + }, + /** + * Seconds to hours converter + * + * @param seconds + * @returns {string} + */ + secondsToHours: function Helpers_secondsToHours(seconds) { + var str = ''; + str = ((seconds % 60 < 10) ? '0' : '') + (seconds % 60); + seconds = parseInt(seconds / 60, 10); + str = ':' + str; + str = ((seconds % 60 < 10) ? '0' : '') + (seconds % 60) + str; + seconds = parseInt(seconds / 60, 10); + str = ':' + str; + str = ((seconds % 24 < 10) ? '0' : '') + (seconds % 24) + str; + seconds = parseInt(seconds / 24, 10); + return str; + }, + + /** + * Returns scroll position of the given scroll view. + * @param {object} element ScrollView element + * @returns {number} scroll position + */ + getScrollPosition: function Helpers_getScrollPosition(element) { + return element.scrollview('getScrollPosition').y; + }, + + /** + * Scrolls given jQuery ScrollView to given Y position. + * @param {object} element ScrollView element to be scrolled. + * @param {number} position Y position to scroll to + */ + scrollTo: function Helpers_scrollTo(element, position) { + // don't allow scrolling too much + var maxPosition = element.children().first().outerHeight(true) - element.height(); + if (maxPosition < 0) { + maxPosition = 0; + } + if (position > maxPosition) { + position = maxPosition; + } + if (position < 0) { + position = 0; + } + // scroll + element.scrollview('scrollTo', 0, -position); + } + + }; +}()); diff --git a/project/js/app.js b/project/js/app.js new file mode 100644 index 0000000..e58ca9d --- /dev/null +++ b/project/js/app.js @@ -0,0 +1,176 @@ +/*jslint devel: true*/ +/*global Config, Model, Ui, tizen, setTimeout, navigator */ + +var App = null; + +(function () { // strict mode wrapper + 'use strict'; + + /** + * Creates a new application object + * + * @class Application + */ + App = function App() { + this.configData = {}; + }; + + App.prototype = { + /** + * @type Array + */ + requires: ['js/app.config.js', 'js/app.model.js', 'js/app.ui.js', 'js/app.ui.templateManager.js', 'js/app.ui.templateManager.modifiers.js', 'js/app.helpers.js'], + + /** + * @type Model + */ + model: null, + + /** + * @type Ui + */ + ui: null, + + /** + * @type Config + */ + config: null, + + /** + * @type {number} + */ + lastViewedCaller: 0, + + /** + * Initialisation function + */ + init: function App_init() { + // instantiate the libs + this.config = new Config(); + this.model = new Model(); + this.model.registerChangeListener(this.updateCallLists.bind(this)); + this.ui = new Ui(); + this.updateCallLists(); + + return this; + }, + + /** + * Updates call history and caller detailed history lists + */ + updateCallLists: function App_updateCallLists() { + // workaround - time zone update + tizen.time.getCurrentDateTime().toLocalTimezone(); + // workaround; + setTimeout(this.showHistoryForCaller(this.lastViewedCaller), 500); + this.showCallHistory(); + }, + + /** + * Renders call history view + */ + showCallHistory: function App_showCallHistory() { + this.model.getCallHistory(this.ui.showCallHistory.bind(this.ui)); + }, + + /** + * Renders history for caller view + * @param {string} phoneNumber + */ + showHistoryForCaller: function App_showHistoryForCaller(phoneNumber) { + this.lastViewedCaller = phoneNumber; + this.model.getCallHistoryForCaller(phoneNumber, this.ui.showHistoryForCaller.bind(this.ui, phoneNumber)); + }, + + /** + * @param {number} addressBookId + * @param {number} contactId + * @returns {string} photoURI Photo URI for specified contact + */ + getPhotoURIForContact: function App_getPhotoURIForContact(personId) { + return this.model.getPhotoURIForContact(personId); + }, + + /** + * Launch extension call service + * @param {string} phoneNumber + */ + makeCall: function App_makeCall(phoneNumber) { + var self = this, + appControl = new tizen.ApplicationControl( + 'http://tizen.org/appcontrol/operation/call', + 'tel:' + phoneNumber + ); + tizen.application.launchAppControl( + appControl, + null, + function () { + }, + function (e) { + console.error('Call to ' + phoneNumber + + ' failed. Call service is unavailable.', e); + self.ui.showErrorPopup('Call failed. ' + + 'Call service is unavailable.'); + }, + { + onsuccess: function () { + }, + onfailure: function (e) { + console.log('App_makeCall: Call to ' + phoneNumber + + ' failed. Call service was busy.', e); + //self.ui.showErrorPopup('Call failed.' + // + 'Call service was busy.'); + } + } + ); + }, + + /** + * Launch extension sms service + * @param {string} phoneNumber + */ + sendSms: function App_sendSms(phoneNumber) { + + var self = this, + appControl = new tizen.ApplicationControl( + 'http://tizen.org/appcontrol/operation/compose', + 'sms:' + phoneNumber + ); + + tizen.application.launchAppControl( + appControl, + null, + null, + function (e) { + console.error('Message launch error: ', e); + self.ui.showErrorPopup('Message service is unavailable'); + app.ui.unlockOptionButtons(); + }, + { + onfailure: function (er) { + console.error('Message service launch error: ', er); + self.ui.showErrorPopup('Message service is unavailable'); + app.ui.unlockOptionButtons(); + } + } + ); + }, + + /** + * Deletes specified log entry + * @param {CallHistoryEntry} entry + */ + deleteLog: function App_deleteLogs(entry) { + this.model.deleteLog(entry); + }, + + /** + * App exit + */ + exit: function App_exit(event) { + event.preventDefault(); + event.stopPropagation(); + tizen.application.getCurrentApplication().exit(); + } + }; +}()); diff --git a/project/js/app.model.js b/project/js/app.model.js new file mode 100644 index 0000000..42c3c64 --- /dev/null +++ b/project/js/app.model.js @@ -0,0 +1,113 @@ +/*jslint devel: true*/ +/*global tizen, $, app, localStorage */ + +/** + * @class Model + */ +function Model() { + 'use strict'; + + this.init(); +} + +(function () { // strict mode wrapper + 'use strict'; + Model.prototype = { + + /** + * API module initialisation + */ + init: function Model_init() { + }, + + /** + * Registers listener for call history change + * @param {onSuccessCallback} function + */ + registerChangeListener: function Model_registerChangeListener(onSuccessCallback) { + + var callHistoryListener = { + onadded: onSuccessCallback, + onchanged: onSuccessCallback, + onremoved: onSuccessCallback + }; + + tizen.callhistory.addChangeListener(callHistoryListener); + }, + + /** + * @param {function} onSuccess callback + * @param {function} onError callback + */ + getCallHistory: function Model_getCallHistory(onSuccess, onError) { + if (typeof onError !== 'function') { + onError = function (e) { + console.error('Model_getCallHistory error', e); + }; + } + + tizen.callhistory.find(onSuccess, onError, null, new tizen.SortMode('startTime', 'DESC')); + }, + + /** + * @param {number} phoneNumber + * @param {function} onSuccess Callback + */ + getCallHistoryForCaller: function Model_getCallHistoryForCaller(phoneNumber, onSuccess) { + var filter = null, success; + if (phoneNumber) { + filter = new tizen.AttributeFilter('remoteParties.remoteParty', 'EXACTLY', phoneNumber); + success = onSuccess; + } else { + success = function (calls) { + onSuccess(calls.filter(function (element) { + if(!element.remoteParties[0].remoteParty) { + return element; + } + })); + }; + } + + tizen.callhistory.find(success, + function (e) { + console.error(e); + }, + filter, + new tizen.SortMode('startTime', 'DESC') + ); + }, + + /** + * Deletes specified log entry + * @param {CallHistoryEntry} entry + */ + deleteLog: function Model_deleteLog(entry) { + try { + tizen.callhistory.remove(entry); + } catch (e) { + console.error('Error on entry delete: ' + e.message); + } + }, + + /** + * @param {number} addressBookId + * @param {number} contactId + * @returns {string} photoURI + */ + getPhotoURIForContact: function Model_getPhotoURIForContact(personId) { + try { + var addressBook = tizen.contact.getDefaultAddressBook(), + contact; + + if (addressBook) { + contact = addressBook.get(personId); + return contact.photoURI || false; + } + } catch (e) { + console.error('updatePhotoByContactId error:' + e.message); + } + return false; + } + + }; +}()); diff --git a/project/js/app.ui.js b/project/js/app.ui.js new file mode 100644 index 0000000..073572a --- /dev/null +++ b/project/js/app.ui.js @@ -0,0 +1,792 @@ +/*jslint devel: true*/ +/*global tizen, document, $, jQuery, app, UiPanel, UiContact, TemplateManager, window, Helpers */ + +/** + * @class Ui + */ + +function Ui(contacts) { + 'use strict'; + this.init(); +} + +(function () { // strict mode wrapper + 'use strict'; + Ui.prototype = { + /** + * UI remove mode + * @type {boolean} + */ + removeMode: false, + + addressBook: tizen.contact.getDefaultAddressBook(), + + photoURIdefault: null, + + contactsLoaded: null, + + checkedLogs: [], + + /** + * @type {TemplateManager} + */ + templateManager: null, + + /** + * UI Initializer + */ + init: function Ui_init() { + this.loadContacts(); + this.templateManager = new TemplateManager(); + this.helpers = new Helpers(); + $(document).ready(this.domInit.bind(this)); + $.mobile.tizen.disableSelection(document); + }, + + /** + * When DOM is ready, initialise it (bind events) + */ + domInit: function Ui_domInit() { + this.templateManager.loadToCache(['callView', + 'callerHistory', + 'callItemRow', + 'callerCallItemRow', + 'messageWindow', + 'errorWindow', + 'dateRow'], this.initPages.bind(this)); + }, + + /** + * UI pages initializer + */ + initPages: function Ui_initPages() { + var pages = [], body = $('body'); + + body + .append(this.templateManager.get('messageWindow')) + .append(this.templateManager.get('errorWindow')) + .trigger('create'); + $('#callView') + .append($(this.templateManager.get('callView')) + .children()) + .trigger('pagecreate') + .trigger('pageshow'); + pages.push(this.templateManager.get('callerHistory')); + body.append(pages.join('')); + this.removeSearchBarToHeader(); + + this.addEvents(); + app.showCallHistory(); + + this.photoURIdefault = $("#header .photo").css('background-image'); + }, + + /** + * Add UI events + */ + addEvents: function Ui_addEvents() { + var self = this; + + $(window).on('resize', function () { + $.mobile.activePage.page('refresh'); + }); + + $('#callView').on('pagebeforeshow', function () { + app.showCallHistory(); + }); + + //original code, do not remove until web-ui release; N_SE-48946 + //$(".ui-scrollview-view").listview(); + + $('#historyForCallerView').on('pagebeforeshow', function () { + self.hideCheckboxes(); + $('#historyForCallerView .ui-content.ui-scrollview-clip .ui-scrollview-view') + .css('-webkit-transform', 'translate3d(0px, 0px, 0px)'); + $('#selectAllDetails').on('change', function () { + self.selectAll(); + }); + }); + + $('#historyForCallerView').on('pageshow', function () { + $('#content').css('top', '160px'); + $('#header').css('height', '160px'); + $('#callerListContainer') + .children('.ui-overflow-indicator-top', '.ui-overflow-indicator-bottom') + .hide(); + }); + + $('#callerListContainer').on('scrollstart', function () { + $(this) + .children('.ui-overflow-indicator-top', '.ui-overflow-indicator-bottom') + .fadeIn(200); + }); + + $('#callerListContainer').on('scrollstop', function () { + $(this) + .children('.ui-overflow-indicator-top', '.ui-overflow-indicator-bottom') + .fadeOut(200); + }); + + $('.selectAllBox').children().on('click', function () { + $('.selectAllBox input[type=checkbox]').trigger('click'); + self.selectAll(); + self.selectAllDetailsEach(); + }); + + $('#calllogList').on('click', '.date', function (event) { + event.stopPropagation(); + }); + + $('#calllogList').on('click', '.call', function onCalllogEntryClick(event) { + var remoteParty = $(this) + .data('entries')[0] + .remoteParties[0] + .remoteParty; + app.showHistoryForCaller(remoteParty); + $.mobile.changePage('#historyForCallerView'); + }); + + $('#smsActionBtn').on('click', function (event) { + event.stopPropagation(); + event.preventDefault(); + self.lockButtons('#smsActionBtn'); + self.hideCheckboxes(); + app.sendSms($('#forCallerList').data('remoteParty')); + }); + + $('#callActionBtn').on("click", function (event) { + self.lockButtons('#callActionBtn'); + self.hideCheckboxes(); + app.makeCall($('#forCallerList').data('remoteParty')); + }); + + $('#deleteActionBtn').on('click', function () { + self.changeDetailsToRemoveState(); + }); + + $('.selectAllBox').on('click', 'li', function () { + var checkbox = $(this).find(':checkbox'); + if (self.removeMode === true) { + if (checkbox.attr('checked')) { + checkbox.attr('checked', false) + .data('checkboxradio') + .refresh(); + } else { + checkbox.attr('checked', true) + .data('checkboxradio') + .refresh(); + } + self.setSelectAllDetails(); + } + }); + + $('#popup') + .on('click', '#popupCancelActionBtn', this.closePopup.bind(this)) + .on('click', '#popupSubmitActionBtn', this.deleteCheckedLogs.bind(this)); + + $('#errorPopup').on('click', '#errorPopupOkBtn', this.closeErrorPopup); + + $( "#errorPopup" ).bind({ + popupafterclose: function(){ + self.unlockButtons(); + } + }); + + $(window).keyup(function(e){ + if (e.which === 13) { + $('input:focus').blur(); + } + }); + + window.addEventListener('tizenhwkey', function(e) { + if (e.keyName == "back") { + if ($.mobile.popup.active) { + $.mobile.popup.active.close(); + } else if (self.removeMode === true) { + app.ui.changeDetailsToRemoveState(undefined, true); + } else if ($.mobile.activePage.attr('id') === 'callView') { + tizen.application.getCurrentApplication().exit(); + } else { + history.back(); + } + } + }); + self.onVisibilityChange(); + }, + + addEventsForCallerListCheckboxes: function Ui_addEventsForCallerListCheckboxes() { + var self = this; + $('#forCallerList :checkbox').on('change', function (event) { + if ($(this).attr('checked')) { + $(this).attr('checked', true); + } else { + $(this).attr('checked', false); + } + self.setSelectAllDetails(); + }); + }, + + selectAll: function () { + if ($('#selectAllDetails').attr('checked')) { + this.selectCheckbox($('#selectAllDetails'), true); + } else { + this.selectCheckbox($('#selectAllDetails'), false); + } + this.selectAllDetailsEach(); + }, + + selectCheckbox: function (obj, state) { + var deleteButton = $('#deleteActionBtn'), numChecked; + obj.attr('checked', state) + .data('checkboxradio') + .refresh(); + + numChecked = $('#forCallerList input:checked').length; + if (this.removeMode && numChecked === 0 && !deleteButton.hasClass('ui-disabled')) { + deleteButton.addClass('ui-disabled').attr('tabIndex', '-1').blur(); + } else if (deleteButton.hasClass('ui-disabled')) { + deleteButton.removeClass('ui-disabled').attr('tabIndex', '0'); + } + }, + + /** + * Returns number of selected call logs + * @return {number} length + */ + getCountSelectedLogEntries: function Ui_getCountSelectedLogEntries() { + return $('#forCallerList li .toRemove label.ui-checkbox-on').length; + }, + + selectAllDetailsEach: function Ui_selectAllDetailsEach() { + var self = this; + $('#forCallerList').find('input').each(function () { + if ($('#selectAllDetails').attr('checked')) { + self.selectCheckbox($(this), true); + } else { + self.selectCheckbox($(this), false); + } + }); + }, + + /** + * Hides checkboxes + */ + hideCheckboxes: function Ui_hideCheckboxes() { + var self = this; + this.selectCheckbox($('#selectAllDetails'), false); + + $('#forCallerList').find('input').each(function () { + self.selectCheckbox($(this), false); + }); + this.changeDetailsToRemoveState('hide'); + }, + + /** + * Returns css classes for specified entry + * + * @param {CallHistoryEntry} entry + * @returns {string} + */ + cssClassesForEntry: function Ui_cssClassesForEntry(entry) { + return 'call dir_' + entry.direction.toLowerCase() + ' type_' + entry.type.replace('.', '-').toLowerCase(); + }, + + setSelectAllDetails: function Ui_setSelectAllDetails() { + if ($('#forCallerList input[type="checkbox"]').length === + $('#forCallerList input[checked="checked"]').length) { + this.selectCheckbox($('#selectAllDetails'), true); + } else { + this.selectCheckbox($('#selectAllDetails'), false); + } + }, + + /** + * Shows popup with specified message + * @param {string} message + */ + showPopup: function Ui_showPopup(message) { + $('#popupMessage').html(message); + $('#popup').popup('open', {'positionTo': 'window'}); + }, + + /** + * Hides popup + */ + closePopup: function Ui_closePopup() { + $('#popup').popup('close'); + }, + + showErrorPopup: function Ui_showErrorPopup(message) { + $('#errorPopupMessage').html(message); + $('#errorPopup').popup('open', {'positionTo': 'window'}); + }, + + closeErrorPopup: function Ui_closeErrorPopup() { + $('#errorPopup').popup('close'); + }, + + /** + * Deletes checked log entries + */ + deleteCheckedLogs: function Ui_deleteCheckedLogs(e) { + this.closePopup(); + + this.selectCheckbox($('#selectAllDetails'), false); + + $('#forCallerList li.call').each(function () { + if ($(this).find('form label').hasClass('ui-checkbox-on')) { + app.deleteLog($(this).data('entries')[0]); + $(this).remove(); + } + }); + + if ($('#forCallerList li.call').length > 0) { + this.updateCallerHeaderNumberOfEntries($('#forCallerList li.call').length); + } else { + e.preventDefault(); + $('.ui-listview-filter .ui-input-text').val(''); + $('.ui-listview-filter .ui-input-text').trigger('change'); + $.mobile.changePage('#callView'); + } + + this.changeDetailsToRemoveState(true); + this.scrollToBottom(); + }, + + changeDetailsToRemoveState: function Ui_changeDetailsToRemoveState(set, clear) { + var counter = this.getCountSelectedLogEntries(), + numChecked = $('#forCallerList input:checked').length, + matrix, pos; + if (clear === true) { + counter = 0; + $('#forCallerList').find(':checkbox').attr('checked', false) + .data('checkboxradio').refresh(); + $('.selectAllBox').find(':checkbox').attr('checked', false) + .data('checkboxradio').refresh(); + } + if (set !== undefined) { + this.removeMode = false; + } else if (counter === 0) { + this.removeMode = !this.removeMode; + } + + if (this.removeMode && numChecked === 0) { + $('#deleteActionBtn').addClass('ui-disabled').attr('tabIndex', '-1').blur(); + } else if (!this.removeMode) { + $('#deleteActionBtn').removeClass('ui-disabled').attr('tabIndex', '0'); + this.selectAllDetailsEach(); + } + + if (counter === 0) { + if (this.removeMode) { + $('#historyForCallerView .toRemove').removeClass('hidden'); + $('#historyForCallerView .selectAllBox') + .removeClass('hidden'); + } else { + $('#historyForCallerView .toRemove').addClass('hidden'); + $('#historyForCallerView .selectAllBox').addClass('hidden'); + } + } else if (counter > 1) { + this.showPopup('Are you sure you want to delete selected logs?'); + } else { + this.showPopup('Are you sure you want to delete selected log?'); + } + + matrix = $('#historyForCallerView .ui-scrollview-view').css('transform'); + pos = matrix.substr(7, matrix.length -8).split(',')[5]; + if (pos !== undefined) { + $('#callerListContainer').scrollview('scrollTo', 100, parseInt(pos), 10); + } else { + this.refreshScrollView(); + } + }, + + /** + * Renders call history list + * + * @param {CallHistoryEntry[]} callEntries + */ + showCallHistory: function Ui_showCallHistory(callEntries) { + var self = this, + pdate = null, // previous date + date = '', + elements = [], // list elements + len = callEntries.length, // entries length + tempLength = 0, // length of temporary table; + i, // loop counter + j, // loop counter + current, // current entry object + today = this.helpers.getShortDate(new Date()), // today short date + entryShortDate, + filterResult, + groupsOfDays = [], + dayLog, + index = 0, + calllogList = $('#calllogList'), + calllogListContent = $('#calllogListContent'), + calllogListContentPos; + + function filterForSameEntry(element) { + return self.getNumber(current) === self.getNumber(element) + && current.direction === element.direction; + } + + $('.selectedCount').hide(); + + for (i = 0; i < len; i = i + 1) { + current = callEntries[i]; + date = this.helpers.toNativeDate(current.startTime); + + // if date is changed create new deyLog; + if (date !== pdate) { + dayLog = {}; + dayLog.date = date; + dayLog.entries = []; + dayLog.counters = []; + groupsOfDays.push(dayLog); + pdate = date; + } + + // group entries by remote Party; + filterResult = dayLog.entries.filter(filterForSameEntry); + if (filterResult.length) { + index = dayLog.entries.indexOf(filterResult[0]); + dayLog.counters[index] += 1; + } else { + dayLog.entries.push(current); + dayLog.counters[dayLog.entries.length - 1] = 1; + } + } + // Create UL list with dividers; + len = groupsOfDays.length; + for (i = 0; i < len; i += 1) { + dayLog = groupsOfDays[i]; + tempLength = dayLog.entries.length; + entryShortDate = this.helpers.getShortDate(dayLog.entries[0].startTime); + // add date header; + //original code, do not remove until web-ui release; N_SE-48946 +// elements.push($(this.templateManager.get('dateRow', { +// 'date': today === entryShortDate ? 'Today' : dayLog.date +// })).get(0)); + + for (j = 0; j < tempLength; j = j + 1) { + elements.push(this.getCallItemRow(dayLog.entries[j], dayLog.counters[j])); + } + } + calllogListContentPos = this.helpers.getScrollPosition(calllogListContent); + calllogList.empty().append(elements); + + /* workaround solution for searching phrase remain*/ + if ($("[data-type='search']").val().length != "") { + calllogList.listview('refresh'); + $("[data-type='search']").trigger("keyup"); + $(".ui-li-divider").removeClass("ui-li ui-li-divider ui-bar-s").addClass("date"); + } else { + calllogList.listview({ + autodividers: true, + //filter: true, + autodividersSelector: function ( li ) { + return $(li).find('.callDate').text() === app.ui.helpers.toNativeDate(new Date()) + ? "Today" : $(li).find('.callDate').text(); + } + }).listview('refresh'); + $(".ui-li-divider").removeClass().addClass("date"); + } + + setTimeout(this.helpers.scrollTo.bind(this, calllogListContent, calllogListContentPos), 10); + }, + + /** + * @param: {CallHistoryEntry} entry + */ + getNumber: function (entry) { + return entry.remoteParties[0].remoteParty; + }, + + /** + * Returns HTML for single log entry + * + * @param {CallHistoryEntry} entry + * @param {number} counter + * @returns {HTMLPartial} + */ + getCallItemRow: function Ui_getCallItemRow(entry, counter) { + var party = entry.remoteParties[0], + name = this.getNameByNumber(party.remoteParty), + tpl; + + if (counter > 1) { + name += ' (' + counter + ')'; + } + + tpl = this.templateManager.get('callItemRow', { + 'name': name, + 'callTime': this.helpers.toNativeTime(entry.startTime), + 'callDate': this.helpers.toNativeDate(entry.startTime), + 'cssClasses': this.cssClassesForEntry(entry), + 'uid': entry.uid + }); + + return $(tpl) + .data('remoteParty', entry.remoteParties[0].remoteParty) + .data('entries', [entry]) + .get(0); // return clean DOM element so array of those could be appended at once*/ + }, + + 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.substr(-9) + === number.substr(-9)) { + name = contact.name.displayName; + break; + } + } + } + } + } + return name || number || 'Unknown'; + }, + + /** + * Returns HTML for single caller log entry + * + * @param {CallHistoryEntry} entry + * @returns {HTMLElement} + */ + getCallerCallLogRow: function Ui_getCallerCallLogRow(entry) { + return $(this.templateManager.get('callerCallItemRow', { + 'cssClass': this.cssClassesForEntry(entry), + 'callTime': this.helpers.toNativeTime(entry.startTime), + 'callDuration': this.helpers.secondsToHours(entry.duration), + 'uid': entry.uid + })).data('entries', [entry]).get(0); + }, + + /** + * Renders call log list for specified caller + * + * @param {string} remoteParty + * @param {CallHistoryEntry[]} entries + */ + showHistoryForCaller: function Ui_showHistoryForCaller(remoteParty, entries) { + var pdate = '', + date = '', + elements = [], + len = entries.length, + i, + checkbox; + + if (len) { + this.updateCallerHeader(entries[0], entries.length); + } else { + // if last call log has been removed + this.removedLastLog(); + this.app.lastViewedCaller = 0; + } + + $('#forCallerList') + .data('remoteParty', remoteParty) + .data('modified', false) + .empty(); + + // group caller log entries by date + for (i = 0; i < len; i = i + 1) { + date = this.helpers.toNativeDate(entries[i].startTime); + + // if date is changed render new header + if (date !== pdate) { + elements.push($(this.templateManager.get('dateRow', {'date': date})).get(0)); + pdate = date; + } + elements.push(this.getCallerCallLogRow(entries[i])); + } + + $('#forCallerList') + .empty() + .append(elements); + + if (elements.length > 0) { + $('li#delete > a').addClass('ui-btn-active'); + } else { + $('li#delete > a').removeClass('ui-btn-active'); + } + + $('#forCallerList').trigger('create'); + + // change to remove mode if it was active before registering call + if (this.removeMode) { + this.removeMode = !this.removeMode; + this.changeDetailsToRemoveState(); + // check previous checked entries + this.checkedLogs.forEach(function(logUid){ + checkbox = $('#forCallerList li.call[data-uid="' + logUid + '"]') + .find(':checkbox'); + + if (checkbox.length > 0) { + checkbox.attr('checked', true) + .data('checkboxradio') + .refresh(); + $('#deleteActionBtn').removeClass('ui-disabled').attr('tabIndex', '0'); + } + }); + + this.setSelectAllDetails(); + + // close popup if there are no checked checkboxes + if (!$("#forCallerList input:checked").length) { + if ($.mobile.popup.active) { + $.mobile.popup.active.close(); + } + } + } + this.addEventsForCallerListCheckboxes(); + // lock buttons if unknown caller + if (remoteParty) { + this.unlockButtons(); + } else { + this.lockButtons('#callActionBtn, #smsActionBtn'); + } + }, + + /** + * Update accoundId + * @param {string} accountId + */ + updateCallerHeaderAccountId: function Ui_updateCallerHeaderAccountId(accountId) { + $('.infoContainer .accountId').html(accountId); + }, + + /** + * Update number of entries + * @param numberOfEntries + */ + updateCallerHeaderNumberOfEntries: function Ui_updateCallerHeaderNumberOfEntries(numberOfEntries) { + $('.infoContainer .numberOfEntries').html('' + numberOfEntries + ' ' + (numberOfEntries === 1 ? 'call' : 'calls')); + }, + + /** + * Updates caller main info + * @param {CallHistoryEntry} entry + * @param {number} numberOfEntries + */ + updateCallerHeader: function Ui_updateCallerHeader(entry, numberOfEntries) { + var name = '', party, imgPath, personId; + + $('#header .photo').css('background-image', this.photoURIdefault); + + if (entry.remoteParties !== null) { + party = entry.remoteParties[0]; + personId = parseInt(party.personId, 10); + name = this.getNameByNumber(party.remoteParty); + if (party.displayName) { + this.updateCallerHeaderAccountId(party.remoteParty); + } + this.updateCallerHeaderNumberOfEntries(numberOfEntries); + if (personId !== 0) { + imgPath = app.getPhotoURIForContact(personId); + if (imgPath !== false) { + $('#header .photo').css('background-image', 'url(' + imgPath + ')'); + } + } + } else if (entry.contactId !== null) { + name = entry.accountId; + this.updateCallerHeaderAccountId(entry.accountId); + this.updateCallerHeaderNumberOfEntries(numberOfEntries); + } else { + name = entry.accountId; + this.updateCallerHeaderAccountId(''); + this.updateCallerHeaderNumberOfEntries(numberOfEntries); + } + $('.contact > .infoContainer > .name').html(this.templateManager.modifiers.escape(name)); + + }, + + loadContacts: function Model_loadContacts(callback) { + var contactsFoundCB, errorCB; + + this.contactsLoaded = null; + + contactsFoundCB = function (contacts) { + this.contactsLoaded = contacts; + 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); + }, + + /** + * Remove search filter from content and appends it to header + */ + removeSearchBarToHeader: function () { + $('#page-header').append($('#callView .ui-listview-filter')); + $.mobile.activePage.page('refresh'); + $('.ui-input-cancel').remove(); // patch for WebUI bug + $('#calllogListContent').trigger('resize'); // WebUi scrollview fix + }, + + scrollToBottom: function () { + var scrollView = $(".ui-scrollview-view"); + scrollView.css("-webkit-transform", "translate3d(0px, -" + + scrollView.height() + "px, 0px)"); + }, + + onVisibilityChange: function () { + var self = this; + document.addEventListener('webkitvisibilitychange', function () { + if (document.webkitVisibilityState === 'hidden') { + self.checkedLogs = []; + $('#forCallerList li.call').each(function () { + if ($(this).find('form label') + .hasClass('ui-checkbox-on')) { + var checkedEntry = $(this).data('entries')[0]; + self.checkedLogs.push(checkedEntry.uid); + } + }); + } else { + self.loadContacts(app.updateCallLists.bind(app)); + $('#callActionBtn, #smsActionBtn') + .removeClass('ui-disabled'); + } + }); + }, + + lockButtons: function Ui_lockButtons(buttons) { + $(buttons).addClass('ui-disabled').attr('tabIndex', '-1').blur(); + }, + + unlockButtons: function Ui_unlockButtons(){ + $('#callActionBtn, #smsActionBtn').removeClass('ui-disabled').attr('tabIndex', '0'); + }, + + /** + * WORKAROUND; + * Patch for UI, bad refresh scrollView + */ + refreshScrollView: function () { + var scrollView = $('.ui-scrollview-view'), + show = function () { + scrollView.show(); + }; + scrollView.hide(); + setTimeout(show, 0); + }, + + removedLastLog: function () { + this.hideCheckboxes(); + $(".ui-popup").popup('close'); + $.mobile.changePage('#callView'); + } + }; + +}()); diff --git a/project/js/app.ui.templateManager.js b/project/js/app.ui.templateManager.js new file mode 100644 index 0000000..463c032 --- /dev/null +++ b/project/js/app.ui.templateManager.js @@ -0,0 +1,139 @@ +/*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); + } + 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); + } + 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 index 0000000..1d57ce7 --- /dev/null +++ b/project/js/app.ui.templateManager.modifiers.js @@ -0,0 +1,36 @@ +/*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 $('').text(str).html(); + } + } + }; +}()); \ No newline at end of file diff --git a/project/js/main.js b/project/js/main.js new file mode 100644 index 0000000..8a205f5 --- /dev/null +++ b/project/js/main.js @@ -0,0 +1,78 @@ +/* + * 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. + */ + +/*jslint devel: true*/ +/*global $, tizen, App */ + +/** + * This file acts as a loader for the application and its dependencies + * + * First, the 'app.js' script is loaded . + * Then, scripts defined in 'app.requires' are loaded. + * Finally, the app is initialised - the app is instantiated ('app = new App()') + * and 'app.init()' is called. + */ + + +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/callItemRow.tpl b/project/templates/callItemRow.tpl new file mode 100644 index 0000000..7cdb23a --- /dev/null +++ b/project/templates/callItemRow.tpl @@ -0,0 +1,7 @@ +
  • + +
    %name|escape%
    +
    +
    %callTime%
    + +
  • \ No newline at end of file diff --git a/project/templates/callView.tpl b/project/templates/callView.tpl new file mode 100644 index 0000000..15c7610 --- /dev/null +++ b/project/templates/callView.tpl @@ -0,0 +1,16 @@ +
    + +
    +
      +
    +
    +
    + + \ No newline at end of file diff --git a/project/templates/callerCallItemRow.tpl b/project/templates/callerCallItemRow.tpl new file mode 100644 index 0000000..08c6f67 --- /dev/null +++ b/project/templates/callerCallItemRow.tpl @@ -0,0 +1,6 @@ +
  • + +
    %callTime%
    +
    +
    %callDuration%
    +
  • \ No newline at end of file diff --git a/project/templates/callerHistory.tpl b/project/templates/callerHistory.tpl new file mode 100644 index 0000000..f936220 --- /dev/null +++ b/project/templates/callerHistory.tpl @@ -0,0 +1,36 @@ +
    + +
    +
    +
    + +
    +

    Select all

    +
    +
    +
      +
      +
      +
      +
      + +
      +
      +
      \ No newline at end of file diff --git a/project/templates/dateRow.tpl b/project/templates/dateRow.tpl new file mode 100644 index 0000000..9e6caa9 --- /dev/null +++ b/project/templates/dateRow.tpl @@ -0,0 +1 @@ +
    • %date%
    • \ No newline at end of file diff --git a/project/templates/errorWindow.tpl b/project/templates/errorWindow.tpl new file mode 100644 index 0000000..f0b0216 --- /dev/null +++ b/project/templates/errorWindow.tpl @@ -0,0 +1,6 @@ +
      +
      +
      + OK +
      +
      diff --git a/project/templates/messageWindow.tpl b/project/templates/messageWindow.tpl new file mode 100644 index 0000000..d916cae --- /dev/null +++ b/project/templates/messageWindow.tpl @@ -0,0 +1,7 @@ + diff --git a/tizen-app-template.xml b/tizen-app-template.xml new file mode 100755 index 0000000..247e616 --- /dev/null +++ b/tizen-app-template.xml @@ -0,0 +1,12 @@ + + + CallLog + TIZEN + + description.xml + + + + + + diff --git a/tizen_32.png b/tizen_32.png new file mode 100644 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 index 0000000..b188083 Binary files /dev/null and b/tizen_64.png differ