From 98f58b012f89778070461d594f48c44b24429b89 Mon Sep 17 00:00:00 2001 From: brianjjones Date: Thu, 3 Apr 2014 16:43:31 -0700 Subject: [PATCH 1/1] Initial commit of the Modello Phone app Change-Id: I32f48155ddfbd49f8fb9ebce1b9e72c8539f0eef Signed-off-by: brianjjones --- Makefile | 18 + config.xml | 18 + css/contacts_library.css | 216 ++++++++ css/images/null.png | Bin 0 -> 2791 bytes css/phone_style.css | 277 ++++++++++ data/contacts.json | 202 +++++++ data/history.json | 313 +++++++++++ data/photos/nophoto.png | Bin 0 -> 3117 bytes icon.png | Bin 0 -> 2512 bytes index.html | 182 +++++++ js/callhistorycarousel.js | 268 +++++++++ js/contacts_library.js | 210 ++++++++ js/main.js | 809 ++++++++++++++++++++++++++++ js/phone.js | 361 +++++++++++++ packaging/html5-ui-phone.changes | 3 + packaging/html5-ui-phone.spec | 41 ++ templates/contactCarouselDelegate.html | 24 + templates/libraryContactDetailDelegate.html | 47 ++ templates/template-contacts.html | 10 + 19 files changed, 2999 insertions(+) create mode 100644 Makefile create mode 100644 config.xml create mode 100644 css/contacts_library.css create mode 100644 css/images/null.png create mode 100644 css/phone_style.css create mode 100644 data/contacts.json create mode 100644 data/history.json create mode 100644 data/photos/nophoto.png create mode 100644 icon.png create mode 100644 index.html create mode 100644 js/callhistorycarousel.js create mode 100644 js/contacts_library.js create mode 100644 js/main.js create mode 100644 js/phone.js create mode 100644 packaging/html5-ui-phone.changes create mode 100644 packaging/html5-ui-phone.spec create mode 100644 templates/contactCarouselDelegate.html create mode 100644 templates/libraryContactDetailDelegate.html create mode 100644 templates/template-contacts.html diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f2fcf6c --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +PROJECT = html5UIPhone + +VERSION := 0.0.1 +PACKAGE = $(PROJECT)-$(VERSION) + +INSTALL_FILES = $(PROJECT).wgt +INSTALL_DIR = ${DESTDIR}/opt/usr/apps/.preinstallWidgets + +wgtPkg: + zip -r $(PROJECT).wgt config.xml css data icon.png index.html js templates + +install: + @echo "Installing Phone, stand by..." + mkdir -p $(INSTALL_DIR)/ + cp $(PROJECT).wgt $(INSTALL_DIR)/ + +dist: + tar czf ../$(PACKAGE).tar.bz2 . diff --git a/config.xml b/config.xml new file mode 100644 index 0000000..4de9d2a --- /dev/null +++ b/config.xml @@ -0,0 +1,18 @@ + + + + + + Phone + + + + + + + + + + + + diff --git a/css/contacts_library.css b/css/contacts_library.css new file mode 100644 index 0000000..5cee618 --- /dev/null +++ b/css/contacts_library.css @@ -0,0 +1,216 @@ +.contactsLibraryContentList .contactElement { + padding: 10px; + height: 70px; + box-shadow: none; + border-bottom-style: solid; + border-bottom-width: 2px; + overflow: hidden; + white-space: nowrap; + cursor: pointer; +} + +.phoneIcon { + background-repeat: no-repeat; + background-position: center center; + width: 44px; + height: 44px; + display: inline-block; +} + +.emailIcon { + background-repeat: no-repeat; + background-position: center center; + width: 44px; + height: 44px; + display: inline-block; +} + +.addressIcon { + background-repeat: no-repeat; + background-position: center center; + width: 44px; + height: 44px; + display: inline-block; +} + +.contactsLibraryContentList .phoneIcon { + padding-top: 23px; +} + +.contactsLibraryContentList .contactPhotoBox { + width: 70px; + height: 70px; + display: inline-block; + margin-left: 22px; + background-position: center center; + background-repeat: no-repeat; + background-size: 100% 100%; +} + +.contactsLibraryContentList .contactPhoto { + width: 100%; + height: 100%; +} + +.contactsLibraryContentList .contactName { + display: inline-block; + vertical-align: top; + padding-left: 18px; + line-height: 70px; + background-color: transparent !important; + text-transform: uppercase; +} + +.contactsLibraryContentGrid { + line-height: 0; +} + +.contactsLibraryContentGrid .contactElement { + width: 180px; + height: 180px; + display: inline-block; + margin-right: 7px; + margin-bottom: 10px; + cursor: pointer; +} + +.contactsLibraryContentGrid .phoneIcon { + display: none; +} + +.contactsLibraryContentGrid .contactPhotoBox { + width: 100%; + height: 100%; + background-size: 100% 100%; +} + +.contactsLibraryContentGrid .contactPhoto { + width: 100%; + height: 100%; +} + +.contactsLibraryContentGrid .contactName { + position: relative; + padding-left: 10px; + padding-right: 10px; + padding-top: 10px; + top: -80px; + left: 0; + height: 70px; + overflow: hidden; + text-transform: uppercase; +} + +.contactDetail { + margin-top: 48px; +} + +.contactDetailBox1 { + margin-left: 45px; + width: 120px; + display: inline-block; + vertical-align: top; +} + +.contactDetailPhotoBox { + width: 120px; + height: 120px; + display: block; + background-position: center center; + background-repeat: no-repeat; + background-size: 100% 100%; +} + +.contactDetailPhoto { + width: 100%; + height: 100%; +} + +.contactDetailFavorite { + width: 48px; + height: 48px; + margin: 30px auto 0 auto; + background-position: center center; + background-repeat: no-repeat; + background-size: 100% 100%; +} + +.contactDetailBox2 { + display: inline-block; + vertical-align: top; + margin-left: 53px; + width: 385px; +} + +.contactDetailBox3 { + margin-bottom: 30px; +} + + +.contactDetailBox4 { + margin-left: 15px; + vertical-align: top; + display: inline-block; + width: 320px; +} + +.missedCallIcon { + background-repeat: no-repeat; + background-position: center center; + width: 42px; + height: 36px; + display: inline-block; +} + +.missedNewCallIcon { + background-repeat: no-repeat; + background-position: center center; + width: 42px; + height: 36px; + display: inline-block; +} + +.receivedCallIcon { + background-repeat: no-repeat; + background-position: center center; + width: 42px; + height: 36px; + display: inline-block; +} + +.dialedCallIcon { + background-repeat: no-repeat; + background-position: center center; + width: 42px; + height: 36px; + display: inline-block; +} + +.callHistoryBox { + margin-top: 20px; + border-top-style: solid; + border-top-width: 2px; +} + +.callHistoryElement { + padding: 18px 0 18px 0; + box-shadow: none; + border-bottom-style: solid; + border-bottom-width: 2px; + overflow: hidden; + white-space: nowrap; +} + +.callHistoryIcon { + padding-top: 8px; + margin-left: 20px; +} +.callHistoryIconGen { + top: -10px; + background-size: 70%; +} + +.callHistoryDetails { + display: inline-block; + vertical-align: top; +} diff --git a/css/images/null.png b/css/images/null.png new file mode 100644 index 0000000000000000000000000000000000000000..841863cbd84c82e87de8dd1a1717527d8053781b GIT binary patch literal 2791 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 t000w7L_t(|+G6pQ!%0LzRCr$PUHNm| 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 literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 0000000..6e8b606 --- /dev/null +++ b/index.html @@ -0,0 +1,182 @@ + + + + + + + +Phone application + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
A-Z
+
+ +
+ +
+
+ +
+ +
+
1
+
+ 2 +
ABC
+
+
+ 3 +
DEF
+
+
+ 4 +
GHI
+
+
+ 5 +
JKL
+
+
+ 6 +
MNO
+
+
+ 7 +
PQRS
+
+
+ 8 +
TUV
+
+
+ 9 +
WXYZ
+
+
*
+
+ 0 +
+
+
+
#
+
+ +
+ + +
    +
    + +
    +
    +
    + +
    +
    00:00
    +
    +
    +
    +
    +
    +
    SPEAKER
    +
    MUTE
    +
    HOLD
    +
    ADD + CALL
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + \ No newline at end of file diff --git a/js/callhistorycarousel.js b/js/callhistorycarousel.js new file mode 100644 index 0000000..9306bb9 --- /dev/null +++ b/js/callhistorycarousel.js @@ -0,0 +1,268 @@ +/*global Phone, callContactCarousel*/ + +/** + */ + /** + * This class provides methods to operate with call history carousel. It wrapps handling CarouFredSel object and provides + * following operations: + * + * * Show placed, received and missed phone calls ordered by date of phone call + * * For each call displays phone number, contact name, date and time of phone call + * + * @module PhoneApplication + * @class Carousel + * @constructor + */ +var Carousel = function() { + "use strict"; + this.initializeSwipe(); +}; +/** +* This property holds call history data array for show in html. +* @property callHistory {Array} +*/ +Carousel.prototype.callHistory = []; +/** +* This property holds swipe object for internal use in carousel. +* @property swipe {Object} +* @private +*/ +Carousel.prototype.swipe = null; +/** +* This property holds callback function which is called after current element in carousel is changed. +* @property indexChangeCallback {Object} +* @private +*/ +Carousel.prototype.indexChangeCallback = null; +/** + * This method adds listener for current carousel element change. + * + * @method addIndexChangeListener + * @param indexChangeCallback {function()} Callback function called after current carousel element changed. + */ +Carousel.prototype.addIndexChangeListener = function(indexChangeCallback) { + "use strict"; + + this.indexChangeCallback = indexChangeCallback; +}; +/** + * This method initializes and configures carousel object + * + * @method initializeSwipe + * @private + */ +Carousel.prototype.initializeSwipe = function() { + "use strict"; + + var self = this; + if (!this.swipe) { + this.swipe = $('#contactsCarousel').carouFredSel({ + auto : false, + circular : false, + infinite : false, + width : 765, + items : { + visible : 3 + }, + swipe : { + items : 1, + duration : 150, + onMouse : true, + onTouch : true + }, + scroll : { + items : 1, + duration : 150, + onAfter : function(data) { + if (!!self.indexChangeCallback) { + self.indexChangeCallback(self.getCurrentPosition()); + } + } + } + }); + if (!this.swipe.length) { + this.swipe = null; + } + } +}; +/** + * This method provides the index of current selected item of the carousel. + * + * @method getCurrentPosition + * @return Current {Integer} index position in carousel. + */ +Carousel.prototype.getCurrentPosition = function() { + "use strict"; + var self = this; + if (!!self.swipe) { + var pos = parseInt(self.swipe.triggerHandler("currentPosition"), 10); + return pos; + } + return null; +}; +/** + * This method moves current position of the carousel to the given index. + * + * @method slideTo + * @param index {Integer} New index position in carousel + */ +Carousel.prototype.slideTo = function(index) { + "use strict"; + if (!!this.swipe && index >= 0 && index < this.callHistory.length) { + this.swipe.trigger("slideTo", index); + } +}; +/** + * This method fills carousel with call history data and resets its current position to start. + * + * @method loadCallHistory + * @param callHistory {Array} Call history array. + * @param index {Integer} New index position in carousel. + */ +Carousel.prototype.loadCallHistory = function(callHistory, index) { + "use strict"; + this.removeAllItems(); + this.callHistory = callHistory; + this.insertPagesToSwipe(); + if (index >= 0 && index < this.callHistory.length && !!this.swipe) { + this.swipe.trigger("slideTo", [ index, 0, { + duration : 0 + } ]); + } +}; +/** + * This method creates one carousel item for swipe. + * + * @method createSwipeItem + * @param callHistory {Array} Call history array. + * @param index {Integer} Carousel item index it is use as div id in html. + * @return {String} New carousel item as html string. + * @private + */ +Carousel.prototype.createSwipeItem = function(callHistoryEntry, index) { + "use strict"; + var self = this; + + if (!!callHistoryEntry) { + var carouselItem; + + var contact = null; + if (!!callHistoryEntry.remoteParties && callHistoryEntry.remoteParties.length) { + contact = Phone.getContactByPersonId(callHistoryEntry.remoteParties[0].personId); + } + + var id = ""; + var name = ""; + var photoURI = ""; + var phoneNumber = ""; + var startTime = callHistoryEntry.startTime || ""; + var direction = callHistoryEntry.direction || ""; + + if (!!callHistoryEntry.remoteParties && callHistoryEntry.remoteParties.length) { + // personId = phoneNumber + phoneNumber = callHistoryEntry.remoteParties[0].personId; + } + + if (!!contact) { + if (!!contact.id) { + id = contact.id; + } + if (!!contact.photoURI) { + photoURI = contact.photoURI; + } + if (phoneNumber === "" && !!contact.phoneNumbers && contact.phoneNumbers.length) { + phoneNumber = contact.phoneNumbers[0].number; + } + name = Phone.getDisplayNameStr(contact); + } + + if (name === "") { + name = "Unknown"; + } + + carouselItem = '
  • '; + carouselItem += '
    '; + carouselItem += '
    '; + carouselItem += '
    '; + carouselItem += '
    '; + carouselItem += '
    ' + name + '
    '; + carouselItem += '
    ' + phoneNumber + '
    '; + carouselItem += '
    '; + carouselItem += '
    '; + carouselItem += '
    '; + carouselItem += '
    ' + startTime + '
    '; + carouselItem += '
    ' + direction + '
    '; + carouselItem += '
  • '; + + carouselItem = $(carouselItem); + carouselItem.data("callhistory", callHistoryEntry); + carouselItem.data("contact", contact); + carouselItem.click(function() { + self.swipe.trigger("slideTo", [ $(this), -1 ]); + var hystoryEntry = $(this).data("callhistory"); + var contactEntry = $(this).data("contact"); + var contact = { + name : { + displayName : contactEntry.name.displayName, + firstName : contactEntry.name.firstName, + lastName : contactEntry.name.lastName + }, + photoURI : contactEntry.photoURI, + phoneNumbers : [ { + number : hystoryEntry.remoteParties[0].personId + } ] + + }; + callContactCarousel(contact); + }); + return carouselItem; + } + + return null; +}; +/** + * This method inserts pages whit carousel elements to swipe. + * + * @method insertPagesToSwipe + * @private + */ +Carousel.prototype.insertPagesToSwipe = function() { + "use strict"; + var self = this; + var carouselItem; + for ( var index = this.callHistory.length - 1; index >= 0; --index) { + carouselItem = this.createSwipeItem(this.callHistory[index], index); + if (!!carouselItem && !!this.swipe) { + this.swipe.trigger("insertItem", [ carouselItem, 0 ]); + } + } + this.addCarouselEdges(); +}; +/** + * This method removes all item from carousel. + * + * @method removeAllItems + */ +Carousel.prototype.removeAllItems = function() { + "use strict"; + var carouselItem; + + if (!!this.swipe) { + for ( var index = this.callHistory.length + 1; index >= 0; --index) { + this.swipe.trigger("removeItem", index); + } + } +}; +/** + * This method adds emty carousel items to the beginning and the end of the carousel + * (to make sure first and last visible items appear in the middle of screen instead of at the edges when swiped to edges of carousel). + * @method addCarouselEdges + */ +Carousel.prototype.addCarouselEdges = function() { + "use strict"; + if (!!this.swipe) { + var html = "
  • "; + this.swipe.trigger("insertItem", [ html, 0 ]); + this.swipe.trigger("insertItem", [ html, "end", true ]); + } +}; diff --git a/js/contacts_library.js b/js/contacts_library.js new file mode 100644 index 0000000..172f879 --- /dev/null +++ b/js/contacts_library.js @@ -0,0 +1,210 @@ +/*global Phone, callContactCarousel, GRID_TAB, LIST_TAB, loadTemplate, ko*/ + +/** + * Class which provides methods to operate with contacts library which displays all contact information (name, phone number, photo) from paired device ordered by name. + * + * @class ContactsLibrary + * @module PhoneApplication + */ +var ContactsLibrary = { + currentSelectedContact : "", + /** + * Method initializes contacts library. + * + * @method init + */ + init : function() { + "use strict"; + $('#library').library("setSectionTitle", "PHONE CONTACTS"); + $('#library').library("init"); + + var tabMenuModel = { + Tabs : [ { + text : "CONTACTS A-Z", + selected : true + } ] + }; + + $('#library').library("tabMenuTemplateCompile", tabMenuModel); + + $('#library').bind('eventClick_GridViewBtn', function() { + ContactsLibrary.showContacts(); + }); + + $('#library').bind('eventClick_ListViewBtn', function() { + ContactsLibrary.showContacts(); + }); + + $('#library').bind('eventClick_SearchViewBtn', function() { + }); + + $('#library').bind('eventClick_menuItemBtn', function() { + ContactsLibrary.showContacts(); + }); + + $('#library').bind('eventClick_closeSubpanel', function() { + }); + + $("#alphabetBookmarkList").on("letterClick", function(event, letter) { + console.log(letter); + Phone.contactsAlphabetFilter(letter === "*" ? "" : letter); + }); + + ContactsLibrary.showContacts(); + }, + /** + * Method unhides library page. + * + * @method show + */ + show : function() { + "use strict"; + $('#library').library("showPage"); + }, + /** + * Method hides library page. + * + * @method hide + */ + hide : function() { + "use strict"; + $('#library').library("hidePage"); + }, + /** + * Method opens contact detail. + * + * @method openContactDetail + * @param contact + * {Object} Object representing contact's information. + */ + openContactDetail : function(contact) { + "use strict"; + if (!!contact) { + ContactsLibrary.currentSelectedContact = contact; + var history = Phone.getCallHistoryByPersonId(contact.personId); + var formattedContact = ContactsLibrary.initContactDetail(contact); + formattedContact.history = history; + ContactsLibrary.renderContactDetailView(formattedContact); + } else { + console.log("Supplied contact is null."); + } + }, + /** + * Method renders search view. + * + * @method renderContactDetailView + * @param contact + * {Object} Contact object. + */ + renderContactDetailView : function(contact) { + "use strict"; + console.log("open contact called"); + var subpanelModel = { + textTitle : "CONTACT", + textSubtitle : contact.name || "Unknown", + actionName : "BACK", + action : function() { + console.log("back clicked"); + ContactsLibrary.showContacts(); + ContactsLibrary.currentSelectedContact = ""; + } + }; + $('#library').library("subpanelContentTemplateCompile", subpanelModel); + $('#library').library("clearContent"); + $('#library').library("setContentDelegate", "templates/libraryContactDetailDelegate.html"); + $('#library').library("contentTemplateCompile", contact, "contactDetail", function() { + $("#contactDetailMobileTitle").boxCaptionPlugin('initSmall', "MOBILE"); + $("#contactDetailEmailTitle").boxCaptionPlugin('initSmall', "EMAIL"); + $("#contactDetailAddressTitle").boxCaptionPlugin('initSmall', "ADDRESS"); + }); + }, + /** + * Method which shows contacts in grid or list view. + * + * @method showContacts + */ + showContacts : function() { + "use strict"; + console.log("show contacts called"); + var view = ""; + switch ($('#library').library('getSelectetLeftTabIndex')) { + case GRID_TAB: + view = "contactsLibraryContentGrid"; + break; + case LIST_TAB: + view = "contactsLibraryContentList"; + break; + default: + view = "contactsLibraryContentList"; + break; + } + $('#library').library('closeSubpanel'); + $('#library').library("clearContent"); + $('#library').library("changeContentClass", view); + loadTemplate("templates/", "template-contacts", function() { + var contactsElement = '
    '; + $(contactsElement).appendTo($('.' + view)); + ko.applyBindings(Phone); + }); + }, + /** + * Method which initializes contact detail. + * + * @method initContactDetail + * @param contact + * {Object} Contact object. + */ + initContactDetail : function(contact) { + "use strict"; + var tempContact = { + id : "", + name : "", + phoneNumber : "", + email : "", + photoURI : "", + address : "", + isFavorite : false, + history : [] + }; + + if (!!contact) { + var str = ""; + + if (!!contact.uid) { + tempContact.id = contact.uid; + } + + if (!!contact.name) { + tempContact.name = Phone.getDisplayNameStr(contact); + } + + if (!!contact.phoneNumbers && contact.phoneNumbers.length && !!contact.phoneNumbers[0].number) { + tempContact.phoneNumber = contact.phoneNumbers[0].number.trim(); + } + + if (!!contact.emails && contact.emails.length && !!contact.emails[0].email) { + tempContact.email = contact.emails[0].email.trim(); + } + + if (!!contact.photoURI) { + tempContact.photoURI = contact.photoURI.trim(); + } + + if (!!contact.addresses && contact.addresses.length) { + str = !!contact.addresses[0].streetAddress ? contact.addresses[0].streetAddress.trim() + "
    " : ""; + str += !!contact.addresses[0].city ? contact.addresses[0].city.trim() + "
    " : ""; + str += !!contact.addresses[0].country ? contact.addresses[0].country.trim() + "
    " : ""; + str += !!contact.addresses[0].postalCode ? contact.addresses[0].postalCode.trim() : ""; + + if (str.toString().trim() === "") { + str = "-"; + } + + tempContact.address = str.trim(); + } + + tempContact.isFavorite = contact.isFavorite; + } + return tempContact; + } +}; diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..e6d9826 --- /dev/null +++ b/js/main.js @@ -0,0 +1,809 @@ +/*global disconnectCall, Bootstrap, Carousel, ContactsLibrary, getAppByID, disconnectCall, Configuration, Speech, Phone, changeCssBgImageColor, ThemeKeyColorSelected */ + +/** + * This application provides voice call from paired Bluetooth phone. Application uses following APIs: + * + * * {{#crossLink "Phone"}}{{/crossLink}} library + * * [tizen.bt]() as replacement of [tizen.bluetooth](https://developer.tizen.org/dev-guide/2.2.0/org.tizen.web.device.apireference/tizen/bluetooth.html) API due to + * conficts in underlying framework + * + * Application supports multiple connected devices, however only one of the devices can be selected at the time. + * Selection is done from {{#crossLink "Bluetooth"}}{{/crossLink}} UI. In case that phone has active call additional {{#crossLink "Carousel"}}{{/crossLink}} + * element is replaced by {{#crossLink "CallDuration"}}{{/crossLink}} element. + * + * Application allows following operations: + * + * * {{#crossLink "Phone/acceptCall:method"}}Place call{{/crossLink}} + * * Handles incoming calls passed from {{#crossLink "IncomingCall"}}{{/crossLink}} widget + * * Display {{#crossLink "Carousel"}}call history{{/crossLink}} + * * Display {{#crossLink "ContactsLibrary"}}contact list{{/crossLink}} + * * Mute/unmute call - not working due to [TIVI-2448](https://bugs.tizen.org/jira/browse/TIVI-2448) + * + * Additionaly application can be controlled using speech recognition via {{#crossLink "Speech"}}{{/crossLink}} component. + * + * Hover and click on elements in images below to navigate to components of Phone application. + * + * + * + * top bar icons + * clock + * bottom panel + * Settings + * Contacts library + * Call button + * + * + * + * + * @module PhoneApplication + * @main PhoneApplication + * @class Phone + */ + +/** + * Holds object of input for dialing phone number. + * + * @property telInput {Object} + */ +var telInput; +/** + * Instance of class Bootstrap, this class provides unified way to boot up the HTML applications by loading shared components in proper order. + * * {{#crossLink "Bootstrap"}}{{/crossLink}} + * + * @property bootstrap {Object} + */ +var bootstrap; + +/** +* Instance of class Carousel, this class provides methods to operate with hystory carousel. +* * {{#crossLink "Carousel"}}{{/crossLink}} +* +* @property callHistoryCarousel {Object} +*/ +var callHistoryCarousel = null; + +/** +* This property holds information about accept Phone call from Other widgets. +* If is true, phone call came from another widget. +* @property acceptPhoneCallFromOtherWidget {Boolean} +*/ +var acceptPhoneCallFromOtherWidget = false; + +/** + * Class handling user input from keyboard + * + * @class keyboard + * @static + */ +var keyboard = { + /** + * property holding Interval within which next click on the same key is considered as rotating associated characters with the given key. + * + * @property clickInterval {int} + */ + clickInterval: 1000, + + /** + * property holding info about last pressed key + * + * @property pressedKey {string} + */ + pressedKey: "-1", + + /** + * Array of input object holding info about main key character, all associated characters with the given key and index of currently used key + * + * @property inputs {Array of Object} + */ + inputs: [{ + key: "1", + values: ["1"], + index: 0 + }, { + key: "2", + values: ["2", "A", "B", "C"], + index: 0 + }, { + key: "3", + values: ["3", "D", "E", "F"], + index: 0 + }, { + key: "4", + values: ["4", "G", "H", "I"], + index: 0 + }, { + key: "5", + values: ["5", "J", "K", "L"], + index: 0 + }, { + key: "6", + values: ["6", "M", "N", "O"], + index: 0 + }, { + key: "7", + values: ["7", "P", "Q", "R", "S"], + index: 0 + }, { + key: "8", + values: ["8", "T", "U", "V"], + index: 0 + }, { + key: "9", + values: ["9", "W", "X", "Y", "Z"], + index: 0 + }, { + key: "*", + values: ["*"], + index: 0 + }, { + key: "0", + values: ["0", "+"], + index: 0 + }, { + key: "#", + values: ["#"], + index: 0 + }], + + /** + * property holding time of clickedInterval started + * + * @property startedTime {Date} + */ + startedTime: null, + + /** + * property holding input object associated with last pressed key + * + * @property selectedInput {Object} + */ + selectedInput: null, + + /** + * function starting clickInterval + * + * @method startTimer + */ + startTimer: function() { + "use strict"; + keyboard.startedTime = new Date(); + }, + + /** + * function testing if clickInterval expired + * + * @method intervalExpired + * @param currTime {Date} + */ + intervalExpired: function(currTime) { + "use strict"; + if (currTime - keyboard.startedTime > keyboard.clickInterval) { + keyboard.resetIndices(); + return true; + } + return false; + }, + + /** + * function reseting indices for all input objects to 0 + * + * @method resetIndices + */ + resetIndices: function() { + "use strict"; + for (var i in keyboard.inputs) { + if (keyboard.inputs.hasOwnProperty(i)) { + keyboard.inputs[i].index = 0; + } + } + }, + + /** + * function cycling associated input characters within clickInterval + * + * @method nextKey + */ + nextKey: function() { + "use strict"; + for (var i in keyboard.inputs) { + if (keyboard.pressedKey === keyboard.inputs[i].key) { + if (keyboard.inputs[i].values.length > 1) { + if (keyboard.inputs[i].index < keyboard.inputs[i].values.length - 1) { + keyboard.inputs[i].index += 1; + } else { + keyboard.inputs[i].index = 0; + } + } else { + keyboard.inputs[i].index = 0; + } + keyboard.selectedInput = keyboard.inputs[i]; + return keyboard.inputs[i].values[keyboard.inputs[i].index]; + } + } + return keyboard.pressedKey; + }, + + /** + * function setting selected input object based on last pressed key + * + * @method selectInput + */ + selectInput: function() { + "use strict"; + for (var i in keyboard.inputs) { + if (keyboard.pressedKey === keyboard.inputs[i].key) { + keyboard.selectedInput = keyboard.inputs[i]; + } + } + } +}; + +/** + * Holds status of calling panel initialization. + * + * @property callingPanelInitialized {Boolean} if is true, calling panel is initialized + * @default false + */ +var callingPanelInitialized = false; + +/** + * Class which provides initialize call info. + * + * @method initializeCallInfo + * @param contact {Object} Contact object. + * @for Phone + */ +function initializeCallInfo(contact) { + "use strict"; + var callNumber; + console.log(contact); + if ( !! contact) { + + if ( !! contact.name) { + var nameStr; + if (contact.name.displayName) { + nameStr = contact.name.displayName; + } else { + nameStr = !! contact.name.firstName ? contact.name.firstName : ""; + nameStr += !! contact.name.lastName ? " " + contact.name.lastName : ""; + } + $("#callName").html(nameStr.trim()); + } else { + $("#callName").html("Unknown"); + } + + if ( !! contact.phoneNumbers && contact.phoneNumbers.length) { + callNumber = !! contact.phoneNumbers[0].number ? contact.phoneNumbers[0].number : ""; + $("#callNumber").html(callNumber); + } else { + $("#callNumber").html("Unknown"); + } + + if ( !! contact.photoURI) { + $("#callPhoto").attr("src", contact.photoURI); + } + } else { + $("#callName").html("Unknown"); + $("#callNumber").html("Unknown"); + } + + if (!callingPanelInitialized) { + $(".noVolumeSlider").noUiSlider({ + range: [0, 100], + step: 1, + start: 50, + handles: 1, + connect: "lower", + orientation: "horizontal", + slide: function() { + var VolumeSlider = parseInt($(".noVolumeSlider").val(), 10); + console.log("noVolumeSlider" + VolumeSlider); + } + }); + callingPanelInitialized = true; + } + + if ($("#callButton").hasClass("callingFalse")) { + $("#callButton").removeClass("callingFalse"); + $("#callButton").addClass("callingTrue"); + } +} + +/** + * Class which provides methods to operate with call duration. Component show information about current call (call number or call contact, time duration of call). + * + * @class CallDuration + * @static + */ +var CallDuration = { + /** + * Holds value of seconds. + * + * @property sec {Integer} + */ + sec: 0, + /** + * Holds value of minutes. + * + * @property min {Integer} + */ + min: 0, + /** + * Holds value of hours. + * + * @property hour {Integer} + */ + hour: 0, + /** + * Holds object of timer. + * + * @property timeout {Object} + */ + timeout: null, + /** + * Method provides initialization of call timers. + * + * @method initialize + */ + startWatch: function() { + "use strict"; + var self = this; + if (!this.timeout) { + this.timeout = window.setInterval(function() { + self.stopwatch(); + }, 1000); + } else { + this.resetIt(); + this.timeout = window.setInterval(function() { + self.stopwatch(); + }, 1000); + } + }, + + /** + * Method provides reset call timers. + * + * @method resetIt + */ + resetIt: function() { + "use strict"; + CallDuration.sec = 0; + CallDuration.min = 0; + CallDuration.hour = 0; + window.clearTimeout(CallDuration.timeout); + var callStatus = tizen.phone.activeCall.state.toLowerCase(); + if (callStatus === "DIALING".toLowerCase()) { + $("#callDuration").html("DIALING"); + } else if (callStatus === "DISCONNECTED".toLowerCase()) { + $("#callDuration").html("ENDED"); + } else { + $("#callDuration").html( + ((CallDuration.min <= 9) ? "0" + CallDuration.min : CallDuration.min) + ":" + ((CallDuration.sec <= 9) ? "0" + CallDuration.sec : CallDuration.sec)); + } + + }, + /** + * Method provides call stop watch. + * + * @method stopwatch + */ + stopwatch: function() { + "use strict"; + + var callStatus = tizen.phone.activeCall.state.toLowerCase(); + if (callStatus === "DIALING".toLowerCase()) { + $("#callDuration").html("DIALING"); + } else if (callStatus === "DISCONNECTED".toLowerCase()) { + $("#callDuration").html("ENDED"); + + } else { + CallDuration.sec++; + if (CallDuration.sec === 60) { + CallDuration.sec = 0; + CallDuration.min++; + } + + if (CallDuration.min === 60) { + CallDuration.min = 0; + CallDuration.hour++; + } + $("#callDuration").html( + ((CallDuration.min <= 9) ? "0" + CallDuration.min : CallDuration.min) + ":" + ((CallDuration.sec <= 9) ? "0" + CallDuration.sec : CallDuration.sec)); + } + } +}; + +/** +* This property holds information about mute of call. If is true, phone call is mute. +* +* @property VolumeMuteStatus +* @default false +*/ +var VolumeMuteStatus = false; + +/** + * Class provides a muting of a call + * + * @method muteCall + * @for Phone + */ +function muteCall() { + "use strict"; + VolumeMuteStatus = VolumeMuteStatus ? false : true; + + // Not working due to TIVI-2448 + if (tizen.phone) { + //tizen.phone.muteCall(VolumeMuteStatus); + } + if (VolumeMuteStatus) { + changeCssBgImageColor(".muteButton", ThemeKeyColorSelected); + $(".muteButton").addClass("fontColorSelected"); + } else { + changeCssBgImageColor(".muteButton", "#FFFFFF"); + $(".muteButton").removeClass("fontColorSelected"); + } +} + +/** + * Class which provides methods to call contact. + * + * @method acceptCall + * @param contact {Object} Contact object. + * @for Phone + */ +function acceptCall(contact) { + "use strict"; + + ContactsLibrary.hide(); + if ($("#settingsTabs").tabs) { + $("#settingsTabs").tabs("hidePage"); + } + $("#callBox").removeClass("callBoxHidden"); + $("#callBox").addClass("callBoxShow"); + $('#contactsCarouselBox').removeClass("contactsCarouselBoxShow"); + $('#contactsCarouselBox').addClass("contactsCarouselBoxHide"); + if (tizen.phone) { + CallDuration.resetIt(); + + initializeCallInfo(contact); + var callStatus = tizen.phone.activeCall.state.toLowerCase(); + if (callStatus !== "ACTIVE".toLowerCase() && callStatus !== "DIALING".toLowerCase()) { + + if (callStatus === "INCOMING".toLowerCase()) { + tizen.phone.answerCall(function(result) { + console.log(result.message); + }); + } else if (callStatus === "DISCONNECTED".toLowerCase()) { + + var callNumber = contact.phoneNumbers[0] && contact.phoneNumbers[0].number ? contact.phoneNumbers[0].number : ""; + tizen.phone.invokeCall(callNumber, function(result) { + console.log(result.message); + }); + + } + + } else if (callStatus === "ACTIVE".toLowerCase()) { + CallDuration.startWatch(); + } + } +} + +/** + * Class which provides disconnect call. + * + * @method disconnectCall + * @for Phone + */ +function disconnectCall() { + "use strict"; + $("#callButton").removeClass("callingTrue"); + $("#callButton").addClass("callingFalse"); + if (acceptPhoneCallFromOtherWidget !== true) { + $("#callBox").removeClass("callBoxShow"); + $("#callBox").addClass("callBoxHidden"); + $('#contactsCarouselBox').removeClass("contactsCarouselBoxHide"); + $('#contactsCarouselBox').addClass("contactsCarouselBoxShow"); + } + CallDuration.resetIt(); + if (tizen.phone) { + tizen.phone.hangupCall(function(result) { + console.log(result.message); + }); + } + $("#inputPhoneNumber").val(''); +} + +$(document).ready( + function() { + "use strict"; + setTimeout(function() { + /* initialize phone widget by remote device status */ + if (tizen.phone) { + tizen.phone.getSelectedRemoteDevice(function(selectedRemoteDevice){ + if (selectedRemoteDevice !== "") { + $("#noPairedDevice").hide(); + $("#loadingHistorySpinnerWrapper").show(); + } else { + $("#noPairedDevice").show(); + } + }); + /* initialize phone widget by active call status */ + var callStatus = tizen.phone.activeCall.state.toLowerCase(); + if (callStatus === "INCOMING".toLowerCase() || callStatus === "DIALING".toLowerCase() || callStatus === "ACTIVE".toLowerCase()) { + var contact; + if (tizen.phone.callState) { + contact = tizen.phone.activeCall.contact; + } + acceptPhoneCallFromOtherWidget = true; + acceptCall(contact); + } else if (callStatus === "DISCONNECTED".toLowerCase()) { + disconnectCall(); + } + } + /* start keyboard timer */ + keyboard.startTimer(); + /* initialize bootstrap */ + bootstrap = new Bootstrap(function(status) { + telInput = $("#inputPhoneNumber"); + $("#clockElement").ClockPlugin('init', 5); + $("#clockElement").ClockPlugin('startTimer'); + $("#topBarIcons").topBarIconsPlugin('init', 'phone'); + $('#bottomPanel').bottomPanel('init'); + if (!callHistoryCarousel) { + + callHistoryCarousel = new Carousel(); + } + + if (typeof Phone !== "undefined") { + /* add listener to selected remote device */ + tizen.phone.addRemoteDeviceSelectedListener(function(returnID) { + if ((!!returnID && !!returnID.error) || (!!returnID && !!returnID.value && returnID.value === "")) { + $("#loadingHistorySpinnerWrapper").hide(); + $(".caroufredsel_wrapper").hide(); + $("#noPairedDevice").show(); + } else { + $("#noPairedDevice").hide(); + $("#loadingHistorySpinnerWrapper").show(); + $(".caroufredsel_wrapper").show(); + } + }); + /* initialize contacts and call history, if not accept phone call from another widget */ + if (acceptPhoneCallFromOtherWidget !== true) { + window.setTimeout(function() { + Phone.loadContacts(function(err) { + if (!err) { + ContactsLibrary.init(); + Phone.loadCallHistory(function(err) { + if (!err) { + $("#loadingHistorySpinnerWrapper").hide(); + callHistoryCarousel.loadCallHistory(Phone.callHistory(), 0); + } + }); + } + }); + + }, 2000); + } + /* add listener to change contacts list */ + tizen.phone.addContactsChangedListener(function() { + if (acceptPhoneCallFromOtherWidget !== true) { + window.setTimeout(function() { + Phone.loadContacts(function(err) { + if (!err) { + ContactsLibrary.init(); + } + }); + }, 1000); + } + }); + /* add listener to change call history */ + tizen.phone.addCallHistoryChangedListener(function() { + $("#loadingHistorySpinnerWrapper").show(); + if (acceptPhoneCallFromOtherWidget !== true) { + window.setTimeout(function() { + Phone.loadCallHistory(function(err) { + if (!err) { + $("#loadingHistorySpinnerWrapper").hide(); + callHistoryCarousel.loadCallHistory(Phone.callHistory(), 0); + + } + }); + }, 1000); + } + + }); + } + + $("#contactsLibraryButton").bind('click', function() { + + ContactsLibrary.show(); + + }); + + $(".numbersBox").delegate("#numberButton", "click", function() { + var pressTime = new Date(), + number, oneCharPX = 32; + if (keyboard.intervalExpired(pressTime)) { + number = telInput.attr("value") + $(this).data("id"); + telInput.attr("value", number); + $('#inputPhoneNumber').scrollLeft(number.length * oneCharPX); + keyboard.pressedKey = $(this).data("id").toString(); + + } else { + if (keyboard.pressedKey === "-1" || keyboard.pressedKey !== $(this).data("id").toString()) { + number = telInput.attr("value") + $(this).data("id"); + telInput.attr("value", number); + $('#inputPhoneNumber').scrollLeft(number.length * oneCharPX); + keyboard.pressedKey = $(this).data("id").toString(); + } else { + var phoneNumText = telInput.attr("value"); + if (keyboard.pressedKey === $(this).data("id").toString() && keyboard.selectedInput !== null && keyboard.selectedInput.values.length === 1) { + number = telInput.attr("value") + $(this).data("id"); + telInput.attr("value", number); + $('#inputPhoneNumber').scrollLeft(number.length * oneCharPX); + } else { + var numToUpdate = phoneNumText.slice(0, phoneNumText.length - 1); + numToUpdate += keyboard.nextKey(); + telInput.attr("value", numToUpdate); + + } + } + } + keyboard.selectInput(); + keyboard.startTimer(); + return false; + }); + + $(".inputPhoneNumberBox").delegate("#deleteButton", "click", function() { + var number = telInput.attr("value"); + number = number.slice(0, number.length - 1); + telInput.attr("value", number); + return false; + }); + + $('#callButton').bind('click', function() { + var phoneNumber = $("#inputPhoneNumber").val(); + if ($("#callBox").hasClass("callBoxShow")) { + disconnectCall(); + } else if (phoneNumber !== "") { + var contact = Phone.getContactByPhoneNumber(phoneNumber); + if (contact === null) { + + contact = { + phoneNumbers: [{ + number: phoneNumber + }] + }; + + } + acceptCall(contact); + } + }); + $('.muteButton').bind('click', function() { + muteCall(); + }); + if (tizen.phone) { + /* add listener to change call history entry, because if call is ended tizen.phone give back only last history object */ + tizen.phone.addCallHistoryEntryAddedListener(function(contact) { + if (acceptPhoneCallFromOtherWidget !== true) { + + + var tmpCallHistory = Phone.callHistory(); + var tmpContact = []; + tmpContact.push(contact); + tmpContact = Phone.formatCallHistory(tmpContact); + tmpCallHistory.unshift(tmpContact[0]); + Phone.callHistory(tmpCallHistory); + + callHistoryCarousel.loadCallHistory(Phone.callHistory(), 0); + + } + }); + /* add listener to change call state */ + tizen.phone.addCallChangedListener(function(result) { + var contact; + if ( !! result.contact.name) { + contact = result.contact; + } else { + contact = { + phoneNumbers: [{ + /* jshint camelcase: false */ + number: tizen.phone.activeCall.line_id + /* jshint camelcase: true */ + }] + + }; + } + + console.log("result.state " + result.state); + + switch (result.state.toLowerCase()) { + case "DISCONNECTED".toLowerCase(): + + disconnectCall(contact); + + if (acceptPhoneCallFromOtherWidget === true) { + + window.setTimeout(function() { + if (typeof tizen !== "undefined") { + tizen.application.getCurrentApplication().exit(); + } + }, 1000); + } + + Configuration.set("acceptedCall", "false"); + + break; + case "ACTIVE".toLowerCase(): + if (Configuration._values.acceptedCall !== "true") { + /* global self */ + self.incomingCall.acceptIncommingCall(); + CallDuration.startWatch(); + console.log("phone active"); + Configuration.set("acceptedCall", "true"); + } + break; + case "DIALING".toLowerCase(): + acceptCall(contact); + break; + } + }); + } + + if (typeof(Speech) !== 'undefined') { + /* add listener to voice recognition */ + Speech.addVoiceRecognitionListener({ + oncall: function() { + if (ContactsLibrary.currentSelectedContact !== "" && $('#library').library("isVisible")) { + acceptCall(ContactsLibrary.currentSelectedContact); + } + } + }); + } else { + console.warn("Speech API is not available."); + } + + }); + }, 0); + + }); + +/** + * Class which provides call contact carousel. + * + * @method callContactCarousel + * @param contact {Object} Contact object. + * @for Phone + * + */ + +function callContactCarousel(contact) { + "use strict"; + + acceptCall(contact); +} + +/** + * Class which provides call by contact ID. + * + * @method callContactById + * @param contactId {String} Contact id. + * @for Phone + */ +function callContactById(contactId) { + "use strict"; + $("#contactDetailMobile").addClass("fontColorSelected "); + if (contactId !== "" && typeof contactId !== undefined) { + + var contactObject = Phone.getContactById(contactId); + if ( !! contactObject) { + window.setTimeout(function() { + acceptCall(contactObject); + $("#contactDetailMobile").removeClass("fontColorSelected "); + }, 500); + } else { + console.log("contact not found"); + $("#contactDetailMobile").removeClass("fontColorSelected "); + } + } +} diff --git a/js/phone.js b/js/phone.js new file mode 100644 index 0000000..35ebedd --- /dev/null +++ b/js/phone.js @@ -0,0 +1,361 @@ +/* global ko, formatPhoneNumber, moment */ + +/** + * This class provides methods to operate with contacts and call history from [tizen.phone](./native/group__phoned.html). + * Sample contact object looks like: + * + * { + * uid: "JeremyMartinson15417543010", + * personId: "15417543010", + * name: { + * firstName: "Jeremy", + * lastName: "Martinson" + * displayName :"Jeremy Martinson" + * }, + * phoneNumbers: { + * number:"1-541-754-3010" + * }, + * emails: { + * email: "Jeremy.Martinson@gmail.com" + * }, + * photoURI: { + * photoURI: "url://" + * }, + * addresses: { + * streetAddress: "455 Larkspur Dr.", + * city: "San Jose", + * country: "California", + * postalCode: " " + * } + * } + * + * @class Phone + * @module PhoneApplication + * @constructor + */ +var Phone = (function() { + "use strict"; + + function Phone() { + var self = this; + if (typeof(tizen.phone) !== 'undefined') { + this.phone = tizen.phone; + } else { + this.phone = null; + } + this.contacts = ko.observableArray([]); + this.callHistory = ko.observableArray([]); + + this.contactsAlphabetFilter = ko.observable(""); + + this.contactsComputed = ko.computed(function() { + if (self.contactsAlphabetFilter() !== "") { + return ko.utils.arrayFilter(self.contacts(), function(contact) { + if ( !! contact.name && !! contact.name.leftLastName) { + return contact.name.lastName.toString().toLowerCase().trim().indexOf( + self.contactsAlphabetFilter().toString().toLowerCase().trim()) === 0; + } + return false; + }); + } + return self.contacts(); + }); + } + +/** + * This method download contacts from tizen.phone. Using API method tizen.phone.getContacts. + * @method loadContacts + * @param callback {function(error)} Callback function called after method is finished. Parameter `error` will contain any error that was intercepted. + */ + + Phone.prototype.loadContacts = function(callback) { + var self = this; + var i, conntactsArrayLength; + if ( !! self.phone) { + + self.phone.getContacts(0, function(contactsArray) { + + contactsArray = self.formatContacts(contactsArray); + self.contacts(contactsArray); + if ( !! callback) { + callback(null); + } + }, function(err) { + console.log("Error(" + err.code + "): " + err.message); + if ( !! callback) { + callback(err); + } + }); + } else { + if ( !! callback) { + callback("Phone API is not available."); + } + } + }; + + /** + * This method provides compare contact by last name, it is use in observable array from [knockout library](http://knockoutjs.com/documentation/observableArrays.html). + * @method compareByLastName + * @param left {Object} left compare element + * @param right {Object} right compare element + */ + + Phone.prototype.compareByLastName = function(left, right) { + + var leftLastName = "Unknown"; + if ( !!left.name && !!left.name.lastName && left.name.lastName !== "") { + leftLastName = left.name.lastName; + } + leftLastName = leftLastName.toString().trim().toLowerCase(); + var rightLastName = "Unknown"; + if ( !!right.name && !!right.name.lastName && right.name.lastName !== "") { + rightLastName = right.name.lastName; + } + rightLastName = rightLastName.toString().trim().toLowerCase(); + return leftLastName === rightLastName ? 0 : (leftLastName < rightLastName) ? -1 : 1; + }; + /** + * This method compares call history items by date, it is used in observable array from [knockout library](http://knockoutjs.com/documentation/observableArrays.html). + * @method compareHistoryByDate + * @param left {Object} left compare element + * @param right {Object} right compare element + */ + Phone.prototype.compareHistoryByDate = function(left, right) { + + var monthNames = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"]; + + var tmpLeftDateTime = left.startTime.toUpperCase().split(" "); + var tmpLeftDate = tmpLeftDateTime[0].split("/"); + tmpLeftDate[0] = tmpLeftDate[0].indexOf(tmpLeftDate[0]) + 1; + var tmpLeftTime = tmpLeftDateTime[1].split(":"); + var leftDate = new Date(parseInt(tmpLeftDate[0], 10), parseInt(tmpLeftDate[1], 10), parseInt(tmpLeftDate[2], 10), parseInt(tmpLeftTime[0], 10), parseInt(tmpLeftTime[1], 10)); + + var tmpRightDateTime = right.startTime.toUpperCase().split(" "); + var tmpRightDate = tmpRightDateTime[0].split("/"); + tmpRightDate[0] = tmpRightDate[0].indexOf(tmpRightDate[0]) + 1; + var tmpRightTime = tmpRightDateTime[1].split(":"); + var rightDate = new Date(parseInt(tmpRightDate[0], 10), parseInt(tmpRightDate[1], 10), parseInt(tmpRightDate[2], 10), parseInt(tmpRightTime[0], 10), parseInt(tmpRightTime[1], 10)); + + return leftDate === rightDate ? 0 : (leftDate > rightDate) ? -1 : 1; + + }; + /** + * This method clear contacts array. + * @method clearContacts + */ + Phone.prototype.clearContacts = function() { + + var self = this; + self.contacts.removeAll(); + self.contacts([]); + }; + /** + * This method searches contact in contacts list by id . + * @method getContactById + * @param id {String} search contact id + * @return {Object} if contact is found, return contact. Else return first element from contacts list. + */ + Phone.prototype.getContactById = function(id) { + + var self = this; + var contact = ko.utils.arrayFirst(self.contacts(), function(contact) { + return contact.uid === id; + }); + return contact; + }; + /** + * This method searches contact in contacts list by personId. + * @method getContactByPersonId + * @param id {String} search contact personId + * @return {Object} if contact is found, return contact. Else return first element from contacts list. + */ + Phone.prototype.getContactByPersonId = function(personId) { + + var self = this; + var contact = ko.utils.arrayFirst(self.contacts(), function(contact) { + if (contact.personId === personId) { + return true; + } + + if ( !! contact.phoneNumbers && contact.phoneNumbers.length) { + for (var i = 0; i < contact.phoneNumbers.length; ++i) { + if ( !! contact.phoneNumbers[i].number && contact.phoneNumbers[i].number === personId) { + return true; + } + } + } + + return false; + }); + return contact; + }; + /** + * This method searches contact in contacts list by phoneNumber. + * @method getContactByPhoneNumber + * @param id {String} search contact phoneNumber + * @return {Object} if contact is found, return contact. Else return first element from contacts list. + */ + Phone.prototype.getContactByPhoneNumber = function(phoneNumber) { + + var self = this; + if ( !! phoneNumber && phoneNumber !== "") { + phoneNumber = formatPhoneNumber(phoneNumber); + var foundContact = ko.utils.arrayFirst(self.contacts(), function(contact) { + if ( !! contact.phoneNumbers && contact.phoneNumbers.length) { + + + for (var i = 0; i < contact.phoneNumbers.length; ++i) { + if ( !! contact.phoneNumbers[i].number && contact.phoneNumbers[i].number === phoneNumber) { + return true; + } + } + } + return false; + }); + return foundContact; + } + return null; + }; + /** + * This method composes display name from contact names. + * @method getDisplayNameStr + * @param contact {Object} contact object + * @return {String} contact name for display + */ + Phone.prototype.getDisplayNameStr = function(contact) { + + var self = this; + if ( !! contact && !! contact.name) { + var name = []; + if ( !! contact.name.lastName && contact.name.lastName !== "") { + name.push(contact.name.lastName); + } + if ( !! contact.name.firstName && contact.name.firstName !== "") { + name.push(contact.name.firstName); + } + if (name.length === 0 && !! contact.name.displayName && contact.name.displayName !== "") { + name.push(contact.name.displayName); + } + return name.join(" "); + } + return "Unknown"; + }; + /** + * This method downloads call history from tizen.phone. Using API method tizen.phone.getCallHistory. + + * @method loadCallHistory + * @param callback {function(error)} Callback function called after method is finished. Parameter `error` will contain any error that was intercepted. + */ + Phone.prototype.loadCallHistory = function(callback) { + + var self = this; + var i; + var callHistoryArrayLength; + if ( !! self.phone) { + self.phone.getCallHistory(25, function(callHistoryArray) { + callHistoryArray = self.formatCallHistory(callHistoryArray); + + self.callHistory(callHistoryArray); + if ( !! callback) { + callback(null); + } + }, function(err) { + console.log("Error(" + err.code + "): " + err.message); + if ( !! callback) { + callback(err); + } + }); + } else { + if ( !! callback) { + callback("Phone API is not available."); + } + } + }; + /** + * This method prepares contact data to be displayed in html. + + * @method formatContacts + * @param contactsList {Array} list of contacts + * @return {Array} list of contacts + */ + Phone.prototype.formatContacts = function(contactsList) { + var i, j; + var contactsListLength; + var phoneNumbersLength; + contactsListLength = contactsList.length; + for (i = 0; i < contactsListLength; i++) { + if (!contactsList[i].photoURI) { + contactsList[i].photoURI = null; + } + if (!contactsList[i].addresses) { + contactsList[i].addresses = null; + } + + if (!contactsList[i].emails) { + contactsList[i].emails = null; + } + if ( !! contactsList[i].phoneNumbers) { + phoneNumbersLength = contactsList[i].phoneNumbers.length; + for (j = 0; j < phoneNumbersLength; j++) { + contactsList[i].phoneNumbers[j].number = formatPhoneNumber(contactsList[i].phoneNumbers[j].number); + } + } + } + return contactsList; + }; + /** + * This method prepares call history data to be displayed in html. + + * @method formatCallHistory + * @param callHistoryList {Array} list of call history + * @return {Array} call history array + */ + Phone.prototype.formatCallHistory = function(callHistoryList) { + + var callHistoryListLength; + var i; + callHistoryListLength = callHistoryList.length; + for (i = 0; i < callHistoryListLength; i++) { + if ( !! callHistoryList[i].startTime) { + var date = callHistoryList[i].startTime; + callHistoryList[i].startTime = moment(date).format("MMM/DD/YYYY HH:mm"); + } else { + callHistoryList[i].startTime = null; + } + } + return callHistoryList; + }; + /** + * This method clears call history. + * @method clearCallHistory + */ + Phone.prototype.clearCallHistory = function() { + + var self = this; + self.callHistory.removeAll(); + self.callHistory([]); + }; + /** + * This method searches call history by contact personID. + * @method getCallHistoryByPersonId + * @param personId {string} search contact personId + * @return {Array} call history array + */ + Phone.prototype.getCallHistoryByPersonId = function(personId) { + + var self = this; + var callHistory = ko.utils.arrayFilter(self.callHistory(), function(callHistoryEntry) { + if ( !! callHistoryEntry.remoteParties) { + for (var i = 0; i < callHistoryEntry.remoteParties.length; ++i) { + if (callHistoryEntry.remoteParties[i].personId === personId) { + return true; + } + } + } + return false; + }); + return callHistory; + }; + window.__phone = undefined === window.__phone ? new Phone() : window.__phone; + return window.__phone; +})(); \ No newline at end of file diff --git a/packaging/html5-ui-phone.changes b/packaging/html5-ui-phone.changes new file mode 100644 index 0000000..60b5cba --- /dev/null +++ b/packaging/html5-ui-phone.changes @@ -0,0 +1,3 @@ +* Thu Apr 03 2014 brianjjones 2e62fa5 +- Initial commit of the Modello Phone app + diff --git a/packaging/html5-ui-phone.spec b/packaging/html5-ui-phone.spec new file mode 100644 index 0000000..81ac969 --- /dev/null +++ b/packaging/html5-ui-phone.spec @@ -0,0 +1,41 @@ +Name: html5_UI_Phone +Summary: A proof of concept pure html5 UI +Version: 0.0.1 +Release: 1 +Group: Applications/System +License: Apache 2.0 +URL: http://www.tizen.org +Source0: %{name}-%{version}.tar.bz2 +BuildRequires: zip +Requires: html5_UI_Common +Requires: wrt-installer +Requires: wrt-plugins-ivi +Requires: wrt-plugins-tizen-bt +Requires: wrt-plugins-ivi-phone +Requires: phoned + +%description +A proof of concept pure html5 UI + +%prep +%setup -q -n %{name}-%{version} + +%build + +make wgtPkg + +%install +rm -rf %{buildroot} +%make_install + +%post + wrt-installer -i /opt/usr/apps/.preinstallWidgets/html5UIPhone.wgt; + cp -r /opt/usr/apps/_common/js/services /opt/usr/apps/html5POC09/res/wgt/js/ + cp -r /opt/usr/apps/_common/css/* /opt/usr/apps/html5POC09/res/wgt/css/ + +%postun + wrt-installer -un html5POC09.Phone + +%files +%defattr(-,root,root,-) +/opt/usr/apps/.preinstallWidgets/html5UIPhone.wgt diff --git a/templates/contactCarouselDelegate.html b/templates/contactCarouselDelegate.html new file mode 100644 index 0000000..54a4efd --- /dev/null +++ b/templates/contactCarouselDelegate.html @@ -0,0 +1,24 @@ +
  • +
    +
    + +
    +
    + +
    + {{if name}} {{if name.firstName}} {{:name.firstName}} {{/if}} {{if + name.lastName}} {{:name.lastName}} {{/if}} {{/if}}
    +
    + {{if phoneNumbers}} {{if phoneNumbers[0]}} + {{:phoneNumbers[0].number}} {{/if}} {{/if}}
    +
    +
    +
    +
    {{:startTime.toString().toUpperCase()}}
    +
    {{:direction.toString().toUpperCase()}}
    +
    +
    + +
    +
  • diff --git a/templates/libraryContactDetailDelegate.html b/templates/libraryContactDetailDelegate.html new file mode 100644 index 0000000..4d46c86 --- /dev/null +++ b/templates/libraryContactDetailDelegate.html @@ -0,0 +1,47 @@ + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    MOBILE
    +
    {{:phoneNumber}}
    +
    + +
    +
    + +
    +
    EMAIL
    +
    {{:email}}
    +
    +
    +
    +
    +
    +
    ADDRESS
    +
    {{:address.toUpperCase()}}
    +
    +
    +
    + +
    + {{foreach history}} +
    +
    +
    +
    {{:startTime.toString().toUpperCase()}}
    +
    {{:direction.toString().toUpperCase()}}
    +
    +
    + {{/foreach}} +
    \ No newline at end of file diff --git a/templates/template-contacts.html b/templates/template-contacts.html new file mode 100644 index 0000000..b14892a --- /dev/null +++ b/templates/template-contacts.html @@ -0,0 +1,10 @@ +
    +
    +
    + +
    +
    +
    -- 2.7.4