core-apps: Add com.palm.app.messaging from HP webOS SDK

Since it's not available anywhere anymore, adding it here for now for testing IM/Messaging in LuneOS

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
This commit is contained in:
Herman van Hazendonk committed 2020-06-12 16:15:30 +02:00
1 parent f92bf01d21
commit 5eb8001716
137 files changed
+15504

No files matched your search

+1
View File
@@ -0,0 +1 @@
rvm use ruby-1.9.2-p180@palm
+5
View File
@@ -0,0 +1,5 @@
source "http://rubygems.org"
gem "eris"
gem "jasmine"
gem "rake"
+52
View File
@@ -0,0 +1,52 @@
GEM
remote: http://rubygems.org/
specs:
childprocess (0.2.0)
ffi (~> 1.0.6)
curb (0.7.15)
diff-lcs (1.1.2)
eris (0.0.3)
curb
erubis
json
rack
sinatra
thor
erubis (2.7.0)
ffi (1.0.9)
jasmine (1.0.2.1)
json_pure (>= 1.4.3)
rack (>= 1.1)
rspec (>= 1.3.1)
selenium-webdriver (>= 0.1.3)
json (1.5.3)
json_pure (1.5.3)
rack (1.3.2)
rake (0.9.2)
rspec (2.6.0)
rspec-core (~> 2.6.0)
rspec-expectations (~> 2.6.0)
rspec-mocks (~> 2.6.0)
rspec-core (2.6.4)
rspec-expectations (2.6.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.6.0)
rubyzip (0.9.4)
selenium-webdriver (2.2.0)
childprocess (>= 0.1.9)
ffi (>= 1.0.7)
json_pure
rubyzip
sinatra (1.2.6)
rack (~> 1.1)
tilt (< 2.0, >= 1.2.2)
thor (0.14.6)
tilt (1.3.2)
PLATFORMS
ruby
DEPENDENCIES
eris
jasmine
rake
+32
View File
@@ -0,0 +1,32 @@
require 'jasmine'
namespace :jasmine do
task :server do
require 'jasmine'
require 'eris/lib/jasmine_config_overrides'
puts "your tests are here:"
puts " http://localhost:8888/"
Jasmine::Config.new.start_server
end
desc "Run continuous integration tests"
task :ci do
require 'jasmine'
require "rspec"
require "rspec/core/rake_task"
require 'eris'
ENV['JASMINE_BROWSER'] = 'chrome'
RSpec::Core::RakeTask.new(:jasmine_continuous_integration_runner) do |t|
t.rspec_opts = ["--color", "--format", "progress"]
t.verbose = true
t.pattern = ["#{Eris::FILE_PATH}/eris/lib/jasmine_rspec_runner.rb"]
end
Rake::Task["jasmine_continuous_integration_runner"].invoke
end
end
desc "Run specs with local config"
task :jasmine => ['jasmine:server']
@@ -0,0 +1,288 @@
enyo.kind({
name: "AddBuddyDialog",
kind: "ModalDialog",
caption: $L("Add Buddy"),
style: "height: 402px",
contentHeight: "402px",
events: {
onclosed: ""
},
components: [
{kind: "ApplicationEvents", onWindowHidden: "close"},
{name: "addBuddyPane", kind: "Pane", components: [
{name: "defaultView", layoutKind: "enyo.VFlexLayout", components:[
{kind: "RowGroup", caption: $L("IM Service"), style: "margin:5px;", components: [
{name: "service", kind: "AccountSelector", onChange: "serviceChanged"}
]},
{name: "skypeBuddyNameRow", kind: "RowGroup", style: "height:25px;margin:5px;", caption: $L("Buddy Name"), components: [
{name: "skypeName", kind: "CustomButton", caption: $L("Search for a contact"), onclick: "startSkypeSearch"}
]},
{name: "buddyNameRow", kind: "RowGroup", style: "height:25px;margin:5px;", caption: $L("Buddy Name"), components: [
{name: "name", kind: "Input", hint: $L("Enter a buddy name"), oninput: "updateAddButton", onchange: "focusMessage", changeOnKeypress: true, autoCapitalize: "lowercase", autocorrect: false, inputType: "email"}
]},
/*
{kind: "RowGroup", caption: $L("Add To Buddy Group"), components: [
{name: "group", kind: "ListSelector", items: [
{caption: "Buddies", value:"buddies"},
{caption: "Family", value:"family"},
{caption: "Friends", value: "friends"}
]}
]},
*/
{kind: "RowGroup", caption: $L("Invitation Message"), style: "margin:5px;", components: [
{name: "message", kind: "Input", onkeypress: "messageKeyPressed", changeOnKeypress: true, hint: $L("Enter a message")}
]},
{layoutKind: "enyo.HFlexLayout", style: "margin:5px;", components: [
{name: "cancelButton", kind: "Button", caption: $L("Cancel"), flex:1, onclick: "cancelClicked"},
{name: "addButton", kind: "Button", caption: $L("Add Buddy"), flex:1, className:"enyo-button-dark", onclick: "addBuddyClicked"}
]}
]},
{name: "skypeSearchUI", kind: "CrossAppUI", lazy: true, layoutKind: "enyo.VFlexLayout", onResult: "handleSkypeSearchResult"}
]},
{kind: "DbService", onFailure: "fail", components: [
{name: "buddyAdder", dbKind: "com.palm.imcommand:1", method: "put", onSuccess: "addedBuddy"}
]}
],
renderOpen: function() {
this.inherited(arguments);
this.registerAccountService();
this.updateAccountSelector();
if (enyo.g11n.currentLocale().language == "de") {
this.$.addButton.flex = 1.75;
}
},
accountsChanged: function() {
this.accounts = this.accountService.getImAccounts();
this.updateAccountSelector();
this.updateInputs();
this.updateAddButton();
},
updateAccountSelector: function() {
var items = [];
//enyo.log("List of IM accounts: ", this.accounts);
for (var i = 0; i < this.accounts.length; i++) {
items.push({
caption: this.accounts[i].loc_shortName,
value: this.accounts[i]._id,
username: this.accounts[i].username
});
}
this.$.service.setItems(items);
this.updateNameHintText(this.$.service.getValue());
},
updateInputs: function() {
var disabled = this.accounts.length === 0;
this.$.name.setDisabled(disabled);
this.$.message.setDisabled(disabled);
},
updateAddButton: function() {
var nameValue = this.$.name.getValue();
nameValue = nameValue.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); // trim name value
if (nameValue.length === 0 || this.accounts.length === 0) {
this.disableAddButton();
} else {
this.enableAddButton();
}
},
serviceChanged: function(inSender, inValue, inOldValue) {
this.updateNameHintText(inValue);
},
updateNameHintText: function(accountId) {
var account = this.getAccount(accountId);
if (account && account.serviceName === "type_skype") {
this.$.buddyNameRow.hide();
this.$.skypeBuddyNameRow.show();
} else {
this.$.skypeBuddyNameRow.hide();
this.$.buddyNameRow.show();
}
if (account && account.serviceName === "type_gtalk") {
// gtalk requires entire email address for adding a buddy, so change the hint text to reflect that
this.$.name.setHint($L("Email address..."));
} else {
this.$.name.setHint($L("Enter a buddy name"));
}
},
focusMessage: function(inSender, inEvent) {
this.$.message.forceFocus();
},
disableAddButton: function() {
this.$.addButton.setDisabled(true);
},
enableAddButton: function() {
this.$.addButton.setDisabled(false);
},
messageEntered: function() {
this.$.message.forceBlur();
this.addBuddy();
this.closeDialog();
},
messageKeyPressed: function(inSender, inEvent) {
if (inEvent.keyCode === 13 && (this.$.name.getValue() && this.$.name.getValue().replace(/\s*$/, "").length > 0)) {
this.messageEntered();
}
},
addBuddyClicked: function(inSender, inResponse) {
this.addBuddy();
this.closeDialog();
},
getData: function() {
this.data = {};
this.data.accountId = this.$.service.getValue();
this.data.buddyName = this.$.name.getValue();
this.data.message = this.$.message.getValue();
},
addBuddy: function() {
this.getData();
if (!this.data.buddyName || this.data.buddyName.replace(/\s*$/, "").length === 0) {
enyo.warn("@@@@@@@@@@@@@@ Cannot add a buddy if no name is specified");
return;
}
var account = this.getAccount(this.data.accountId);
if (!account) {
enyo.warn("@@@@@@@@@@@@@@ Cannot find account to add buddy");
return;
}
enyo.log("Adding a buddy to account ", this.data.accountId, " using service ", account.serviceName);
var imcommand = enyo.application.accountService.getImCommandKind(account.serviceName);
this.$.buddyAdder.call({
objects:[{
_kind: imcommand,
command: "sendBuddyInvite",
params: {
message: this.data.message
},
handler: "transport",
serviceName: account.serviceName,
fromUsername: account.username,
targetUsername: this.data.buddyName
}]
});
},
getAccount: function(id) {
var account;
for (var i = 0; i < this.accounts.length; i++) {
if (this.accounts[i]._id === id) {
account = this.accounts[i];
break;
}
}
return account;
},
cancelClicked: function(inSender, inResponse) {
this.closeDialog();
},
closeDialog: function() {
this.$.name.setValue("");
this.$.message.setValue("");
this.$.skypeName.setCaption($L("Search for a contact"));
this.disableAddButton();
this.unregisterAccountService();
this.close();
},
registerAccountService: function() {
this.accountService = enyo.application.accountService;
if (this.accountService) {
this.accountService.register(this, this.accountsChanged.bind(this));
}
if (this.$.service.items.length > 0) {
// reset selection position
this.$.service.setValue(this.$.service.items[0].value);
}
},
unregisterAccountService: function() {
if (this.accountService) {
this.accountService.unregister(this);
this.accountService = undefined;
}
},
startSkypeSearch: function() {
var account = this.getAccount(this.$.service.getValue());
if(account && account.serviceName === "type_skype") {
// create skype search UI view
if (!this.$.skypeSearchUI) {
this.$.addBuddyPane.createView("skypeSearchUI");
}
this.$.skypeSearchUI.setApp("com.palm.app.skype");
this.$.skypeSearchUI.setPath("directorySearch.html");
this.$.skypeSearchUI.setParams({});
this.$.addBuddyPane.selectViewByName("skypeSearchUI");
this.setCaption($L("Search for a contact"));
this.$.addBuddyPane.addClass("skype-directory-search");
}
},
handleSkypeSearchResult: function(inSender, inResult) {
if(inResult.value !== undefined && inResult.value !== "") {
this.$.name.setValue(inResult.value);
this.$.skypeName.setCaption(inResult.value);
this.enableAddButton();
}
this.$.addBuddyPane.removeClass("skype-directory-search");
this.setCaption($L("Add Buddy"));
this.$.addBuddyPane.selectViewByName("defaultView");
}
});
enyo.kind({
name: "AccountSelector",
kind: enyo.CustomListSelector,
itemKind: "AccountItem",
setItemProps: function(inItem) {
this.item.setCaption(inItem.caption);
this.item.setUsername(inItem.username);
this.item.setValue(inItem._id);
}
});
enyo.kind({
name: "AccountItem",
kind: enyo.MenuCheckItem,
published: {
caption: "",
username: ""
},
chrome: [
{name: "item", kind: enyo.Item, tapHighlight: true, align: "center",
className: "enyo-menuitem", layoutKind: "HFlexLayout", onclick: "itemClick",
components: [
{name: "icon", kind: "Image", className: "enyo-menuitem-icon"},
{name: "account", layoutKind: "VFlexLayout", components: [
{name: "caption"},
{name: "user", className: "account-username-text"}
]},
{name: "arrow", kind: enyo.CustomButton, toggling: true, className: "enyo-menucheckitem-arrow"}
]}
],
create: function() {
this.inherited(arguments);
this.captionChanged();
this.usernameChanged();
},
captionChanged: function() {
this.$.caption.setContent(this.caption);
},
usernameChanged: function() {
this.$.user.setContent(this.username);
}
});
+25
View File
@@ -0,0 +1,25 @@
enyo.kind({
name: "AppInit",
kind: "enyo.Component",
init: function() {
// set up first launch handler
enyo.application.firstLaunchHandler = new FirstLaunchHandler();
// set up preferences handler
enyo.application.prefsHandler = new PrefsHandler();
// set up account list
enyo.application.accountService = new accountService();
enyo.application.accountService.getAccounts();
// set up notification managers
enyo.application.messageDashboardManager = new MessageDashboardManager();
enyo.application.inviteDashboardManager = new InviteDashboardManager();
enyo.application.class0AlertManager = new Class0AlertManager();
// set up watchers
//enyo.application.buddyWatcher = new BuddyWatcher();
enyo.application.inviteWatcher = new InviteWatcher();
enyo.application.telephonyWatcher = new TelephonyService();
}
});
+11
View File
@@ -0,0 +1,11 @@
enyo.kind({
name: "ChatButton",
kind: enyo.CustomButton,
className: "enyo-button-icon chat-button",
components: [
{name: "caption", className: "chat-button-text"}
],
captionChanged: function() {
this.$.caption.setContent(this.caption);
}
});
+238
View File
@@ -0,0 +1,238 @@
enyo.kind({
name: "ChatView",
kind: "Pane",
className: "conversationList",
transitionKind: "enyo.transitions.Simple",
events: {
onSelectThread: "",
onClearUnreadCount: ""
},
published: {
chatThread: "",
params:"",
loginStates:[]//,
// deletedChatThread: ""
},
components: [
{kind: "ApplicationEvents", onWindowHidden:"windowHiddenHandler"},
{name: "defaultView", kind: DefaultView},
{name: "dbGetById", kind: "DbService", dbKind: "com.palm.db:1", method: "get", onSuccess: "gotRecordById"},
{name: "chatThreadServiceFind", kind: "DbService", dbKind: enyo.messaging.thread.dbKind, method: "find", onSuccess: "gotConversationByPersonId", onFailure: "conversationFailure"},
{name: "chatThreadServiceCreate", kind: "DbService", dbKind: enyo.messaging.thread.dbKind, method: "put", onSuccess: "threadCreated", onFailure: "conversationFailure"},
{name: "errorDialog", kind: "PopupDialog", onAccept: "retryMessage"},
{name: "composeView", kind: "ComposeView", onOpenConversation: "openConversationById"},
{name: "conversationList", kind: "ConversationList", onCloseConversationList:"closeConversationList",onSelectThread: "selectThread", onClearUnreadCount: "doClearUnreadCount", onOpenComposeView:"openComposeView"}
],
create: function() {
this.inherited(arguments);
},
chatThreadChanged: function(inOldChatThread) {
if(this.chatThread){
// Making 'selectViewByName' to be synchronous by passing 'true' as the second
// parameter to fix bug DFISH-26620.
this.selectViewByName("conversationList", true);
}
this.$.conversationList.setChatThread(this.chatThread);
},
selectThread: function(inSender, inThread) {
this.doSelectThread(inThread);
},
setDeletedChatThread: function(inThread){
this.$.conversationList.setDeletedChatThread(inThread);
},
threadLocked: function(inThread){
this.$.conversationList.threadLocked(inThread);
},
loginStatesChanged: function(oldLoginStates){
this.$.conversationList.setLoginStates(this.loginStates);
this.$.composeView.setLoginStates(this.loginStates);
},
resize: function() {
if (this.getViewName() === "conversationList") {
// reset adjusts the scroller and tries to maintain the scroll position
this.$.conversationList.resize();
}
},
closeConversationList: function() {
if (this.getViewName() === "conversationList") {
this.selectViewByName("defaultView");
}
},
//launchParams.compose or launchParams from MessagingApp.js
preHandleLaunch: function(params) {
if (this.$.conversationList) {
// Messaging can be launched from the detailsDialog popup. So close the popup
// and allow the user to compose and send their message.
this.$.conversationList.closeDetailsDialog();
}
if (params.ims && params.ims.length > 0) {
var im = params.ims[0];
var imIsOk = this.validateAccountAndDisplayWarning(im);
if (imIsOk){
this.handleLaunch(params);
}
}
else if(params.phoneNumbers && params.phoneNumbers.length > 0){
var phone = params.phoneNumbers[0];
var textIsOk = this.validateAccountAndDisplayWarning(phone);
if (textIsOk){
this.handleLaunch(params);
}
}
else {
this.handleLaunch(params);
}
},
handleLaunch: function(params) {
if (params && params.personId) {
enyo.log("ChatView::handleLaunch::should be conversation view");
this.composeParameter = params;
var whereClause = [{ prop: "personId", op: "=", val: params.personId }];
this.$.chatThreadServiceFind.cancel();
this.$.chatThreadServiceFind.call({
query: {where: whereClause}
});//gotConversationByPersonId
}
else {
if (this.getViewName() === "conversationList" && this.$.conversationList) {
this.$.conversationList.closeConversation();
}
this.doSelectThread(undefined);
this.openComposeView(this, params);
}
},
openComposeView: function(inSender, params){
if (params) {
this.$.composeView.setParams(params);
}
this.selectViewByName("composeView");
},
gotConversationByPersonId: function(inSender, inResponse){
//todo: found more than one converstaion by personId, both are not locked, bug in chatthread?
if (inResponse.returnValue && inResponse.results && inResponse.results.length > 0) {
//show found thread
if (!this.chatThread || (this.chatThread && this.chatThread._id !== inResponse.results[0]._id)) {
this.chatThread = inResponse.results[0];
this.doSelectThread(this.chatThread);
}
if (this.composeParameter) {
var overrideReplyAddress = {};
if (this.composeParameter.ims && this.composeParameter.ims.length > 0) {
overrideReplyAddress = this.composeParameter.ims[0];
}
else
if (this.composeParameter.phoneNumbers && this.composeParameter.phoneNumbers.length > 0) {
overrideReplyAddress = this.composeParameter.phoneNumbers[0];
}
var addr = overrideReplyAddress.value || overrideReplyAddress.addr;
var serviceName = overrideReplyAddress.serviceName || overrideReplyAddress.type;
enyo.log("ChatView::gotConversationByPersonId::Overriding reply address serviceName:", serviceName, " addr:", addr);
this.chatThread.replyAddress = addr;
this.chatThread.normalizedAddress = enyo.messaging.utils.normalizeAddress(addr, serviceName);
this.chatThread.replyService = serviceName;
}
this.composeParameter = undefined;
this.chatThreadChanged();
}
//case that start chat from contact's im, which has personId, but no chatthread with this person
else if (this.composeParameter.personId) {
this.$.dbGetById.call({"ids":[this.composeParameter.personId]});//gotRecordById()
}
},
//thread is created based on composeParameter.personId
threadCreated : function(inSender, inResponse){
if (inResponse.returnValue && inResponse.results && inResponse.results.length > 0) {
this.$.dbGetById.call({"ids":[inResponse.results[0].id]});//gotRecordById()
this.composeParameter = undefined;
}
},
gotRecordById: function(inSender, inResponse){
if (inResponse.returnValue && inResponse.results && inResponse.results.length > 0) {
if (inResponse.results[0]._kind === enyo.messaging.thread.dbKind) {
//case that compose view create a chatthread, now need open that chatthread, also thread is created in this file for case that composeParamter has personId
if (!this.chatThread || (this.chatThread && this.chatThread._id !== inResponse.results[0]._id)) {
this.chatThread = inResponse.results[0];
this.chatThreadChanged();
this.doSelectThread(this.chatThread);
}
}
else if (inResponse.results[0]._kind === "com.palm.person:1") {
//creat chatthread based on person info, which only displayName which generated from person will be used
var person = inResponse.results[0];
var address;
if (this.composeParameter.phoneNumbers) {
address = this.composeParameter.phoneNumbers[0];
}
else if (this.composeParameter.ims){
address = this.composeParameter.ims[0];
}
var replyAddress = address.value;
var serviceName = address.type;
var displayName = enyo.messaging.person.getDisplayName(person);
this.$.chatThreadServiceCreate.call({
objects: [{
"_kind": enyo.messaging.thread.dbKind,
"timestamp": new Date().getTime(),
"personId": this.composeParameter.personId,
"displayName": enyo.messaging.person.isNotBlank(displayName) ? displayName : undefined,
"replyAddress": replyAddress,
"normalizedAddress": enyo.messaging.utils.normalizeAddress(replyAddress, serviceName),
"replyService": serviceName,
"summary": "",
"flags": {
"visible": false
}
}]
});//threadCreated();
}
}
else {//edge case for non-valid person id, launch compose view instead
enyo.warn("ChatView::launch parameter has a nonvalid personid");
this.composeParameter.personId = undefined;
this.$.composeView.setParams(this.composeParameter);
this.composeParameter = undefined;
this.selectViewByName("composeView");
}
},
validateAccountAndDisplayWarning: function(addressObj) {
var serviceName = addressObj.serviceName || addressObj.type;
var isSupported = enyo.application.accountService.getAccount(serviceName);
if (!isSupported) {
var errorMessage;
if (enyo.messaging.utils.isTextMessage(serviceName)) {
errorMessage = $L("SMS capability is required.");
}
else {
errorMessage = $L("You have not set up an IM account of this type.");
}
this.$.errorDialog.openAtCenter();
this.$.errorDialog.setTitle($L("Unable to launch chat"));
this.$.errorDialog.setMessage(errorMessage);
this.$.errorDialog.setCancelButtonCaption($L("OK"));
this.$.errorDialog.hideAcceptButton();
}
return isSupported;
},
rendered: function() {
this.inherited(arguments);
},
openConversationById: function(inSender, inThreadId){
if(inThreadId)
{ this.$.dbGetById.call({
"ids": [inThreadId]
});//gotRecordById()
}
},
windowHiddenHandler: function(){
this.selectViewByName("defaultView");
},
conversationFailure: function(inSender, inResponse){
enyo.error("ConverstaionList::conversationFailure::inResponse:", inResponse);
}
});
+21
View File
@@ -0,0 +1,21 @@
enyo.kind({
name: "DefaultView",
kind: enyo.VFlexBox,
className: "composeview",
components: [
{kind: "VFlexBox", flex: 1, components: [
{kind: "Toolbar", className:"enyo-toolbar-light conversation-header", layoutKind: "HFlexLayout"},
{className:"header-shadow"},
{kind: "VFlexBox", className: "body-placeholder", flex: 1, pack: "center", align: "center", components: [
{kind:"Spacer", flex:1},
{kind: "Image", src: "images/icon-165x165.png"},
{kind:"Spacer"}
]}
]},
{className:"footer-shadow footer-app-shadow"},
{kind: "Toolbar", className:"enyo-toolbar-light conversation-bottom", layoutKind: "HFlexLayout", components: [
{name: "slidingDrag", slidingHandler: true, kind: "GrabButton" }/*,
{name: "richText", kind: "RichText", hint: $L("Enter message here..."), flex: 1, disabled: true}*/
]}
]
});
+191
View File
@@ -0,0 +1,191 @@
/*globals enyo */
enyo.kind({
name: "Messaging",
kind: enyo.VFlexBox,
events: {
onSelectThread: "",
onDeleteThread: "",
onThreadLocked: "",
onOpenComposeView: "",
onNewBuddy: "",
onNewFavorite: "",
onLoginStatesChange: "",
onAddAccount: ""
},
components: [
{kind:"Toolbar", className:"enyo-toolbar-light messaging-availability", components: [
{name: "imStatus", kind: "ImStatus", onLoginStatesChange: "loginStatesChange", onAddAccount: "doAddAccount"}
]},
{kind: "TabGroup", onChange: "menuToggle", className:"messaging-radiobuttons", components: [
{value: 0, components: [{name: "unreadCountButton", kind: "ChatButton"}]},
{value: 1, icon: "images/menu-icon-buddies.png"},
{value: 2, icon: "images/menu-icon-favorites.png"}
]},
{kind: "Pane", flex: 1, transitionKind: "enyo.transitions.Simple", components: [
{kind: "ThreadList", onSelectThread: "doSelectThread", onDeleteThread: "doDeleteThread", onThreadLocked: "doThreadLocked", onUnreadCountChanged: "updateUnreadCount"},
{kind: "BuddyList", onSelectBuddy: "personSelected", onDeleteThread: "doDeleteThread"},
{kind: "FavoriteList", onSelectFavorite: "personSelected"}
]},
{className:"footer-shadow footer-app-shadow"},
{kind: "Toolbar", className:"enyo-toolbar-light", components: [
{name: "conversationBtn", kind: "Button", label:$L("New Conversation"), onclick: "doOpenComposeView"},
{name: "buddyBtn", kind: "Button", label:$L("Add Buddy"), onclick: "doNewBuddy", showing: false},
{name: "favoriteBtn", kind: "Button", label:$L("Add Favorite"), onclick: "doNewFavorite", showing: false}
]},
{kind: "DbService", dbKind: "com.palm.chatthread:1", components: [
{name: "findThread", method: "find", onSuccess: "threadFound"},
{name: "createThread", method: "put", onSuccess: "threadCreated"},
{name: "threadGetter", method: "get", onSuccess: "threadFound"}
]}
],
create: function() {
this.inherited(arguments);
this.buttons = [];
this.buttons.push(this.$.conversationBtn);
this.buttons.push(this.$.buddyBtn);
this.buttons.push(this.$.favoriteBtn);
},
loginStatesChange: function(inSender, inStates){
this.doLoginStatesChange(inStates);
this.$.buddyList.setLoginStates(inStates);
},
workaroundListVisibilityBug: function(inValue) {
// this object loses scrollTop when it goes invisible, this hack restores scrollTop
if (inValue === undefined || inValue === 0) {
this.$.threadList.updateList();
}
// this one has never rendered at startup (it was invisible), we need more power for this one
if (inValue === undefined || inValue === 1) {
this.$.buddyList.updateList();
}
//this.$.aggregator.findThreads();
if (inValue === undefined || inValue === 2) {
this.$.favoriteList.updateList();
}
},
menuToggle: function(inSender, inValue) {
this.$.pane.selectViewByIndex(inValue, true);
this.updateButtons(inValue);
// this.updateDashboard(inValue);
// FIXME: should no longer be needed.
this.workaroundListVisibilityBug(inValue);
},
highlightTabButton: function(inValue) {
this.$.tabGroup.setValue(inValue);
},
updateButtons: function(index) {
for (var i = 0; i < this.buttons.length; i++) {
this.buttons[i].setShowing(i === index);
}
},
updateDashboard: function(pane) {
var dashboardMgr = enyo.application.messageDashboardManager;
var filter = dashboardMgr.getFilter();
if (pane === 0) {
// this is the conversation view
if (filter) {
filter = {};
}
filter.filterAll = true;
} else {
filter.filterAll = false;
}
dashboardMgr.setFilter(filter);
},
personSelected: function(inSender, inPerson) {
this.person = inPerson;
this.findThread();
},
findThread: function() {
this.$.findThread.cancel();
if (this.person.personId) {
this.$.findThread.call({
query: {
where: [{
prop: "personId",
op: "=",
val: this.person.personId
}]
}
//parseQuery("where personId='" + this.person.personId + "'")
});
} else {
// if person Id is not found, use username and service name to search
// for a chat thread
this.$.findThread.call({
query: {
where: [{
prop: "normalizedAddress",
op: "=",
val: enyo.messaging.utils.normalizeAddress(this.person.username, this.person.serviceName)
}]
}
});
}
},
threadFound: function(inSender, inResponse) {
var threads = inResponse.results;
if (threads.length > 0) {
enyo.log("got chat thread record: ", threads[0]._id);
this.doSelectThread(threads[0]);
} else {
this.createThread();
}
},
createThread: function() {
var p = this.person; // there can be a race condition here if user tap on different buddies or favorites quick. we may want to use an array to handle pending person selection.
this.$.createThread.call({
objects: [{
"_kind": "com.palm.chatthread:1",
"timestamp": new Date().getTime(),
"personId": p.personId,
"displayName": p.displayName || p.username,
"replyAddress": p.username,
"normalizedAddress": p.username,
"replyService": p.serviceName,
"summary": "",
"flags": {"visible":false}
}]
});
},
threadCreated: function(inSender, inResponse) {
//enyo.log("*********************************** created thread: ", inResponse);
if (inResponse.returnValue && inResponse.results && inResponse.results.length > 0) {
this.$.threadGetter.call({"ids": [inResponse.results[0].id]});
}
},
setOfflinePrefs: function(prefs) {
this.$.buddyList.setShowOffline(!prefs.showOnlineBuddiesOnly);
},
updateUnreadCount: function(inSender, inCount) {
//enyo.log("*********** unread count: ", inCount);
this.$.unreadCountButton.setClassName("enyo-button-icon chat-button"+ (inCount > 99?"-plus":""));
this.$.unreadCountButton.setCaption(inCount > 99?"99":(inCount || ""));
},
setSelection: function(chatThread){
this.$.threadList.setSelection(chatThread);
this.$.buddyList.setSelection(chatThread);
this.$.favoriteList.setSelection(chatThread);
},
windowHiddenHandler: function(){
// show chat list
this.menuToggle(this, 0);
// highlights the radio button for conversations view
this.$.tabGroup.setValue(0);
this.$.threadList.windowHiddenHandler();
this.$.buddyList.windowHiddenHandler();
this.$.favoriteList.windowHiddenHandler();
this.$.imStatus.windowHiddenHandler();
},
windowShownHandler:function() {
// reset all lists so that their db service restarted
this.$.threadList.resetList();
this.$.buddyList.resetList();
this.$.favoriteList.resetList();
}
});
+482
View File
@@ -0,0 +1,482 @@
/*globals enyo */
enyo.kind({
name: "enyo.MessagingApp",
kind: "Pane",
transitionKind: "enyo.transitions.Simple",
className: "enyo-bg",
components: [
{kind: "ApplicationEvents", onOpenAppMenu: "openAppMenuHandler", onBack:"backHandler", onWindowParamsChange:"windowParamsChangeHandler",
onResize:"resizeHandler", onUnload:"unloadHandler", onWindowHidden:"windowHiddenHandler", onWindowShown:"windowShownHandler",
onWindowActivated:"windowActivatedHandler", onWindowDeactivated:"windowDeactivatedHandler"},
{name: "slidingGroup", kind: enyo.SlidingPane, className: "loading-background", components: [
{width: "320px", name: "messagingSliding", fixedWidth: true, components: [{
flex: 1,
kind: "Messaging",
onSelectThread: "selectThread",
onDeleteThread: "deleteThread",
onThreadLocked: "threadLocked",
onOpenComposeView: "openComposeView",
onNewBuddy: "addBuddy",
onNewFavorite: "showAddFavoriteList",
onLoginStatesChange: "LoginStatesChange",
onAddAccount: "showAddAccountDialog"
}]},
{name: "conversationSliding", flex: 1, className:"conversationSliding-right", components: [{
kind: "ChatView",
onSelectThread: "selectThread",
onClearUnreadCount: "clearUnreadCount",
flex: 1
}]}
]},
{name: "firstLaunch", kind: "FirstLaunch", onFirstLaunchDone: "doneFirstLaunch", lazy: true},
{name: "preferencesView", kind: "PreferencesView", onClosePreferences: "closePreferences", lazy: true},
{name: "threadServiceMerge", kind: "DbService", dbKind: "com.palm.chatthread:1", method: "merge"/*, onSuccess: "setUnreadcount"*/},
{name: "threadFinder", kind: "DbService", dbKind: "com.palm.chatthread:1", method: "get", onSuccess: "gotThread"},
{name: "readMessageMerger", kind: "DbService", dbKind: "com.palm.message:1", method: "merge"},
{name: "addFavoriteList", kind: "com.palm.library.contactsui.personListDialog", onBeforeOpen: "onBeforeOpenFavoriteList", onContactClick: "favoriteSelected", onCancelClick: "closeAddFavoriteList", mode: "noFavoritesOnly", showFavStars: false, showIMStatuses: false},
{name: "buddyDialog", kind: "AddBuddyDialog"},
{name: "noAccountDialog", kind: "ModalDialog", autoClose: true, style: "width: 20em; height: 8em;", contentHeight: "8em", components: [
{content: $L("Add a messaging account to set your online status."), style:"padding-bottom:22px;text-align:center;"},
{layoutKind: "HFlexLayout", components: [
{kind: "Button", flex:1, caption: $L("OK"), onclick: "closeDialog"},
{kind: "Button", flex:1, caption: $L("Add an Account"), onclick: "addAccountClicked"}
]}
]},
{name: "appMenu", kind: "AppMenu", automatic: false, onOpen: "menuOnOpen", components: [
{name: "addBuddyMenuItem", caption: $L("Add Buddy"), onclick: "addBuddy"},
{name: "showOfflineMenuItem", caption: $L("Show Offline Buddies"), onclick: "updateOfflinePrefs", showOffline: true},
{name: "prefsAccntsMenuItem", caption: $L("Preferences & Accounts"), onclick: "openPreferences"},
{name: "helpMenuItem", kind: enyo.HelpMenu, target: "http://help.palm.com/messaging/index.html"}
]},
{name: "firstUseAppMenu", kind: "AppMenu", automatic: false, components: []},
{name: "buddyInviteDelete", kind: "DbService", dbKind: "com.palm.iminvitation:1", method: "del"},
{name: "buddyInviteFind", kind: "DbService", dbKind: "com.palm.iminvitation:1", method: "find", onSuccess: "invitesToDelete"}
],
create: function() {
this.inherited(arguments);
// Mark main card as cachable off-screen.
// This means it will usually not be closed when the user throws it away.
if (window.PalmSystem) {
PalmSystem.keepAlive(true);
}
this.viewInitialized = false;
this.registerServices();
this.resize();
if (window.PalmSystem && enyo.application.firstLaunchHandler.shouldShowFirstLaunch()) {
this.handleFirstLaunch();
}
// register for account changes
enyo.application.accountService.register(this, this.accountsChanged.bind(this));
},
menuOnOpen: function(){
enyo.messaging.keyboard.setKeyboardAutoMode();
},
accountsChanged: function() {
var removedAccount = this.getRemovedAccount(enyo.application.accountService.getImAccounts());
if (removedAccount) {
// Inform the DashboardManagers that the account has been removed so any
// messages related to this account can be removed from the dashboard.
enyo.application.messageDashboardManager.accountRemoved(removedAccount);
enyo.application.inviteDashboardManager.accountRemoved(removedAccount);
// Query for all the invite messages associated with this account so they
// can be deleted.
this.$.buddyInviteFind.cancel();
this.$.buddyInviteFind.call({
query: {
where: [{ prop: "accountId",
op: "=",
val: removedAccount.accountId
}],
select: ["_id"]
}
});
}
},
invitesToDelete: function(inSender, inResponse) {
if (inResponse && inResponse.results) {
for (var i = 0; i < inResponse.results.length; i++) {
this.$.buddyInviteDelete.call({
ids: [inResponse.results[i]._id]
});
}
}
},
LoginStatesChange: function(inSender, inStates){
if (this.$.chatView) {
this.$.chatView.setLoginStates(inStates);
}
},
prefsUpdated: function(inPrefs) {
this.prefs = inPrefs;
if (!this.prefs) {
// app is still initializing
return;
}
this.setOffline();
},
updatePrefs: function() {
this.prefsHandler.setPrefs(this.prefs);
},
selectThread: function(inSender, inThread){
//inThread could be null
this.checkWideLayout();
enyo.application.selectedThread = inThread;
if (inSender === "Messaging") {
this.$.chatView.setChatThread(inThread);
}
else if (inSender === "ChatView") {
//update highlight for threadlist, buddylist and favorite list
this.$.messaging.setSelection(inThread);
}
else {
this.$.chatView.setChatThread(inThread);
this.$.messaging.setSelection(inThread);
}
},
deleteThread: function(inSender, inThread) {
this.$.chatView.setDeletedChatThread(inThread);
},
threadLocked:function(inSender, inThread) {
if (this.$.chatView.getViewName() === "chatView") {
this.$.chatView.threadLocked(inThread);
}
},
openComposeView: function(inSender) {
enyo.messaging.keyboard.setKeyboardAutoMode();
this.checkWideLayout();
if (enyo.application.selectedThread) {
this.selectThread(undefined, undefined);
}
if (this.$.chatView.getViewName() !== "composeView") {
this.$.chatView.selectViewByName("composeView");
}
},
checkWideLayout: function() {
/*
if (!this.$.slidingGroup.getWideLayout()) {
this.$.slidingGroup.setSelected(this.$.conversationSliding);
}
*/
},
clearUnreadCount: function(inSender, inThreadId){
this.clearUnreadCountForThread(inThreadId);
this.setThreadMessagesRead(inThreadId);
},
clearUnreadCountForThread: function(inThreadId) {
this.$.threadServiceMerge.call({objects: [
{_id: inThreadId, unreadCount: 0}
]});
},
setThreadMessagesRead: function(inThreadId){
//enyo.log("MessagingApp: updating inbox messages to be read for thread '", inThreadId, "'");
//TODO: may want to use the 'unreadRevSet' value to limit to only new
//inbox messages' read flag to be updated
this.$.readMessageMerger.call({
query: {
from: "com.palm.message:1",
where: [
{
"prop": "conversations",
"op": "=",
"val": inThreadId
},
{
"prop": "folder",
"op": "=",
"val": enyo.messaging.message.FOLDERS.INBOX
}
]
},
props: {
flags: {read: true}
}
});
},
addBuddy: function(inSender, inEvent) {
enyo.messaging.keyboard.setKeyboardAutoMode();
this.$.buddyDialog.openAtCenter();
},
showAddAccountDialog: function() {
this.$.noAccountDialog.openAtCenter();
},
addAccountClicked: function() {
this.$.noAccountDialog.close();
this.openPreferences();
},
closeDialog: function() {
this.$.noAccountDialog.close();
},
onBeforeOpenFavoriteList: function() {
this.$.addFavoriteList.removeClass("enyo-view");
},
showAddFavoriteList: function() {
enyo.messaging.keyboard.setKeyboardAutoMode();
this.$.addFavoriteList.openAtCenter();
},
favoriteSelected: function (inSender, inParams) {
ContactsLib.PersonFactory.createPersonDisplay(inParams).makeFavorite();
this.$.addFavoriteList.closeDialog();
},
closeAddFavoriteList: function () {
this.$.addFavoriteList.closeDialog();
},
updateOfflinePrefs: function() {
this.prefs.showOnlineBuddiesOnly = !this.prefs.showOnlineBuddiesOnly;
this.updatePrefs();
},
setOffline: function() {
this.setOfflineMenuItem();
this.$.messaging.setOfflinePrefs(this.prefs);
},
setOfflineMenuItem: function() {
if (this.$.showOfflineMenuItem) {
if (this.prefs.showOnlineBuddiesOnly) {
this.$.showOfflineMenuItem.setCaption($L("Show Offline Buddies"));
}
else {
this.$.showOfflineMenuItem.setCaption($L("Hide Offline Buddies"));
}
}
},
handleFirstLaunch: function() {
if (!this.$.firstLaunch) {
this.createView("firstLaunch");
}
this.selectView(this.$.firstLaunch, true);
// show buddy list after first launch
this.$.messaging.menuToggle(this, 1);
// highlights the radio button for conversations view
this.$.messaging.highlightTabButton(1);
},
doneFirstLaunch: function() {
enyo.application.firstLaunchHandler.passedFirstLaunch();
// change view
this.selectViewByName("slidingGroup");
this.$.messaging.workaroundListVisibilityBug();
},
openPreferences: function() {
if (!this.$.preferencesView) {
this.createView("preferencesView");
}
this.selectView(this.$.preferencesView, true);
// should not filter any messages from dashboards
enyo.application.messageDashboardManager.setFilter({filterAll: false, thread: undefined});
enyo.application.inviteDashboardManager.setFilter({filterAll: false, thread: undefined});
},
closePreferences: function(){
this.$.messaging.workaroundListVisibilityBug();
this.selectViewByName("slidingGroup");
// re-enable filtering in dashboards
var selected = enyo.application.selectedThread && enyo.application.selectedThread._id;
enyo.application.messageDashboardManager.setFilter({filterAll: false, thread: selected});
enyo.application.inviteDashboardManager.setFilter({filterAll: false, thread: selected});
},
backHandler: function() {
this.back();
},
openAppMenuHandler: function() {
var menu = !this.prefs || this.prefs.firstUseMode ? this.$.firstLaunchAppMenu : this.$.appMenu;
if (menu) {
menu.removeClass("enyo-view");
menu.open();
if (this.prefs && !this.prefs.firstUseMode) {
// initialize offline menu item with respect to preference setttings
this.setOfflineMenuItem();
}
}
},
/* closeAppMenuHandler: function() {
this.$.appMenu.close();
},*/
resize: function() {
this.$.slidingGroup.resize();
// instruct the conversation list to resize itself.
this.$.chatView.resize();
},
resizeHandler: function() {
this.inherited(arguments);
this.resize();
},
windowParamsChangeHandler: function(inSender, event) {
enyo.log("in windowParamsChangeHandler in MessagingApp");
enyo.log("event.params: ", event.params);
enyo.error("*&*&*&*&* window reactivated: ", event.params);
enyo.messaging.keyboard.setKeyboardAutoMode();
this.checkWideLayout();
var launchHandled = this.handleDisplayMessageLaunch(event && event.params);
return launchHandled;
},
handleDisplayMessageLaunch: function (launchParams) {
enyo.log("in MessagingApp handleDisplayMessageLaunch");
enyo.log("launchParams: ", launchParams);
if (window.PalmSystem && enyo.application.firstLaunchHandler.shouldShowFirstLaunch()) {
this.handleFirstLaunch();
return true;
}
this.selectViewByName("slidingGroup");
this.registerServices();
if (launchParams.compose) {
this.$.chatView.preHandleLaunch(launchParams.compose);
return true;
} else if (launchParams.message) {
// case launch from taping on notification, find the chat thread and launch it
this.findThread(launchParams.message.conversations[0]);
return true;
} else if (launchParams.messageText || launchParams.attachment|| launchParams.personId || (launchParams.composeRecipients && launchParams.composeRecipients.length)) {
enyo.warn("*** Warning, using deprecated launch params:",launchParams);
if(launchParams.attachment){
enyo.error("*** ERROR:attachment is not supported");
}
this.$.chatView.preHandleLaunch(launchParams);
} else {
// show threadsList in Messaging
this.showThreadsList();
return true;
}
},
findThread: function(threadId) {
var tIds = [threadId];
this.$.threadFinder.call({ids: tIds});
},
gotThread: function(inSender, inResponse) {
//case that taping on notifications, so, need update both chatView(open or update with new chatThread) and Messaging (for selection of threadList, buddyList)
this.selectThread(this, inResponse.results[0]);
},
showThreadsList: function() {
// show threadsList
this.$.messaging.menuToggle(this, 0);
// highlights the radio button for threadsList view
this.$.messaging.highlightTabButton(0);
},
windowHiddenHandler: function() {
enyo.messaging.keyboard.setKeyboardAutoMode();
if (this.prefs && !this.prefs.firstUseMode) {
// close addFavoriteList if it is opened
if (this.$.addFavoriteList && this.$.addFavoriteList.isOpen) {
this.$.addFavoriteList.closeDialog();
}
// let enclosed components to handle window hidden state themselves
this.$.messaging.windowHiddenHandler();
// clear other app states
this.unloadHandler();
// close menu
this.$.appMenu.close();
}
},
windowShownHandler:function() {
if (this.prefs && !this.prefs.firstUseMode) {
// switch back to default view
this.$.slidingGroup.selectViewImmediate(this.$.messagingSliding);
this.selectView(this.$.slidingGroup);
// call windows show handler to child components
this.$.messaging.windowShownHandler();
}
},
windowActivatedHandler: function() {
//enyo.log("***************** messaging app window activated");
// clear new message notifications
enyo.application.messageDashboardManager.setAppDeactivated(false);
if (enyo.application.selectedThread) {
this.clearUnreadCount(this, enyo.application.selectedThread._id);
}
},
windowDeactivatedHandler: function() {
//enyo.log("***************** messaging app window deactivated");
// allow new message notifications to show
enyo.application.messageDashboardManager.setAppDeactivated(true);
if (enyo.application.selectedThread) {
this.clearUnreadCount(this, enyo.application.selectedThread._id);
}
},
unloadHandler: function(){
if (enyo.application.selectedThread) {
// clear out selected thread
enyo.application.selectedThread = undefined;
}
this.unregisterServices();
// this.inherited(arguments);
},
registerServices: function() {
this.prefsHandler = enyo.application.prefsHandler;
if (this.prefsHandler) {
this.prefsHandler.register(this, this.prefsUpdated.bind(this));
}
},
unregisterServices: function() {
if (this.prefsHandler) {
this.prefsHandler.unregister(this);
if (this.$.preferencesView) {
this.$.preferencesView.unloadHandler();
}
}
if (this.$.firstLaunch) {
this.$.firstLaunch.unregisterServices();
}
},
/***********************************
* Functions below are unit tested *
***********************************/
getRemovedAccount: function(imAccounts) {
var removedAccount = null;
function imAccountsContain(account) {
var i = imAccounts.length;
while (i--) {
if (imAccounts[i].accountId === account.accountId) {
return true;
}
}
return false;
}
if (this.accountsInfo === undefined) {
this.accountsInfo = [];
}
if (imAccounts && (imAccounts.length > this.accountsInfo.length)) {
// Clear out the array so it can refreshed.
this.accountsInfo.splice(0, this.accountsInfo.length);
for (var i = 0; i < imAccounts.length; i++) {
this.accountsInfo.push(imAccounts[i]);
}
} else if (imAccounts && (imAccounts.length < this.accountsInfo.length)) {
// Determine which account that was removed.
var x = this.accountsInfo.length;
while (x--) {
if (!imAccountsContain(this.accountsInfo[x])) {
removedAccount = this.accountsInfo[x];
this.accountsInfo.splice(x, 1);
break;
}
}
}
return removedAccount;
}
});
+48
View File
@@ -0,0 +1,48 @@
enyo.kind({
name: "PopupDialog",
kind: "ModalDialog",
height: "190px",
events: {
onAccept: ""
},
components: [
{kind: "ApplicationEvents", onWindowHidden: "close"},
{layoutKind: "VFlexLayout", components:[
{name: "message", content: "", className:"enyo-paragraph"},
{name: "buttonOrientation", layoutKind: "HFlexLayout", components: [
{name: "cancelButton", kind: "Button", flex:1, caption: $L("Cancel"), onclick: "close"},
{name: "acceptButton", kind: "Button", flex:1, caption: $L("OK"), className:"enyo-button-negative", onclick: "acceptButtonClick"}
]}
]}
],
setTitle: function(title) {
if (title !== undefined) {
this.setCaption(title);
}
},
setMessage: function(message) {
if (message !== undefined) {
this.$.message.setContent(message);
}
},
setAcceptButtonCaption: function(caption) {
if (caption !== undefined) {
this.$.acceptButton.setCaption(caption);
}
},
setCancelButtonCaption: function(caption) {
if (caption !== undefined) {
this.$.cancelButton.setCaption(caption);
}
},
hideAcceptButton: function() {
this.$.acceptButton.hide();
},
hideCancelButton: function() {
this.$.cancelButton.hide();
},
acceptButtonClick: function() {
this.doAccept();
this.close();
}
});
@@ -0,0 +1,6 @@
BuddyConstants= {
OFFLINE_MESSAGE: $L("Please sign in to an IM service to see your buddies."),
NO_ONLINE_BUDDY_MESSAGE: $L("You do not have any online buddies."),
NO_BUDDY_MESSAGE: $L("You do not have any buddies."),
DELETE_BUDDY_PROMPT: $L("Are you sure you want to delete #{name} from your buddies list?")
};
@@ -0,0 +1,105 @@
/*globals enyo */
enyo.kind({
name: "BuddyItem",
kind: "SwipeableItem",
confirmCaption: $L("Delete"),
layoutKind: "HFlexLayout",
components: [
{name: "imageContainer", style: "display: inline-block;", components: [
{className: "contact-image-border"},
{name: "contactImage", kind: "Image", className: "contact-image"}
]},
{name: "status", className: "status status-buddy"},
{className:"message-summary", components: [
{layoutKind: "HFlexLayout", align: "center", className:"contact-name-box", components: [
{name: "contactName", className: "contact-name"},
{name: "favorite", kind: "Image", showing: false, className: "favorite-icon"},
{name: "unreadCount", className: "unread-count", showing: false}
]},
{name: "statusMessage", className: "status-message"}
]}
],
create: function() {
this.inherited(arguments);
this.addClass("contactItem");
},
updateContactName: function(inBuddy) {
this.$.contactName.setContent(this.getContactName(inBuddy));
},
updateStatus: function(inAvailability) {
this.$.status.setClassName(this.getStatus(inAvailability));
},
updateStatusMessage: function(inBuddy) {
this.$.statusMessage.setContent(this.getStatusMessage(inBuddy));
},
updateContactImage: function(inPerson) {
//enyo.log("#@#@ inPerson: ", inPerson);
this.$.contactImage.setAttribute("src", enyo.messaging.person.getDisplayImage(inPerson));
},
updateContactFavorite: function(inPerson) {
this.$.favorite.setShowing(inPerson && inPerson.favorite);
},
updateUnreadCount: function(inThread, inPerson) {
//enyo.log("#@#@ buddy item's chat thread: ", inThread);
var count = this.getUnreadCount(inThread);
var hasUnread = !this.isThreadSelected(inThread) && Number(count) > 0;
this.$.contactName.setClassName(this.getContactNameClass(hasUnread, inPerson));
this.$.unreadCount.setShowing(hasUnread);
this.$.unreadCount.setContent(count);
},
isThreadSelected: function(inThread) {
return inThread && enyo.application.selectedThread && (inThread._id === enyo.application.selectedThread._id);
},
setBuddy: function(inBuddy) {
//enyo.log(inBuddy);
this.updateContactName(inBuddy);
this.updateUnreadCount(inBuddy.thread, inBuddy.person);
this.updateStatus(inBuddy.personAvailability !== undefined ? inBuddy.personAvailability : inBuddy.availability);
this.updateStatusMessage(inBuddy);
this.updateContactImage(inBuddy.person);
this.updateContactFavorite(inBuddy.person);
},
/***********************************
* Functions below are unit tested *
***********************************/
getContactName: function(inBuddy) {
var displayName = inBuddy.person ? enyo.messaging.person.getDisplayName(inBuddy.person) : inBuddy.displayName || inBuddy.username;
return enyo.messaging.message.removeHtml(displayName);
},
getStatus: function(inAvailability) {
var statusClass = "status-" + enyo.messaging.im.availabilityClasses[inAvailability];
return "status status-buddy " + statusClass;
},
getStatusMessage: function(inBuddy) {
var inStatus = inBuddy.status || "";
if (inBuddy._kind === "com.palm.imbuddystatus.libpurple:1") {
// needs to unescape &amp; &apos; &lt; and &gt; from status messages
// that are synced by libpurple transport since the libpurple
// library escapes these characters.
inStatus = enyo.messaging.message.unescapeText(inStatus);
}
return enyo.messaging.message.removeHtml(inStatus);
},
getUnreadCount: function(inThread) {
return inThread && inThread.unreadCount || 0;
},
getContactNameClass: function(hasUnread, inPerson) {
var isFavorite = inPerson && inPerson.favorite;
var className = "contact-name";
if (hasUnread && isFavorite) {
className += " contact-name-unread-favorite";
} else if (hasUnread) {
className += " contact-name-unread";
} else if (isFavorite) {
className += " contact-name-favorite";
}
return className;
}
});
@@ -0,0 +1,265 @@
/*globals enyo */
enyo.kind({
name: "BuddyList",
kind: "VFlexBox",
published: {
showOffline: false,
loginStates: null
},
events: {
onSelectBuddy: "",
onShowOfflineBuddies: "",
onDeleteThread: ""
},
components: [
{kind: "BuddyService", onSuccess: "gotBuddies", onWatch: "buddiesWatch"},
{name: "search", kind: "SearchInput", hint: $L("Search"), className:"enyo-middle", onchange: "filterList", onCancel: "filterList", changeOnInput: true, autoCapitalize: "lowercase", onfocus: "disableKeyboardMannualMode"},
{className:"header-shadow header-app-shadow"},
{name: "emptyMessage", content: BuddyConstants.OFFLINE_MESSAGE, className: "messageTexts", showing: false},
{flex: 1, name: "list", kind: "DbList", desc: false, onQuery: "listQuery", className:"messaging-listsDivider", onSetupRow: "listSetupRow", components: [
{kind: "Divider", className:"buddyList-Divider"},
{name: "buddyItem", tapHighlight:true, kind: "BuddyItem", onclick: "buddyClicked", onConfirm: "showDeleteConfirmDialog", onCancel: "disableKeyboardMannualMode"}
]},
{name: "deleteDialog", kind: "PopupDialog", onAccept: "deleteBuddy"},
{kind: "DeleteBuddyService", onDeleteThread: "doDeleteThread"}
],
buddiesWatch: function() {
//enyo.log("#@#@ buddies list is updating......");
this.$.list.reset();
},
showOfflineChanged: function() {
// dump the existing list dataset so that subsequent list query will use
// the new 'showOffline' flag
this.$.list.punt();
},
filterList: function(inSender, inValue){
this.filterString = this.$.search.getValue();
this.$.list.punt();
},
listQuery: function(inSender, inQuery) {
if (this.filterString && this.filterString.length > 0) {
inQuery.where = [{
prop: "primary",
op: "=",
val: true
}, {
prop: "offline",
op: "=",
val: this.showOffline ? [true, false] : false
}, {
prop: "displayName",
op: "?",
val: this.filterString,
"collate": "primary",
"tokenize": "all"
}];
inQuery.search = true;
} else {
// var whereStr = "where primary=true and offline=" + (this.showOffline ? "[true, false]" : "false") + " and groupAvailability%''";
// var where = parseQuery(whereStr).where;
var where = [{
prop: "primary",
op: "=",
val: true
}, {
prop: "offline",
op: "=",
val: this.showOffline ? [true, false] : false
}, {
prop: "groupAvailability",
op: "%",
val: "",
collate: "secondary"
}];
// where[2].collate = "secondary";
inQuery.where = where;
}
if (window.PalmSystem) {
return this.$.buddyService.call({query: inQuery});
} else {
if (!this.gotMockData) {
this.gotMockData = true;
return this.$.buddyService.call({query: inQuery});
} else {
return ;
}
}
},
gotBuddies: function(inSender, inResponse, inRequest) {
//enyo.log("#@#@ got buddies: ", inResponse);
this.$.list.queryResponse(inResponse, inRequest);
this.emptyMessageCheck(inResponse, inRequest, this.loginStates, this.filterString, this.showOffline);
},
showEmptyMessage: function(message) {
this.$.emptyMessage.setContent(message);
this.$.emptyMessage.show();
},
listSetupRow: function(inSender, inBuddy, inIndex) {
//enyo.log("#@#@ buddy list set up row" );
if (!this.filterString || this.filterString.length === 0) {
this.setupDivider(inBuddy, inIndex);
}
else{
this.$.divider.canGenerate = false;
}
this.$.buddyItem.setBuddy(inBuddy);
// highlight selected buddy
this.$.buddyItem.addRemoveClass("enyo-item-selected", this.shouldHighlight(inBuddy, this.selectedRecord));
},
setupDivider: function(inBuddy, inIndex) {
var offlineCaption = $L("offline");
var caption = inBuddy.offline ? offlineCaption : inBuddy.group;
var pt = this.$.list.fetch(inIndex - 1);
var previousCaption = pt && (pt.offline ? offlineCaption : pt.group);
//
var showDivider = caption != previousCaption;
//enyo.log("#@#@ current caption: ", caption, " previous caption: ", previousCaption, " show divider: ", showDivider);
if (showDivider) {
this.$.divider.setCaption(caption.toUpperCase());
this.$.buddyItem.addClass("enyo-first");
}
//
this.$.divider.canGenerate = showDivider;
},
buddyClicked: function(inSender, inEvent) {
var selectedRecord = this.$.list.fetch(inEvent.rowIndex);
if (selectedRecord && this.selectedRecord && selectedRecord.personId === this.selectedRecord.personId){
return;
}
else{
this.selectedRecord = selectedRecord;
}
if (this.selectedRecord) {
enyo.log("clicked on buddy: ", (this.selectedRecord.displayName || this.selectedRecord.username));
// broadcast buddy selection
this.doSelectBuddy(this.selectedRecord);
this.$.list.refresh();
}
},
showDeleteConfirmDialog: function(inSender, inEvent) {
enyo.messaging.keyboard.setKeyboardAutoMode();
var deletedBuddy = this.$.list.fetch(inEvent);
this.$.deleteBuddyService.setBuddy(deletedBuddy);
var template = new enyo.g11n.Template($L("Are you sure you want to delete #{name} from your buddies list?"));
var dialogMessage = template.evaluate({name: deletedBuddy.displayName});
enyo.log("Deleting buddy: ", deletedBuddy);
this.$.deleteDialog.openAtCenter();
this.$.deleteDialog.setAcceptButtonCaption($L("Delete"));
this.$.deleteDialog.setTitle($L("Remove Buddy"));
this.$.deleteDialog.setMessage(dialogMessage);
},
deleteBuddy: function(inSender, inEvent) {
this.$.deleteBuddyService.deleteBuddy();
},
setSelection: function(inChatThread){
if ((this.selectedRecord && inChatThread
&& ((inChatThread.buddyId && this.selectedRecord.buddyId === inChatThread.buddyId)
|| (inChatThread.personId && this.selectedRecord.personId === inChatThread.personId)))
|| (!this.selectedRecord && inChatThread && !inChatThread.personId)) {
//case selection didn't change, do nothing
return;
} else {
this.selectedRecord = {
personId: inChatThread ? inChatThread.personId : null
};
// if (!inChatThread) {
this.$.list.refresh();
// }
}
},
windowHiddenHandler: function(){
// dump data buffer
this.$.list.punt();
// cancel service call
//this.$.buddyService.cancel();
// clear out filter search
this.filterString = "";
this.$.search.setValue(this.filterString);
this.$.search.forceBlur();
},
disableKeyboardMannualMode: function(){
enyo.messaging.keyboard.setKeyboardAutoMode();
},
updateList: function() {
this.$.list.update();
},
resetList: function() {
this.$.list.reset();
},
/***********************************
* Functions below are unit tested *
***********************************/
isOffline: function(states) {
var offline = true;
for (var i = 0; i < states.length; i++) {
if (states[i].availability !== enyo.messaging.im.availability.OFFLINE && states[i].state !== "offline") {
offline = false;
}
}
return offline;
},
emptyMessageCheck: function(inResponse, inRequest, loginStates, filterString, showOffline) {
var message;
// by default, hide these messages
this.$.emptyMessage.hide();
if (!loginStates || loginStates.length === 0) {
message = BuddyConstants.OFFLINE_MESSAGE;
} else if ((inRequest.index === 0) && (!inResponse || !inResponse.results || inResponse.results.length === 0)) {
if (filterString && filterString.length > 0) {
// No buddies found using search
message = enyo.messaging.CONSTANTS.NO_SEARCH_RESULTS;
} else if (this.isOffline(loginStates)) {
// all accounts are offline
message = BuddyConstants.OFFLINE_MESSAGE;
} else if (showOffline) {
// really has no buddies
message = BuddyConstants.NO_BUDDY_MESSAGE;
} else {
// either has no online buddies or no buddies at all
message = BuddyConstants.NO_ONLINE_BUDDY_MESSAGE;
}
}
if (message !== undefined) {
this.showEmptyMessage(message);
}
// Added the return for unit testing
return message;
},
shouldHighlight: function(inBuddy, selectedRecord) {
if (!selectedRecord) {
enyo.log("Selected record doesn't exist, so can't highligh buddy items");
return false;
}
if (selectedRecord.personId) {
// if person Id is found, use it for matching buddy record
return selectedRecord.personId === inBuddy.personId;
}
if (selectedRecord.normalizedAddress) {
// If normalizedAddresss is found, use it for matching buddy record.
// This selection should come from a tap event from conversations list.
return selectedRecord.normalizedAddress === enyo.messaging.utils.normalizeAddress(inBuddy.username, inBuddy.serviceName);
}
if (selectedRecord.username && selectedRecord.serviceName) {
// This selection should come from buddy list.
return enyo.messaging.utils.normalizeAddress(selectedRecord.username, selectedRecord.serviceName) === enyo.messaging.utils.normalizeAddress(inBuddy.username, inBuddy.serviceName);
}
return false;
}
});
@@ -0,0 +1,121 @@
enyo.kind({
name: "BuddyService",
kind: enyo.Service,
requestKind: "BuddyRequest",
events: {
onWatch: ""
}
});
enyo.kind({
name: "BuddyRequest",
kind: enyo.Request,
events: {
onWatch: "doWatch"
},
components: [
{kind: "DbService", onFailure: "fail", components: [
{name: "buddies", method: "find", service: "palm://com.palm.tempdb/", dbKind: "com.palm.imbuddystatus:1", onSuccess: "gotBuddies", subscribe: true, onWatch: "doWatch"/*, resubscribe: true, reCallWatches: true*/},
{name: "buddiesSearch", method: "search", service: "palm://com.palm.tempdb/", dbKind: "com.palm.imbuddystatus:1", onSuccess: "gotBuddies", subscribe: true, onWatch: "doWatch"/*, resubscribe: true, reCallWatches: true*/},
{name: "threads", dbKind: "com.palm.chatthread:1", method: "find", onSuccess: "gotThreads", subscribe: true, onWatch: "doWatch"/*, resubscribe: true, reCallWatches: true*/ },
{name: "people", dbKind: "com.palm.person:1", method: "find", onSuccess: "gotPeople", subscribe: true, onWatch: "doWatch"/*, resubscribe: true, reCallWatches: true*/}
]},
{name: "mockBuddies", kind: "MockDb", dbKind: "buddies/com.palm.imbuddystatus:1", method: "find", onSuccess: "gotBuddies", onFailure: "fail", onWatch: "doWatch"},
{name: "mockPeople", kind: "MockDb", dbKind: "persons/com.palm.person:1", method: "find", onSuccess: "gotPeople", onFailure: "fail", onWatch: "doWatch"},
{name: "mockThreads", kind: "MockDb", dbKind: "chatthreads/com.palm.chatthread:1", method: "find", onSuccess: "gotThreads", onFailure: "fail", onWatch: "doWatch"}
],
create: function() {
this.inherited(arguments);
},
initComponents: function() {
this.inherited(arguments);
if (!window.PalmSystem) {
this.$.buddies = this.$.mockBuddies;
this.$.people = this.$.mockPeople;
this.$.threads = this.$.mockThreads;
}
},
call: function() {
//enyo.log(this.params);
if (this.params && this.params.query && this.params.query.search) {
this.params.query.search = undefined;
this.$.buddiesSearch.cancel();
this.$.buddiesSearch.call(this.params);
} else {
this.$.buddies.cancel();
this.$.buddies.call(this.params);
}
},
reply: function() {
//enyo.log(this.data);
this.receive(this.data);
},
fail: function(inSender, inResponse) {
enyo.error("BuddyService - DbService failure: ", inResponse);
this.reply();
},
finish: function() {
// don't destroy automatically, so we can see watches
},
gotBuddies: function(inSender, inResponse, inRequest) {
//enyo.log("#@#@ got buddies in ", inRequest.latency, "ms");
//enyo.log(inResponse);
this.data = inResponse;
this.assembleIds();
this.queryPeople();
},
assembleIds: function() {
this.pids = [];
for (var i=0, d; d=this.data.results[i]; i++) {
this.pids.push(d.personId);
}
},
queryPeople: function() {
this.$.people.id = "buddyRequest_people";
this.$.people.cancel();
this.$.people.call({query: {
where: [{
prop: "_id",
op: "=",
val: this.pids
}],
select: enyo.messaging.person.selectAttributes
}
});
},
gotPeople: function(inSender, inResponse, inRequest) {
// enyo.log("#@#@ got people in ", inRequest.latency, "ms");
// enyo.log("#@#@ got people", inResponse);
// join person data to buddy data
enyo.messaging.utils.joinData(this.data, inResponse.results, "_id", "personId", "person");
this.queryThreads();
},
queryThreads: function() {
this.$.threads.id = "buddyRequest_threads";
this.$.threads.cancel();
this.$.threads.call({query: {
where: [{
prop: "personId",
op: "=",
val: this.pids
}],
select: [
"_id",
"_kind",
"personId",
"normalizedAddress",
"displayName",
"unreadCount"
]
}
});
},
gotThreads: function(inSender, inResponse, inRequest) {
//enyo.log("#@#@ got chat threads in ", inRequest.latency, "ms");
//enyo.log("#@#@ got chat threads: ", inResponse);
// join chat thread data to buddy data
enyo.messaging.utils.joinData(this.data, inResponse.results, "personId", "personId", "thread");
// all done now
this.reply();
}
});
@@ -0,0 +1,129 @@
enyo.kind({
name: "DeleteBuddyService",
kind: "enyo.Component",
published: {
buddy: undefined
},
events: {
onDeleteThread: ""
},
components: [
{kind: "DbService", onFailure: "fail", components: [
{name: "personFinder", method: "get", onSuccess: "gotPerson"},
{name: "contactFinder", method: "get", onSuccess: "gotContacts"},
{name: "threadFinder", dbKind: enyo.messaging.thread.dbKind, method: "find", onSuccess: "gotThreads"}
]},
{name: "deleteService", kind: "DeleteThreadService"}
],
deleteBuddy: function() {
if (this.buddy.person && this.buddy.person.contactIds) {
// get all the contact records that are linked for this person
this.$.contactFinder.call({
ids: this.buddy.person.contactIds
});
} else {
if (this.buddy.personId) {
this.$.personFinder.call({
ids: [this.buddy.personId]
});
} else {
enyo.error("Can't delete buddy", this.buddy._id, "with no personId!");
}
}
},
gotPerson: function(inSender, inResponse) {
if (inResponse.results.length > 0) {
var person = inResponse.results[0];
// get all the contact records that are linked for this person
this.$.contactFinder.call({
ids: person.contactIds
});
} else {
enyo.error("Can't delete buddy", this.buddy._id, "with no person record!");
}
},
gotContacts: function(inSender, inResponse) {
var contacts = inResponse.results;
contacts.forEach(this.addDeleteBuddy.bind(this));
this.deleteBuddyThreads();
},
addDeleteBuddy: function(contact) {
if (contact.imBuddy) {
new DeleteBuddyCommand().deleteContact(contact, enyo.application.accountService);
}
},
deleteBuddyThreads: function() {
this.$.threadFinder.call({
query: {
where: [{
prop: "personId",
op: "=",
val: this.buddy.personId
}],
select: [
"_id"
]
}
});
},
gotThreads: function(inSender, inResponse){
var results = inResponse.results;
// set the thread id to each deleted buddy
for (i = 0; i < results.length; i++) {
var thread = results[i];
this.deleteThread(thread);
}
},
deleteThread: function(thread) {
this.$.deleteService.setId(thread._id);
this.$.deleteService.deleteThread();
this.doDeleteThread(thread);
}
});
enyo.kind({
name: "DeleteBuddyCommand",
kind: "enyo.Component",
components: [
{kind: "DbService", onFailure: "fail", components: [
{name: "accountFinder", method: "get", onSuccess: "gotAccount"},
{name: "deleteCommand", dbKind: "com.palm.imcommand:1", method: "put"}
]}
],
deleteContact: function(contact, accountService) {
this.contact = contact;
this.accountService = accountService;
// get IM account so that we can get the value for 'fromUsername'
this.$.accountFinder.call({
ids: [this.contact.accountId]
});
},
gotAccount: function(inSender, inResponse) {
var account = inResponse.results[0];
var ims = this.contact.ims;
for (var i = 0; ims && i < ims.length; i++) {
this.doDeleteCommand(ims[i].value, ims[i].type, account.username);
}
},
doDeleteCommand: function(targetUsername, serviceName, fromUsername) {
enyo.log("Creating ImCommand to delete buddy: {", targetUsername, ", ", serviceName, ", ", fromUsername, "}");
if (!targetUsername || !serviceName || !fromUsername) {
enyo.warn("Found invalid parameter to delete a buddy");
return;
}
// create IM command to delete the contact
this.$.deleteCommand.call({
objects: [{
_kind: this.accountService.getImCommandKind(serviceName),
command: "deleteBuddy",
handler: "transport",
targetUsername: targetUsername,
fromUsername: fromUsername,
serviceName: serviceName
}]
});
}
});
@@ -0,0 +1,470 @@
/*globals enyo */
enyo.kind({
name: "ComposeView",
kind: enyo.VFlexBox,
className: "composeview",
published: {
loginStates:[],
params:"",
input:""
},
events: {
onOpenConversation: ""
},
components: [
{kind: "ApplicationEvents", onUnload: "windowUnloadHandler", onWindowHidden:"windowHiddenHandler"},
{layoutKind: "HFlexLayout", className: "enyo-row", components: [
/* Watch keyup because the default action of a key (printing/deleting a character)
* is done before keyup, which means the input will have resized.
* Watch keypress because pressing & holding a key generates
* multiple presses but never a keyup
*/
{kind: "AddressingPopup", onkeyup: "addressingPopupKeyup", onBeforePopupOpen: "onBeforePopupOpenHandler", flex: 1, addressTypes: ["ims", "phoneNumbers"], components: [
{kind: "ImTransportList", onSelect: "chatUsingTransport"}
]}
]},
{flex: 1},
{className:"footer-shadow"},
{kind: "Toolbar", className:"enyo-toolbar-light conversation-bottom", components: [
{name: "slidingDrag", slidingHandler: true, kind: "GrabButton" },
/* Watch keyup because the default action of a key (printing/deleting a character)
* is done before keyup, which means the input will have resized.
* Watch keypress because pressing & holding a key generates
* multiple presses but never a keyup
*/
{name: "scroller", kind: "BasicScroller", style: "max-height: 155px;", flex: 1, horizontal: false, autoHorizontal: false, components: [
{name: "richText", kind: "RichText", hint: $L("Enter message here..."), richContent: false, onkeyup: "checkInputHeight", alwaysLooksFocused:true, onkeydown: "checkKey", autoEmoticons: true}
]}
]},
{name: "errorDialog", kind: "PopupDialog"},
{name: "connectPhoneDialog", kind: "ConnectPhoneDialog", onClose: "openConversationAfterPaired"},
{name: "messageServiceWatch", kind: "DbService", dbKind: "com.palm.message:1", method: "find", subscribe: true, resubscribe: true, reCallWatches: true, onSuccess: "gotMessage"},
{name: "messageService", kind: "DbService", dbKind: enyo.messaging.message.dbKind, onFailure: "messagesFailure", components: [
{name: "messageServiceFind", method: "find", onSuccess: "gotDraftMessages"},
{name: "messageServiceDelete", method: "del", onSuccess: "deletedDraftMessages"},
{name: "messageServicePut", method: "put", onSuccess: "onInsertMessage"}
]}
],
create: function() {
this.inherited(arguments);
if (enyo.application.telephonyWatcher) {
enyo.application.telephonyWatcher.register(this, this.connectionUpdated.bind(this));
}
this.getDraft();
},
getDraft: function(){
this.$.messageServiceFind.call({
query: {
where: [{
prop: "conversations",
op: "=",
val: "_compose"
}, {
prop: "folder",
op: "=",
val: enyo.messaging.message.FOLDERS.DRAFTS
}],
select: [
"contacts",
"conversations",
"messageText"
]
}
});
},
gotDraftMessages: function(inSender, inResponse){
if (inResponse.results[0] && inResponse.results[0].conversations && inResponse.results[0].conversations[0] === "_compose") {
this.$.richText.setValue(inResponse.results[0].messageText);
var contacts = inResponse.results[0].contacts;
if( contacts && contacts.length>0){
this.$.addressingPopup.setContacts(contacts);
}
this.$.messageServiceDelete.call({
query: {
from: enyo.messaging.message.dbKind,
where: [{
prop: "conversations",
op: "=",
val: "_compose"
}, {
prop: "folder",
op: "=",
val: enyo.messaging.message.FOLDERS.DRAFTS
}]
}
});
}
},
connectionUpdated: function(connected) {
this.phoneConnected = connected;
},
onBeforePopupOpenHandler: function(){
// this.$.addressingPopup.$.popup.validateComponents();
this.$.imTransportList.setLoginStates(this.loginStates);
this.$.imTransportList.setInput(this.input);
},
addressingPopupKeyup: function(inSender, inEvent) {
if (this.$.imTransportList) {
this.$.imTransportList.setInput(inSender.getInputValue());
}
this.input = inSender.getInputValue();
},
chatUsingTransport: function (inSender, inDisplayName, inValue, inServiceName){
this.$.addressingPopup.addContact({displayName: inDisplayName, value: inValue, serviceName: inServiceName, type: inServiceName});
},
loginStatesChanged: function(oldLoginStates){
if (this.$.imTransportList) {
this.$.imTransportList.setLoginStates(this.loginStates);
}
},
paramsChanged: function(inOldParams){
enyo.log("ComposeView::paramsChanged::params:",this.params, "inOldParams:",inOldParams);
this.prePopulatedRecipients = [];
if (this.params !== undefined) {
var contactAddress;
if (this.params.personId !== undefined) {
this.personId = this.params.personId;
}
if (this.params.ims !== undefined) {
for (i = 0; i < this.params.ims.length; i++) {
contactAddress = this.params.ims[i];
if (contactAddress.value || contactAddress.addr) {
this.prePopulatedRecipients.push({
value: contactAddress.value || contactAddress.addr,
displayName: contactAddress.displayName || contactAddress.value || contactAddress.addr,
type: "im",
serviceName: contactAddress.serviceName || contactAddress.type
});
}
}
}
if (this.params.phoneNumbers !== undefined) {
for (i = 0; i < this.params.phoneNumbers.length; i++) {
contactAddress = this.params.phoneNumbers[i];
if (contactAddress.value || contactAddress.addr) {
this.prePopulatedRecipients.push({
value: contactAddress.value || contactAddress.addr,
displayName: contactAddress.displayName || contactAddress.value || contactAddress.addr,
serviceName: contactAddress.serviceName || contactAddress.type || "sms",
type: "phone"
});
}
}
}
// composeRecipients expects the following:
// [
// {
// "address": <phone number or IM>,
// "serviceName": <serviceName or domain> // only required for IM addresses
// }, ...
// ]
if(this.params.composeRecipients && this.params.composeRecipients.length) {
for (i = 0; i < this.params.composeRecipients.length; i++) {
var recipient = this.params.composeRecipients[i];
var serviceName;
if (recipient.serviceName && (recipient.serviceName).indexOf("type_") === 0){
serviceName = recipient.serviceName;
}
else {
serviceName = recipient.serviceName ? "type_"+recipient.serviceName : '';
}
if (recipient.address) {
this.prePopulatedRecipients.push({
value: recipient.address,
displayName: recipient.address,
type: (recipient.serviceName ? 'im' : 'phone'),
serviceName: serviceName
});
}
}
}
if (this.params.messageText !== undefined) {
this.$.richText.setValue(this.params.messageText);
setTimeout((function(){
this.$.addressingPopup.forceFocus();
}).bind(this), 0);
}
}
if (this.prePopulatedRecipients.length > 0) {
enyo.log("ComposeView::has prePopulatedRecipients");
var contacts = [], i;
for (i=0; i < this.prePopulatedRecipients.length; i++){
contacts.push({displayName:this.prePopulatedRecipients[i].displayName, value: this.prePopulatedRecipients[i].value, serviceName: this.prePopulatedRecipients[i].serviceName});
}
this.$.addressingPopup.setContacts(contacts);
}
},
considerForSend: function(){
var message = this.$.richText.getValue();
if (message.length > 0) {//todo: check max length?
var contacts = this.$.addressingPopup.getContacts();
enyo.log("ComposeView::considerForSend::contacts:",contacts);
var recipientsArray = [];
if (contacts && contacts.length > 0) {
recipientsArray = contacts;
if (recipientsArray.length > 0) {
var phoneRecipients = [];
var imRecipients = [];
var lastService = null;
var isGroupChatCandidate = recipientsArray.length > 1;
// separate the different types of recipients
for (var x = 0; x < recipientsArray.length; x++) {
var recipient = recipientsArray[x];
if (recipient !== undefined) {
//this.pushReminder(recipient);
var serviceName;
if(!recipient.serviceName && recipient.type){
serviceName = recipient.type;
}
else{
serviceName = recipient.serviceName;
}
if (enyo.messaging.utils.isTextMessage(serviceName)) {
phoneRecipients.push(recipient);
isGroupChatCandidate = false;
}
else {
// make sure that the serviceName is supported
enyo.log("ComposeView::ConsiderForSend::recipient.serviceName:", serviceName);
var account = enyo.application.accountService.getAccount(serviceName);
if (!account) {
var errorMessage = "";
//errorMessage = $L("You are attempting to send to an IM type that is not supported. Please choose a different address.");
errorMessage = $L("You are attempting to send to an IM type for which you do not have an account to send from.");
this.$.errorDialog.openAtCenter();
this.$.errorDialog.setTitle($L("Unable to send"));
this.$.errorDialog.setMessage(errorMessage);
this.$.errorDialog.setCancelButtonCaption($L("OK"));
this.$.errorDialog.hideAcceptButton();
return;
}
else {
if (lastService === null) {
lastService = serviceName;
}
else
if (lastService !== serviceName) {
isGroupChatCandidate = false;
}
}
if(!recipient.serviceName && recipient.type){ //gtalk has type set, but not serviceName
recipient.serviceName = recipient.type;
}
imRecipients.push(recipient);
}
}
}
// send to phone recipients
if (phoneRecipients.length > 0) {
this.sendMessage(phoneRecipients, message);
}
if (imRecipients.length > 0) {
// send to IM recipients if not starting a group chat, don't segment the data for IM
// we start a group chat if all recipients are IM recipients of the same service, and that
// service supports group chat.
if (isGroupChatCandidate && false) {
/* this.supportsGroupChat(lastService)) {
place holder for group logic later
}*/
}
else {
this.sendMessage(imRecipients, message);
this.sendMessageCount++;
}
}
}
else {//no recipient from addressingPopup.contacts
enyo.error("ComposeView::considerForSend:recipientsArray is empty");
}
}
else{
enyo.warn("ComposeView::considerForSend:: addressingPopup.getContacts() return 0 contacts, don't do anything.");
}
}
else{
enyo.warn("ComposeView::considerForSend:: messagingText is empty, don't do anything.");
}
},
sendMessage: function(recipientsArray, message) {
enyo.log("ComposeView::sendMessage: no. recip:", recipientsArray.length, " message:", message);
var kind;
for( var i=0; i<recipientsArray.length; i++ ){
kind = undefined; // ensure this starts out undefined each iteration
var recipient = recipientsArray[i];
var currentTimestamp = Date.now();
if(recipient.displayName !== undefined) {
recipient.displayName = recipient.displayName;
}
if(!recipient.serviceName){
recipient.type = "phone";
}
else if(enyo.messaging.utils.isTextMessage(recipient.serviceName)){
recipient.type = "phone";
}
else{
recipient.type = "im";
}
var params = {
folder: enyo.messaging.message.FOLDERS.OUTBOX,
status: "pending",
to: [{
"addr": enyo.messaging.utils.cleanPhoneNumber(recipient.value, recipient.type),
"name": recipient.displayName
}],
timestamp: currentTimestamp,
flags: {
visible: true,
deliveryReport: false//this.Messaging.messagingPrefs.getUseDeliveryReceipts() && enyo.messaging.utils.isTextMessage(recipient.serviceName)
},
localTimestamp: currentTimestamp,
serviceName: recipient.serviceName,
messageText: message
};
if (recipient.type === "im") {
var dbkinds = enyo.application.accountService.getDbKinds(recipient.serviceName);
if (dbkinds) {
kind = dbkinds.immessage;
}
else {
enyo.error("No dbkinds specified for service ", recipient.serviceName);
}
//NOTE: this is a dumb attempt at getting an account. It should really figure out which account
// is associated with the buddy (recipient.value)
var account = enyo.application.accountService.getAccount(recipient.serviceName);
if (account) {
enyo.error("ComposeView::sendMessage::Recipient is missing sender address. Guessing that account is ok:", account.username);
params.from = {
addr: account.username
};
params.username = account.username;
}
else {
enyo.error("ComposeView::sendMessage::Recipient ",recipient.value, " is missing sender address and there is no account for serviceName " , recipient.serviceName);
}
}
else {
kind = "com.palm.smsmessage:1";
params.serviceName = "sms";
}
if(kind !== undefined) {
this.sendMessageHelper(params, kind);
// MessagingUtils.checkAirplaneMode(params);
} else {
enyo.error("ComposeView::sendMessage::Warning! Unsupported recipient: ", recipient);
}
}
},
sendMessageHelper: function(params, kind){
params._kind = kind;
params.folder = "outbox";
params.localTimestamp = Date.now();
this.$.messageServicePut.call({objects: [params]});
this.$.richText.setValue("");
this.prePopulatedRecipients = [];
this.$.addressingPopup.setContacts(null);
if (kind === enyo.messaging.message.SMS.dbKind && !this.phoneConnected) {
// this call is needed for devices that lack of SMS capability
// phone is not connected, so prompt the user to connect to the phone
this.$.connectPhoneDialog.openAtCenter();
}
},
onInsertMessage: function(inSender, inResponse){
if (inResponse.returnValue && inResponse.results && inResponse.results.length > 0) {
var whereClause = [{"prop":"_id","op":"=","val":inResponse.results[0].id}];
this.$.messageServiceWatch.cancel();
this.$.messageServiceWatch.call({
query: {
where: whereClause
}
});
}
},
gotMessage: function(inSender, inResponse){
if (inResponse.returnValue && inResponse.results && inResponse.results.length > 0 && inResponse.results[0].folder !== enyo.messaging.message.FOLDERS.DRAFTS) {
this.message = inResponse.results[0];
if (this.message.conversations && this.message.conversations.length) {
this.$.messageServiceWatch.cancel();
if (this.message._kind !== enyo.messaging.message.SMS.dbKind || this.phoneConnected) {
//enyo.log("************ opening conversation: ", this.message.conversations[0]);
this.doOpenConversation(this.message.conversations[0]);
this.message = undefined;
}
}
}
},
openConversationAfterPaired: function() {
if (this.message && this.message.conversations && this.message.conversations.length > 0) {
this.doOpenConversation(this.message.conversations[0]);
this.message = undefined;
}
},
checkKey: function(inSender, inEvent) {
var messageText;
// Pressing "enter" should send a message and clear the input
if (inEvent.keyCode === 13) {
inEvent.preventDefault();
messageText = this.$.richText.getValue();
// Only send non-empty messages
if (messageText) {
this.considerForSend();
}
}
},
windowHiddenHandler: function(){
this.$.addressingPopup.close();
if (this.$.richText.hasFocus()) {
this.$.richText.forceBlur();
}
},
saveDraft: function(){
var message = this.$.richText.getValue();
if (message.length > 0) {
var contacts = this.$.addressingPopup.getContacts();
var params = {
contacts: contacts,
messageText: message,
attachment: this.attachment,
folder: enyo.messaging.message.FOLDERS.DRAFTS,
flags: { visible: "false" },
_kind: enyo.messaging.message.dbKind,
conversations: ["_compose"],
localTimestamp: Date.now()
};
this.$.messageServicePut.call({objects: [params]});
}
},
messagesFailure: function(inSender, inResponse){
enyo.error("--------ComposeView::messagesFailure::inResponse:", inResponse);
},
windowUnloadHandler: function(){
if (enyo.application.telephonyWatcher) {
enyo.application.telephonyWatcher.unregister();
}
this.saveDraft();
}
});
@@ -0,0 +1,43 @@
var msg_cpd_windowHeight = "220px";
if (enyo.g11n.currentLocale().language == "fr") msg_cpd_windowHeight = "250px";
if (enyo.g11n.currentLocale().language == "de") msg_cpd_windowHeight = "270px";
enyo.kind({
name: "ConnectPhoneDialog",
kind: "ModalDialog",
caption: $L("Message Can't Be Sent"),
height: msg_cpd_windowHeight,
contentClassName: "enyo-paragraph",
components: [
{kind: "ApplicationEvents", onWindowHidden: "close", onUnload: "close", onWindowActivated: "windowActivatedHandler"},
{name: "message", content: $L("A phone must be connected to this device via Bluetooth in order to send a text message.")},
{name: "buttonOrientation", layoutKind: "VFlexLayout", components: [
{name: "connectButton", kind: "Button", flex:1, caption: $L("Connect Phone"), className:"enyo-button-dark", onclick: "connectButtonClick"},
{name: "closeButton", kind: "Button", flex:1, caption: $L("OK"), onclick: "close"}
]},
{name: "launchApp", kind: "Launcher", app: "com.palm.app.bluetooth"}
],
renderOpen: function() {
this.inherited(arguments);
if (enyo.application.telephonyWatcher) {
enyo.application.telephonyWatcher.register(this, this.connectionUpdated.bind(this));
}
},
connectionUpdated: function(connected) {
this.connected = connected;
},
connectButtonClick: function() {
this.$.launchApp.launch();
},
windowActivatedHandler: function() {
if (this.isOpen && this.connected) {
this.close();
}
},
close: function() {
this.inherited(arguments);
if (this.isOpen && enyo.application.telephonyWatcher) {
enyo.application.telephonyWatcher.unregister(this);
}
}
});
@@ -0,0 +1,182 @@
enyo.kind({
name: "ImTransportList",
kind: enyo.Control,
published: {
loginStates: [],
input: ""
},
shortcode: "",
events: {
onSelect: ""
},
chrome: [
// {kind: "Divider", caption: $L("SHORT CODE")},
// {name: "sms", kind: enyo.Item, tapHighlight: true, align: "center", layoutKind: "HFlexLayout", onclick: "itemClick"},
{name: "imDivider", kind: "Divider", caption: $L("IM USING:")},
{name: "transports", kind: enyo.Item, tapHighlight: true, align: "center", layoutKind: "HFlexLayout"}
],
create: function(){
this.inherited(arguments);
// this.$.divider.hide();
// this.$.sms.hide();
},
inputChanged: function(){
if (this.input.length > 0) {
/* this.shortcode = this.toPhonepadNumber(this.input);
if (this.shortcode) {
this.$.sms.setContent(this.input + " (" + this.shortcode + ")");
this.$.divider.show();
this.$.sms.show();
} else {
this.$.divider.hide();
this.$.sms.hide();
}*/
var template = new enyo.g11n.Template($L("IM with \"#{input}\" using:"));
var userinput = template.evaluate({input: this.input});
this.$.imDivider.setCaption(userinput.toUpperCase());
if (this.loginStates.length > 0) {
if (this.imItems === undefined) {
this.loginStatesChanged();
} else if(this.imItems.length>0) {
this.$.imDivider.show();
this.$.transports.show();
} else {
this.$.imDivider.hide();
this.$.transports.hide();
}
} else {
this.$.imDivider.hide();
this.$.transports.hide();
}
} else {
// this.$.divider.hide();
// this.$.sms.hide();
this.$.imDivider.hide();
this.$.transports.hide();
}
},
loginStatesChanged: function(){
if (this.loginStates.length > 0 && this.input.length > 0) {
var state, icon;
var items = [];
var serviceNameHash = {};
for (var i = 0; i < this.loginStates.length; i++) {
state = this.loginStates[i];
if(state.state === "online" && enyo.application.accountService.getChatWithNonBuddies(state.serviceName) !== false){
icon = enyo.application.accountService.getIcons(state.serviceName);
if (icon && icon.splitter && !serviceNameHash[state.serviceName]) {
serviceNameHash[state.serviceName] = state.serviceName;
items.push({name: state.serviceName, kind: "Image", src: icon.splitter, style: "display: block", onclick: "itemClick"});
}
}
}
this.imItems = items;
if (items.length > 0) {
this.$.transports.destroyControls();
this.$.transports.createComponents(items, {owner: this});
this.$.transports.components = items;
this.render();
this.$.imDivider.show();
this.$.transports.show();
} else {
this.$.imDivider.hide();
this.$.transports.hide();
}
} else {
this.$.imDivider.hide();
this.$.transports.hide();
}
},
itemClick: function(inSender){
if (inSender.name === "sms") {
this.doSelect(this.input+"("+this.shortcode+")", this.shortcode, inSender.name);
} else {
this.doSelect(this.input, this.input, inSender.name);
}
this.input = "";
this.inputChanged();
},
toPhonepadNumber: function(str){
var smsAccount = enyo.application.accountService.getAccount("sms");
if (!smsAccount){
return null;
}
return this.getNumberString(str);
},
/***********************************
* Functions below are unit tested *
***********************************/
getNumberString: function(str){
var is = true;
var numChar = "-1";
var numberStr = "";
for (var i = 0; i < str.length; i++) {
numChar = this.translateToPhonepadDigit(str.charAt(i));
if (numChar == "-1") {
is = false;
break;
} else {
numberStr += numChar;
}
}
if (is) {
return numberStr;
} else {
return null;
}
},
translateToPhonepadDigit: function(letter){
var number = -1;
letter = letter.toLowerCase();
switch (letter) {
case "a":
case "b":
case "c":
number = 2;
break;
case "d":
case "e":
case "f":
number = 3;
break;
case "g":
case "h":
case "i":
number = 4;
break;
case "j":
case "k":
case "l":
number = 5;
break;
case "m":
case "n":
case "o":
number = 6;
break;
case "p":
case "q":
case "r":
case "s":
number = 7;
break;
case "t":
case "u":
case "v":
number = 8;
break;
case "w":
case "x":
case "y":
case "z":
number = 9;
break;
}
return number;
}
});
@@ -0,0 +1,44 @@
enyo.kind({
name: "BlockPersonService",
kind: "enyo.Component",
published: {
thread: null
},
components: [
{kind: "DbService", components:[
{name: "messageFinder", dbKind: "com.palm.message:1", method: "find", onSuccess: "gotMessage"},
{name: "dbAdder", dbKind: "com.palm.imcommand:1", method: "put"}
]}
],
blockPerson: function() {
this.$.messageFinder.call({
query: {
where: [{
prop: "conversations",
op: "=",
val: this.thread._id
}],
select: [
"username"
]
}
});
},
gotMessage: function(inSender, inResponse) {
this.addBlockBuddy(inResponse.results[0]);
},
addBlockBuddy: function(data) {
enyo.log("BlockPersonService::addBlockBuddy::thread: ",this.thread);
this.$.dbAdder.call({
objects: [{
_kind: enyo.application.accountService.getImCommandKind(this.thread.replyService),
command: "blockBuddy",
params: { block: true },
handler: "transport",
targetUsername: this.thread.replyAddress,
fromUsername: data.username,
serviceName: this.thread.replyService
}]
});
}
});
@@ -0,0 +1,145 @@
/*globals enyo */
enyo.kind({
name: "ConversationItem",
kind: "enyo.SwipeableItem",
confirmCaption: $L("Delete"),
layoutKind: "enyo.HFlexLayout",
align: "start",
published: {
message: ""
},
events: {
onError: ""
},
components: [
{name: "imageContainer", className:"conversationContactImage", components: [
{className: "contact-image-border"},
{name: "contactImage", kind: "Image", className: "contact-image"}
]},
{name: "messageContainer", flex: 1, components:[
{name: "message", components:[
{name: "messageText", allowHtml:true},
{layoutKind: "HLayout", components:[
{name: "messageTime", className: "message-time"},
{name: "errorIcon", kind: "Image", src: "images/header-warning-icon.png", className:"erroricon", onclick: "showError"}
]} ,
{name: "invitationButtons", layoutKind: "HLayout", className:"accept-decline-box", components: [
{name: "declineButton", kind: "IconButton", className: "enyo-button-negative", icon: "images/icon-decline.png", onclick: "declinedBuddy"},
{name: "acceptButton", kind: "IconButton", className: "enyo-button-affirmative", icon: "images/icon-accept.png", onclick: "acceptedBuddy"}
]}
]}
]},
{name: "inviteService", kind: "InviteResponseService"}
],
create: function() {
this.inherited(arguments);
this.messageChanged();
this.addClass("chat-balloon");
},
messageChanged: function() {
this.updateMessageText(this.message, this.message.folder !== enyo.messaging.message.FOLDERS.OUTBOX && this.message.folder !== enyo.messaging.message.FOLDERS.INBOX);
this.updateContactImage(this.message.personImage);
this.updateSentReceived(this.message.folder);
this.updateTime(this.message.localTimestamp);
this.updateMessageStatus(this.message.status, this.message.errorCategory);
this.updateInvite(this.message);
this.updatePriority(this.message);
},
updatePriority: function(message){
// 0 = normal priority
// 1 = interactive
// 2 = urgent
// 3 = emergency
// 4 = low priority
if (message.priority && (message.priority === 2 || message.priority === 3)) {
this.$.message.addClass("high-priority");
}
},
updateMessageStatus: function(inStatus, errorCategory){
if (inStatus !== "successful") {
this.$.message.setClassName("enyo-item chat-balloon-error");
}
if(errorCategory && (inStatus === enyo.messaging.message.MESSAGE_STATUS.FAILED || inStatus === enyo.messaging.message.MESSAGE_STATUS.UNDELIVERABLE)){
this.$.errorIcon.canGenerate = true;
this.$.errorIcon.show();
}
else{
this.$.errorIcon.canGenerate = false;
}
},
updateMessageText: function(inMessage, skipTextIndexer) {
var inText = inMessage.messageText || "";
if (enyo.messaging.message.isMMSMessage(inMessage)) {
inText = enyo.messaging.message.getMMSDisplayMessage();
} else if (inMessage.folder === enyo.messaging.message.FOLDERS.INBOX) {
inText = inText.replace(/\r|\n|\\r|\\n/g, "<br>");
} else if (inMessage.folder === enyo.messaging.message.FOLDERS.OUTBOX) {
// outgoing message needs to be sanitized since the incoming ones
// are already sanitized before they are written into database.
inText = enyo.string.escapeHtml(inText);
}
if (!skipTextIndexer) {
inText = enyo.string.runTextIndexer(inText);
}
this.$.messageText.setContent(inText);
},
updateContactImage: function(personImage) {
this.$.contactImage.setAttribute("src", personImage);
},
updateTime: function(localTimestamp) {
this.$.messageTime.setContent(this.formatTime(new Date(localTimestamp)));
},
formatTime: function(date){
if (!date) {
return "";
}
return Utils.formatShortTime(date);
},
updateSentReceived: function(inFolder) {
if (inFolder === enyo.messaging.message.FOLDERS.INBOX) {
this.$.message.setClassName("enyo-item chat-balloon-received");
this.$.imageContainer.canGenerate = true;
this.$.imageContainer.show();
} else if(inFolder === enyo.messaging.message.FOLDERS.OUTBOX){
this.$.message.setClassName("enyo-item chat-balloon-sent");
this.$.imageContainer.canGenerate = false;
} else {
this.$.message.setClassName("enyo-item chat-balloon-system");
this.$.imageContainer.canGenerate = false;
}
},
updateInvite: function(message) {
var showInvite = this.message._kind === "com.palm.iminvitation:1" && this.message.accepted === "pending";
// update invite buttons
this.updateInviteButtons(showInvite);
// update style
if (showInvite) {
this.$.message.setClassName("enyo-item chat-balloon-error");
this.$.imageContainer.canGenerate = false;
}
},
updateInviteButtons: function(show) {
this.$.invitationButtons.canGenerate = show;
this.$.invitationButtons.setShowing (show);
},
showError: function(inSender, inEvent){
this.doError(this.message);
return true;
},
acceptedBuddy: function(inSender, inEvent) {
this.setResponseInvitation(true);
return true;
},
declinedBuddy: function(inSender, inEvent) {
this.setResponseInvitation(false);
return true;
},
setResponseInvitation: function(accepted) {
this.$.inviteService.responseToInvite(this.message, accepted);
}
});
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,51 @@
enyo.kind({
name: "ConversationService",
kind: enyo.Service,
requestKind: "ConversationRequest",
events: {
onWatch: ""
}});
enyo.kind({
name: "ConversationRequest",
kind: enyo.Request,
events: {
onWatch: "doWatch"
},
components: [
{kind: "DbService", onFailure: "fail", components: [
{name: "messages", dbKind: "com.palm.message:1", method: "find", onSuccess: "gotMessages", subscribe: true, onWatch: "doWatch"/*, resubscribe: true, reCallWatches: true*/}
]}/*,
{name: "mockMessages", kind: "MockDb", dbKind: "com.palm.message:1", method: "find", onSuccess: "gotMessages", onFailure: "fail", onWatch: "doWatch"}*/
],
create: function() {
this.inherited(arguments);
},
/*
initComponents: function() {
this.inherited(arguments);
if (!window.PalmSystem) {
this.$.messages = this.$.mockMessages;
}
},
*/
finish: function() {
// don't destroy automatically, so we can see watches
},
call: function() {
this.$.messages.cancel();
this.$.messages.call(this.params);
},
reply: function() {
this.receive(this.data);
},
fail: function(inSender, inResponse) {
enyo.log(inResponse);
this.reply();
},
gotMessages: function(inSender, inResponse) {
this.data = inResponse;
//update list as soon as got data
this.reply();
}
});
@@ -0,0 +1,70 @@
enyo.kind({
name: "InviteResponseService",
kind: "enyo.Component",
components: [
{kind: "DbService", onFailure: "faile", components: [
{dbKind: "com.palm.imcommand:1", components: [
{name: "commandMerger", method: "merge", onSuccess: "mergedCommand"}
]},
{dbKind: "com.palm.iminvitation:1", components: [
{name: "inviteMerger", method: "merge", onSuccess: "mergedInvite"}
]},
{dbKind: "com.palm.immessage:1", components: [
{name: "responseAdder", method: "put"}
]}
]}
],
responseToInvite: function(invite, accepted) {
this.invite = invite;
this.accepted = accepted;
// update IMCommand so that transport will process the accepted/rejected buddy invitation
this.$.commandMerger.call({
objects: [{
_id: this.invite.commandId,
params: { "accept": this.accepted },
handler: "transport"
}]
});
},
mergedCommand: function() {
// update ImInvitation so that messaging app can update the conversation item
this.$.inviteMerger.call({
objects: [{
_id: this.invite._id,
accepted: this.accepted ? "accepted" : "rejected"
}]
});
},
mergedInvite: function(inSender, inResponse) {
var messageText;
var from = this.invite.from;
if (!from.name) {
from.name = "";
} else {
from.name = from.name + " ";;
}
var template;
if (this.accepted) {
template = new enyo.g11n.Template($L("Accepted Invitation from #{name}#{address}"));
} else {
template = new enyo.g11n.Template($L("Rejected Invitation from #{name}#{address}"));
}
messageText = template.evaluate({name: from.name, address: from.addr});
// also create a acceptance/rejection message back to the person
this.$.responseAdder.call({
objects: [{
_kind: "com.palm.immessage:1",
folder: "outbox",
status: "successful",
"messageText": messageText,
"serviceName": this.invite.serviceName,
"localTimestamp": Date.now(),
"from": this.invite.to[0],
"to": [this.invite.from],
"imType": "system"
}]
});
}
});
@@ -0,0 +1,98 @@
enyo.kind({
name: "TransportSelector",
kind: enyo.CustomListSelector,
events: {
onPhoneClick: "",
onVideoClick: ""
},
itemKind: "TransportMenuCheckItem",
setItemProps: function(inItem) {
this.item.setCaption(inItem.caption);
this.item.setPhone(inItem.phone);
this.item.setVideo(inItem.video);
this.item.setSecondaryLabel(inItem.secondaryLabel);
},
makePopup: function() {
this.popup = this.createComponent({
kind: "TransportPopupSelect",
onSelect: "popupSelect",
onBeforeOpen: "popupBeforeOpen",
defaultKind: this.itemKind,
onPhoneClick: "doPhoneClick",
onVideoClick: "doVideoClick"
});
},
closePopup: function() {
this.popup.close();
}
});
enyo.kind({
name: "TransportPopupSelect",
kind: enyo.PopupSelect,
events: {
onPhoneClick: "",
onVideoClick: ""
},
itemsChanged: function() {
this.selected = null;
this.inherited(arguments);
},
phoneClick: function(inSender) {
return this.doPhoneClick(inSender.replyAddress, inSender.serviceName);
},
videoClick: function(inSender) {
return this.doVideoClick(inSender.replyAddress, inSender.serviceName);
}
});
enyo.kind({
name: "TransportMenuCheckItem",
kind: enyo.MenuCheckItem,
className: "transport-picker",
published: {
phone: "",
video: "",
secondaryLabel:""
},
events: {
onPhoneClick: "phoneClick",
onVideoClick: "videoClick"
},
chrome: [
{name: "item", kind: enyo.Item, tapHighlight: true, align: "center", className: "enyo-menuitem",
layoutKind: "HFlexLayout", onclick: "itemClick", components: [
{name: "phone", kind: "IconButton", onclick: "doPhoneClick", icon: "images/phone-icon.png"/*, className: "transport-picker-dial-icon"*/},
{name: "video", kind: "IconButton", onclick: "doVideoClick", icon: "images/video-icon2.png"/*, className: "transport-picker-video-icon"*/},
{name: "icon", kind: "Image", className: "enyo-menuitem-icon"},
{flex: 1, components: [
{name: "caption", flex: 1, className: "enyo-menucheckitem-caption"},
{name: "secondaryLabel", flex: 1, className:"transport-picker-phoneLabel"}]},
{name: "arrow", kind: enyo.CustomButton, toggling: true, showing: false, className: "enyo-menuitem-arrow"}
]}
],
create: function() {
this.inherited(arguments);
this.phoneChanged();
this.videoChanged();
this.secondaryLabelChanged();
},
setCaptionControl: function() {
this.captionControl = this.$.caption;
},
secondaryLabelChanged: function() {
this.$.secondaryLabel.setContent(this.secondaryLabel);
if (this.secondaryLabel && this.secondaryLabel.length > 0) {
this.$.secondaryLabel.setShowing(true);
this.$.secondaryLabel.setContent(this.secondaryLabel.toUpperCase());
}
else{
this.$.secondaryLabel.setShowing(false);
}
},
phoneChanged: function() {
this.$.phone.setShowing(!this.hideIcon && this.phone);
},
videoChanged: function() {
this.$.video.setShowing(!this.hideIcon && this.video);
}
});
@@ -0,0 +1,456 @@
/* Copyright 2010 Palm, Inc. All rights reserved. */
transportPicker = {
_selectedTransport: -1,
_transports: [{disabled: true, caption:$L("Send message to...")}],
_buddyStatusHash: {},
availabilityIcons: [
"images/status-available.png",
"images/status-available.png",
"images/status-away.png",
"images/status-offline.png",
"images/status-offline.png",
"images/status-offline.png",
"images/status-offline.png"
],
getSelectedTransport: function() {
enyo.log("transportPicker::getSelectedTransport::this._selectedTransport:", this._selectedTransport);
if (this._selectedTransport < 1 || this._selectedTransport >= this._transports.length) {
enyo.error("****************************** Invalid transport selection=", this._selectedTransport);
//enyo.error("Available options are: %j", this._transports);
return {};
}
return this._transports[this._selectedTransport];
},
getTransports: function() {
return this._transports;
},
setTransportsByChatThread: function(chatThread, listSelectorObj) {
this._transportSelectorObj = listSelectorObj;
this._selectedTransport = -1;
var displayName, secondaryLabel;
if (!chatThread.replyAddress) {
displayName = $L("Unknown");
} else if (enyo.messaging.utils.isTextMessage(chatThread.replyService)) {
var phonefmt = new enyo.g11n.PhoneFmt({style: "default"});
displayName = phonefmt.format(new enyo.g11n.PhoneNumber(chatThread.replyAddress));
secondaryLabel = $L("Text"); //assume it is Mobile since the number isn't associated with a contact
} else {
displayName = chatThread.replyAddress; //enyo.string.escapeHtml(chatThread.replyAddress);
}
var account = enyo.application.accountService.getAccount(chatThread.replyService);
if (account) {
secondaryLabel = account.loc_shortName;
}
else{
enyo.warn("transportPicker::setTransposByChatThread::can't find account based on replyService:", chatThread.replyService);
}
var transportCommand = this.getTransportId(chatThread.replyAddress, chatThread.replyService);
this._transports = [
{disabled: true, caption:$L("Send message to...")},
{kind: "Divider", caption: enyo.messaging.utils.isTextMessage(chatThread.replyService)? $L("SMS ACCOUNT"): secondaryLabel},
{
label: secondaryLabel,
caption: displayName, //enyo.string.escapeHtml(displayName),
displayName: displayName, //enyo.string.escapeHtml(displayName),
value: transportCommand,//displayName,
replyAddress: chatThread.replyAddress,
normalizedValue: chatThread.normalizedAddress,
serviceName: chatThread.replyService,
secondaryLabel: secondaryLabel,
//availability: enyo.messaging.im.availability.NO_PRESENCE,
//className: "status-offline",
icon: this.availabilityIcons[enyo.messaging.im.availability.NO_PRESENCE],
account: account,
id: transportCommand
}];
this._selectedTransport = 2;
listSelectorObj.setItems(this._transports);
listSelectorObj.setValue(this._transports[this._selectedTransport].id);
if (this._transports[this._selectedTransport].label !== undefined) {
listSelectorObj.setLabel(this._transports[this._selectedTransport].label);
}
else{//edge case: accout was sent from is removed.
listSelectorObj.setLabel((this._transports[this._selectedTransport].serviceName).slice(5));
}
},
setTransportsByPerson: function(headerStatusObj, listSelectorObj, person, chatThread, selectIMTransport, selectIMBuddyStatus ) {
this._headerStatusObj = headerStatusObj;
this._transportSelectorObj = listSelectorObj;
//TODO: if a list of transports already exists and a selection is set, we need to remember that selection
//and make sure it is still selected after the update
this._transports = [{disabled: true, caption:$L("Send message to...")}];
this._selectedTransport = -1;
var i, addr, transport, headerName;
var validAccountTypes = enyo.application.accountService.getMyAccountTypesHash();
if(!validAccountTypes){
enyo.warn("transportPicker::setTransportsByPerson::can't find validAccountTypes");
setTimeout(this.setTransportsByPerson.bind(this,headerStatusObj, listSelectorObj, person, chatThread, selectIMTransport, selectIMBuddyStatus ), 10);
return;
}
//phone numbers
if (validAccountTypes.sms === undefined) {
enyo.warn("TransportPicker skipping phone numbers because IMAccounts.getMyAccountTypesHash doesn't contain an SMS entry.");
} else {
if (person.phoneNumbers.length > 0) {
this._transports.push({
kind: "Divider",
caption: $L("SMS ACCOUNT")
});
}
for (i = 0; i < person.phoneNumbers.length; ++i) {
addr = person.phoneNumbers[i];
if (!addr.value) {
enyo.error("setTransportsByPerson ignoring person because phone value property is missing");
} else {
this._transports.push({
label: $L("Text"),//enyo.messaging.utils.phoneNumberLabels[addr.type] == undefined ? $L("Text"): enyo.messaging.utils.phoneNumberLabels[addr.type],//ContactsLib.PhoneNumber.Labels.getLabel(addr.type),
caption: enyo.messaging.utils.formatAddress(addr.value, addr.type),
displayName: enyo.messaging.utils.formatAddress(addr.value, addr.type),
value: this.getTransportId(addr.value, "sms"),//addr.value,
replyAddress: addr.value,
normalizedValue: enyo.messaging.utils.normalizeAddress(addr.value, addr.type),
serviceName: "sms", //TODO get this from IMAccounts
secondaryLabel: ContactsLib.PhoneNumber.Labels.getLabel(addr.type),
//availability: enyo.messaging..Availability.NO_PRESENCE,
//className: "status-offline",
icon: this.availabilityIcons[enyo.messaging.im.availability.NO_PRESENCE],
account: {},//TODO add "account" property
id: this.getTransportId(addr.value, "sms")
});
}
}
}
if (person.ims.length > 0 && person.contactIds.length > 0) {
//need build IM transport in ConversationList::gotContacts
}
else {
this._headerStatusObj.setClassName("status status-no-presence");
this.selectBestTransport(chatThread.replyService, chatThread.replyAddress, chatThread.normalizedAddress, listSelectorObj, selectIMTransport, selectIMBuddyStatus);
}
},
setTransportsByContacts: function(headerStatusObj, listSelectorObj, contacts, chatThread, selectIMTransport, selectIMBuddyStatus/*, allowVideo*/){
enyo.log("transportpicker::setTransportsByContacts contacts.length:", contacts.length);
//todo: need test out
this._headerStatusObj = headerStatusObj;
var i, j, contact;
var validAccountTypes = enyo.application.accountService.getMyAccountTypesHash();
if(!validAccountTypes){
enyo.warn("transportpicker::setTransportsByContacts:: can't find validAccountTypes");
setTimeout(this.setTransportsByContacts.bind(this,headerStatusObj, listSelectorObj, contacts, chatThread, selectIMTransport, selectIMBuddyStatus/*, allowVideo*/), 10);
return;
}
for (i = 0; i < contacts.length; i++) {
contact = contacts[i];
if(contact.imBuddy){
for (j = 0; contact.ims && j < contact.ims.length; j++) {
addr = contact.ims[j];
if (!addr.value) {
enyo.error("transportpicker::setTransportsByContacts ignoring contact because IM value property is missing");
}
else if (validAccountTypes[contact.accountId] === undefined) {
enyo.error("setTransportsByContacts ignoring service=", addr.type," because we don't have an account for it.");
}
else {
var handled = false;
var serviceName = addr.type;
if (serviceName == this._transports[this._transports.length - 1].serviceName /*&& this._transports[this._transports.length - 1].account.loc_shortName === validAccountTypes[contact.accountId].loc_shortName*/) {
//no divider needed for same type
this._transports.push({
label: validAccountTypes[contact.accountId].loc_shortName,
caption: addr.value,//validAccountTypes[contact.accountId].loc_shortName,//addr.value,
displayName: addr.value, //enyo.string.escapeHtml(addr.value),
value: this.getTransportId(addr.value, serviceName,contact.accountId),//addr.value,
replyAddress: addr.value,
normalizedValue: enyo.messaging.utils.normalizeAddress(addr.value, addr.type),
serviceName: serviceName,
phone: serviceName === "type_skype" ? true : undefined,
video: serviceName === "type_skype"/* && allowVideo*/ ? true : undefined,
//availability: this.getBuddyAvailability(serviceName, addr.value),
//className: "status-offline",
icon: this.availabilityIcons[enyo.messaging.im.availability.NO_PRESENCE],
account: validAccountTypes[contact.accountId],
id: this.getTransportId(addr.value, serviceName, contact.accountId)
});
}
else {
var k;
for (k = 0; k < this._transports.length; k++) {
if (serviceName === this._transports[k].serviceName /*&& this._transports[this._transports.length - 1].account.loc_shortName === validAccountTypes[contact.accountId].loc_shortName*/) {
//insert after this one
this._transports.splice(k, 0, {
label: validAccountTypes[contact.accountId].loc_shortName,
caption: addr.value,//validAccountTypes[contact.accountId].loc_shortName,//addr.value,
displayName: addr.value, //enyo.string.escapeHtml(addr.value),
value: this.getTransportId(addr.value, serviceName, contact.accountId),//addr.value,
replyAddress: addr.value,
normalizedValue: enyo.messaging.utils.normalizeAddress(addr.value, addr.type),
serviceName: serviceName,
phone: serviceName === "type_skype" ? true : undefined,
video: serviceName === "type_skype" /*&& allowVideo */? true : undefined,
//availability: this.getBuddyAvailability(serviceName, addr.value),
//className: "status-offline",
icon: this.availabilityIcons[enyo.messaging.im.availability.NO_PRESENCE],
account: validAccountTypes[contact.accountId],
id: this.getTransportId(addr.value, serviceName, contact.accountId)
});
handled = true;
break;
}
}
if (!handled) { //insert divider and transport
this._transports.push({
kind: "Divider",
caption: validAccountTypes[contact.accountId].loc_shortName
});
this._transports.push({
label: validAccountTypes[contact.accountId].loc_shortName,
caption: addr.value,//validAccountTypes[contact.accountId].loc_shortName,//addr.value,
displayName: addr.value, //enyo.string.escapeHtml(addr.value),
value: this.getTransportId(addr.value, serviceName, contact.accountId),//addr.value,
replyAddress: addr.value,
normalizedValue: enyo.messaging.utils.normalizeAddress(addr.value, addr.type),
serviceName: serviceName,
phone: serviceName === "type_skype" ? true : undefined,
video: serviceName === "type_skype" /*&& allowVideo */? true : undefined,
//availability: this.getBuddyAvailability(serviceName, addr.value),
//className: "status-offline",
icon: this.availabilityIcons[enyo.messaging.im.availability.NO_PRESENCE],
account: validAccountTypes[contact.accountId],
id: this.getTransportId(addr.value, serviceName, contact.accountId)
});
}
}
}
}
}
}
if (this._transports.length > 1) {
//todo: for im only, should not check this._transports.length, instead should be buddy's length, can also move out of transportPicker or in for obj?
enyo.warn("transportPicker::setTransportsByContacts::call selectBestTransport");
this.selectBestTransport(chatThread.replyService, chatThread.replyAddress, chatThread.normalizedAddress, listSelectorObj, selectIMTransport, selectIMBuddyStatus);
}
else{
this.setTransportsByChatThread(chatThread, listSelectorObj);
}
},
selectTransportById: function(id) {
//enyo.log("TransportPickerModel.selectTransportById id=", id);
if (this._transports.length > 0) {
var transport = undefined;
var found = false;
var i = 0;
while (found !== true && i < this._transports.length) {
transport = this._transports[i];
if (transport.id === id) {
enyo.log("TransportPickerModel.selectTransportById transport found ", transport.id);
found = true;
} else {
i++;
}
}
if (found === true && this._selectedTransport !== i) {
//enyo.log("TransportPickerModel.selectTransportById old=", this._selectedTransport,", new=" ,i);
this._selectedTransport = i;
}
}
},
//TODO: selectBestTransport needs a healthy cleanup to work better with bedlam
// TODO: this could be further improved to choose an online transport over one that is offline
// when no exact matches are found
// @param selectIMTransport if set to true, the most online IM address in the transport will be selected
selectBestTransport: function(serviceName, chatAddress, normalizedChatAddress, listSelectorObj, selectIMTransport, selectIMBuddyStatus) {
enyo.log("transportpicker::selectBestTransport:serviceName:", serviceName, " chatAddress:", chatAddress, " normalizedChatAddress:", normalizedChatAddress, " selectIMTransport:", selectIMTransport);
var selectedTransport = -1;
var data = this._transports;
if (data.length > 0) {
var i, transport;
var serviceIsSms = enyo.messaging.utils.isTextMessage(serviceName);
if (serviceIsSms && serviceName !== "sms") {
serviceName = "sms";
}
if (!normalizedChatAddress) {
normalizedChatAddress = enyo.messaging.utils.normalizeAddress(chatAddress, serviceName);
//enyo.warn("Normalized chat address not supplied, estimating as %s", normalizedChatAddress);
}
// Special case where the chatview was launched from the buddy list, but the latest
// chat over a phone number. HI wants this to switch to a IM transport so choose the
// IM transport with the best availability
if (selectIMTransport === true) {
for (i = 0; i < data.length; i++) {
transport = data[i];
if (!transport.kind && !transport.disabled) {
if (transport.serviceName === selectIMBuddyStatus.serviceName && transport.value === selectIMBuddyStatus.username) {
//enyo.log("match found %i", i);
selectedTransport = i;
break;
}
}
}
}
if (selectedTransport === -1) {
// selected transport is a phone number
if (serviceIsSms === true) {
//enyo.log("TransportPickerModel.selectBestTransport: looking for phone number");
for (i = 0; i < data.length; i++) {
transport = data[i];
if (!transport.kind && !transport.disabled) {
if (enyo.messaging.utils.isTextMessage(transport.serviceName) === true) {
// The transport may have a longer normalized phone number (eg, including area code)
// so just see if the two strings match up to the length of normalizedChatAddress.
if (transport.normalizedValue.indexOf(normalizedChatAddress) === 0) {
//enyo.log("match found %i", i);
selectedTransport = i;
break;
}
}
}
}
}
else { // transport is IM
//enyo.log("TransportPickerModel.selectBestTransport: looking for IM addr");
for (i = 0; i < data.length; i++) {
transport = data[i];
if (transport.serviceName === serviceName && transport.normalizedValue === normalizedChatAddress) {
selectedTransport = i;
//enyo.log("TransportPicker.selectBestTransport: found IM addr selectedTransport:",selectedTransport," this._selectedTransport:",this._selectedTransport);
break;
}
}
}
}
// if there was no match, use a transport of the same type
// else: just use the first transport as a last resort
if (selectedTransport === -1) {
enyo.warn("Unable to match transport that the chat was launched with. Choosing the best one.");
var foundMatch = false;
var backupChoice = -1;
for (i = 0; i < data.length && !foundMatch; i++) {
transport = data[i];
if (!transport.kind && !transport.disabled) {
// match if both are phone numbers (serviceName == sms) or match where the serviceNames are the same (same IM transport)
if (serviceName === transport.serviceName) {
//enyo.log("match found %i", i);
selectedTransport = i;
foundMatch = true;
}
// backup match: both are an IM transport. We will use this if we cannot match another IM transport with the same serviceName
if (serviceIsSms === false && enyo.messaging.utils.isTextMessage(transport.serviceName) === false) {
backupChoice = i;
}
}
}
if (selectedTransport === -1) {
if (backupChoice !== -1) {
//enyo.log("No match found. Using backup IM choice %i", i);
selectedTransport = backupChoice; // use the backup if we have one
} else {
//enyo.log("No match found. Selecting first item in list");
selectedTransport = 2; // only other option is to use the first transport in the array
}
}
}
} else {
enyo.error("transportpicker::selectBestTransport::Error, the transport list is empty.");
selectedTransport = 0;
this._transports = [{
username: "Error",
serviceName: "error",
availability: enyo.messaging.im.availability.NO_PRESENCE
}];
}
if (selectedTransport !== -1 && this._selectedTransport !== selectedTransport) {
this._selectedTransport = selectedTransport;
enyo.log("--------transportPicker::selectBestTransports::this._transports:", this._transports[this._selectedTransport]);
listSelectorObj.setItems(this._transports);
if (this._transports[this._selectedTransport]) {
listSelectorObj.setValue(this._transports[this._selectedTransport].id);
listSelectorObj.setLabel(this._transports[this._selectedTransport].label);
}
}
},
_getBuddyHashKey: function(serviceName, address) {
return serviceName+(address.toLowerCase());
},
getBuddyAvailability: function(serviceName, address) {
var availability = enyo.messaging.im.availability.NO_PRESENCE;
if (enyo.messaging.utils.isTextMessage(serviceName) === false) {
var buddy = this._buddyStatusHash[this._getBuddyHashKey(serviceName, address)];
if (buddy === undefined || buddy.availability === undefined) {
availability = enyo.messaging.im.availability.OFFLINE;
} else {
availability = buddy.availability;
}
}
return availability;
},
getTransportId: function(addr, serviceName, accountId) {
var id;
if (enyo.messaging.utils.isTextMessage(serviceName)) {
id = "phone_"+addr;
} else {
id = "IM_"+addr+":"+serviceName;
}
return (accountId ? id+accountId: id);
},
gotStatus: function(inResponse, statusChangedFn){
this._buddyStatusHash = {};
if(inResponse.returnValue && inResponse.results && inResponse.results.length > 0){
var i, buddy;
var transportUpdateFn = function(transport) {
if (transport.serviceName === buddy.serviceName && transport.caption === buddy.username) {
transport.availability = this.getBuddyAvailability(transport.serviceName, transport.caption);
transport.icon = this.availabilityIcons[transport.availability];
}
}.bind(this);
for(i = 0; i < inResponse.results.length; ++i) {
buddy = inResponse.results[i];
this._buddyStatusHash[this._getBuddyHashKey(buddy.serviceName, buddy.username)] = buddy;
// Whenever a buddy changes, update the transport for that buddy (copy over availability)
this._transports.forEach(transportUpdateFn);
var selection = this._transports[this._selectedTransport];
if( selection && selection.serviceName === buddy.serviceName &&
selection.caption === buddy.username) {
this._headerStatusObj.setClassName("status status-"+enyo.messaging.im.buddyAvailabilities[this.getBuddyAvailability(buddy.serviceName, buddy.username)]);
//update buddy status in converstaion list
statusChangedFn({
serviceName: buddy.serviceName,
username: buddy.username,
availability: this.getBuddyAvailability(buddy.serviceName, buddy.username),
status: buddy.status,
_kind: buddy._kind
});
}
}
this._transportSelectorObj.setItems(this._transports);
}
else {
enyo.error("transportPicker::gotStatus:Failed to get status:payload: ", inResponse.results);
this._headerStatusObj.setClassName("status status-no-presence");
}
}
};
@@ -0,0 +1,65 @@
enyo.kind({
name: "BannerThrottler",
kind: enyo.Component,
newMessages: [],
bannerWidgetIds: [],
lastShownTimestamp: 0,
SHOW_BANNER_INTERVAL: 2000, // two seconds
create: function(db) {
this.inherited(arguments);
this.dashboard = db;
},
addNewMessages: function(messages) {
this.newMessages = this.newMessages.concat(messages);
if (Date.now() - this.lastShownTimestamp > this.SHOW_BANNER_INTERVAL * 2) {
// show the banner immediately if the time for last banner shown is greater than twice of the pre-defined shown interval
this.showBanner();
} else if (!this.showBannerTimer) {
this.showBannerTimer = setTimeout(function() {
this.showBannerTimer = undefined;
this.showBanner();
}.bind(this), this.SHOW_BANNER_INTERVAL);
}
},
showBanner: function() {
this.lastShownTimestamp = Date.now();
//enyo.log("this.newMessages: ", this.newMessages);
if (this.newMessages.length > 0) {
var messages = this.newMessages;
enyo.log("Showing banner for ", messages.length, " new messages received");
this.newMessages = [];
var bannerMessage = this.getBannerMessagePrefix(messages) + this.getBannerMessage(messages[messages.length - 1]);
// play notification sound and then put up banner
this.dashboard.playSoundNotification();
if (this.bannerWidgetIds.length > 10) {
this.bannerWidgetIds = [];
}
this.bannerWidgetIds.push(enyo.windows.addBannerMessage(bannerMessage, "{}", "images/notification-small.png"));
}
},
getBannerMessagePrefix: function(messages) {
return messages.length > 1 ? ("(" + messages.length + "/" + messages.length + ") ") : "";
},
getBannerMessage: function(latestMessage) {
enyo.log("Latest message for banner: ", latestMessage);
return this.dashboard.getDisplayName(latestMessage) + ": " + this.dashboard.getDisplayText(latestMessage);
},
clearBannerMessages: function(filter) {
var ids = this.bannerWidgetIds;
this.bannerWidgetIds = [];
// TODO: there was a framework issue that prevented the following codes to work, so this function is currently not used.
for (var i = 0; i < ids.length; i++) {
enyo.log("Clearing banner with id: ", ids[i]);
try {
enyo.windows.removeBannerMessage(ids[i]);
} catch (e) {
enyo.warn("Unable to remove banner messages");
}
}
}
});
@@ -0,0 +1,80 @@
enyo.kind({
name: "Class0AlertManager",
kind: "enyo.Component",
dialogName: "messaging-class0Alert-stage",
components: [
{kind: "DbService", onFailure: "fail", components: [
{dbKind: "com.palm.carrierdb.settings.current:1", components: [
{name: "carrierFinder", method: "find", onSuccess: "gotCarriers", subscribe: true, resubscribe: true, reCallWatches: true}
]}
]},
{name: "messageAdder", kind: "DbService", dbKind: "com.palm.message:1", method: "put"}
],
create: function() {
this.inherited(arguments);
this.$.carrierFinder.call();
},
gotCarriers: function(inSender, inResponse) {
var carrier;
enyo.log("Class0AlertManager.gotCarriers(), got carrier settings: ", inResponse);
if (inResponse && inResponse.results && inResponse.results.length > 0) {
this.carrierSettings = inResponse.results[0];
}
},
fail: function(inSender, inResponse) {
enyo.error("Failed to get carrier db settings: ", inResponse);
},
handleNotification: function(params) {
enyo.log("Class0Manager.handleNotification(), parmas: ", params);
var dialog = enyo.windows.fetchWindow(this.dialogName);
var layer = this.getAlertLayer(params);
//show banner
this.putUpBanner(layer);
// show alert dialog
if (!dialog) {
enyo.log("Class0Manager.handleNotification(), creating class0 dialog with: ", layer);
enyo.windows.openPopup("app/dashboards/class0/class0.html", this.dialogName, layer, undefined, "135px");
}
else {
enyo.log("Class0Manager.handleNotification(), activating class0 dialog with: ", layer);
enyo.windows.activate(undefined, this.dialogName, layer);
}
},
getAlertLayer: function(params) {
var layer = {};
layer.message = params.message;
layer.text = enyo.string.removeHtml(layer.message.messageText);
layer.message.carrierName = "carrier";
if (this.carrierSettings) {
layer.message.carrierName = this.carrierSettings.qOperatorLongName;
}
var template = new enyo.g11n.Template($L("Message from #{name}"));
layer.title = layer.message.alertTitle = template.evaluate({name: layer.message.carrierName});
return layer;
},
putUpBanner: function(layer) {
// put up banner
var bannerMessage = layer.title + ": " + enyo.string.removeHtml(layer.text);
enyo.windows.addBannerMessage(bannerMessage, "{}", "images/notification-small.png", "notifications");
},
addClass0Message: function(message) {
// add message to MojoDB
// set dbkind to message
message._kind = "com.palm.smsmessage:1";
this.$.messageAdder.call({
objects: [message]
});
},
closeDialog: function() {
var dialog = enyo.windows.fetchWindow(this.dialogName);
if (dialog) {
dialog.close();
}
}
});
@@ -0,0 +1,446 @@
enyo.kind({
name: "DashboardManager",
kind: enyo.Component,
published: {
appDeactivated: true,
filter: undefined
},
components: [
{kind: "DbService", onFailure: "fail", components: [
{dbKind: "com.palm.message:1", components: [
{name: "newMessagesFinder", method: "find", onSuccess: "gotMessages",subscribe: true, resubscribe: true, reCallWatches: true}
]},
{dbKind: "com.palm.chatthread:1", components: [
{name: "threads", method: "get", onSuccess: "gotThreads"}
]}
]},
//{name: "displayOn", kind:"PalmService", service:"palm://com.palm.display/control/", onSuccess: "", onFailure: ""},
{name: "displayState", kind: "PalmService", service: "palm://com.palm.display/control/", method: "status", onSuccess: "displayUpdate", subscribe: true}
],
create: function() {
this.inherited(arguments);
this.init();
},
init: function() {
this.cutoffRevSet = 0; // 'readRevSet' is a more reliable reference to indicate the order that the message is received on the phone. We can't use 'localTimestamp' since multiple messages can have the same local timestamp.
this.lastRev = 0;
this.filter = {filterAll: false};
this.dashboardUtil = new MessageDashboardUtil();
this.bannerThrottler = new BannerThrottler(this);
// listening to display on/off changes
this.$.displayState.call();
// try to query the largest readRevSet for all inbox messages
this.queryMessages();
// register for prefs changes
enyo.application.prefsHandler.register(this, this.prefsUpdated.bind(this));
},
queryMessages: function() {
enyo.log("*********** In ", this.name, "DashboardManager, querying new messages in MasessageDashboardManager");
this.$.newMessagesFinder.cancel();
this.$.newMessagesFinder.call({
query: {
orderBy: "readRevSet",
desc: true,
limit: !this.isInited ? 1 : 500
}
});
},
gotMessages: function(inSender, inResponse) {
// TODO: need to handle the case that the result set has more records to follow
//enyo.log("*********************** In ", this.name, "DashboardManager, gotMessages: ", inResponse);
if (inResponse && inResponse.results.length > 0) {
this.processChanges(inResponse.results);
}
if (!this.isInited) {
this.isInited = true;
}
},
updateWatch: function() {
enyo.log("In ", this.name, "DashboardManager, update notification watch with revision: ", this.lastRev);
// var where = parseQuery("where folder='inbox' and _rev>" + this.lastRev).where;
var where = [{
prop: "folder",
op: "=",
val: "inbox"
}, {
prop: "_rev",
op: ">",
val: this.lastRev
}];
this.$.newMessagesFinder.cancel();
this.$.newMessagesFinder.call({
query: {
where: where,
orderBy: "_rev"
}
});
// reset this flag
this.shouldUpdateWatch = false;
},
processPendingMessages: function() {
var tids = [];
for (var i = 0; i < this.pendingMessages.length; i++) {
var msg = this.pendingMessages[i];
tids.push(msg.conversations[0]);
}
enyo.log("In ", this.name, "DashboardManager, looking up threads for new messages: ", tids);
this.findThreads(tids);
},
findThreads: function(tids) {
this.$.threads.call({
ids: tids
});
},
gotThreads: function(inSender, inResponse) {
var results = inResponse.results;
var peopleThreads = {};
for (var i = 0; i < results.length; i++) {
var thread = results[i];
peopleThreads[thread._id] = thread.displayName;
}
//enyo.log("In ", this.name, "DashboardManager, got threads ", results);
for (var j = 0; j < this.pendingMessages.length; j++) {
var msgThread = this.pendingMessages[j].conversations[0];
this.pendingMessages[j].displayName = peopleThreads[msgThread];
}
this.notify();
},
notify: function() {
// add dashboard layers into banner throttle so that throttle can batch the layers to show only one banner per specified interval
this.bannerThrottler.addNewMessages(this.pendingMessages);
var layers = this.getPendingLayers(this.pendingMessages);
// put new message layers into dashboard
this.putUpDashboard(layers);
// clean up pendingMessages cache
delete this.pendingMessages;
// update db watch
if (this.shouldUpdateWatch) {
this.updateWatch();
}
},
putUpDashboard: function(layers) {
enyo.log("In ", this.name, "DashboardManager, updating dashboard with new layers: ", layers);
this.updateDashboard(layers);
},
updateDashboard: function(layers) {
var db = this.dashboard;
if (!db && layers.length === 0) {
return;
}
if (!db) {
enyo.log("$$$$$$$$$$$$$$ In ", this.name, "DashboardManager, creating dashboard for ", this.name);
db = this.createComponent({name: "messaging-" + this.name + "-dashboard", kind:"enyo.Dashboard", smallIcon:"images/notification-small.png", onIconTap:"dashboardIconTap", onMessageTap:"dashboardMessageTap", onUserClose: "dashboardClose"});
this.dashboard = db;
}
this.dashboardUtil.addLayers(db, layers);
},
playSoundNotification: function() {
var options = this.getSoundOptions(this.prefs);
if (options && window.PalmSystem) {
window.PalmSystem.playSoundNotification(options.soundClass, options.soundPath);
}
},
dashboardIconTap: function(inSender, layer, event) {
//enyo.log("In ", this.name, "DashboardManager, icon is tapped");
var layers = this.dashboard.getLayers();
enyo.windows.activate("index.html", "messaging", (layers.length > 1 ? {} : {message:layers[0]._message}));
// clear & close dashboard
this.dashboard.setLayers([]);
this.dashboardClose(this);
},
dashboardMessageTap: function(inSender, layer, event) {
//enyo.log("In ", this.name, "DashboardManager, message text is tapped");
enyo.windows.activate("index.html", "messaging", {message:layer._message});
},
dashboardClose: function(inSender, event) {
//enyo.log("In ", this.name, "Dashboard is closed.");
delete this.dashboard;
},
filterChanged: function() {
this.clearDashboard(this.filter);
this.bannerThrottler.clearBannerMessages(this.filter);
},
clearDashboard: function(filter) {
var db = this.dashboard;
if (db) {
//enyo.log("In ", this.name, "DashboardManager, clearing dashboard with filter: ", filter);
var layers = [];
var currLayers = db.getLayers();
if (currLayers) {
for (var i = 0; i < currLayers.length; i++) {
if (!this.matchFilter(filter, currLayers[i]._message.conversations)) {
layers.push(currLayers[i]);
}
}
}
db.setLayers(layers);
}
},
removeThread : function(thread) {
//enyo.log("In ", this.name, "DashboardManager, removeThread - thread = ", thread);
var db = this.dashboard;
function threadInCoversations (conversations) {
var x = conversations.length;
while (x--) {
if (conversations[x] === thread) {
return true;
}
}
return false;
}
if (db) {
var layers = [];
var currLayers = db.getLayers();
if (currLayers) {
for (var i = 0; i < currLayers.length; i++) {
if (!threadInCoversations(currLayers[i]._message.conversations)) {
layers.push(currLayers[i]);
}
}
}
db.setLayers(layers);
}
},
accountRemoved : function(account) {
//enyo.log("In ", this.name, "DashboardManager, accountRemoved: ", account.username);
var db = this.dashboard;
function keepLayer(layer) {
if (layer._message.serviceName !== account.serviceName) {
return true;
}
if (layer._message.username === account.username) {
return false;
}
/**
* If target user name is not an email address, then we need to use the
* user name prefix of the email address from the IM account and compare
* it to the target user name. So 'bob.dole' should match 'bob.dole@company.com'
* but not 'bob@company.com'.
*/
var accountUsername = account.username.replace(/\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/, "");
return layer._message.username !== accountUsername;
}
if (db) {
var layers = [];
var currLayers = db.getLayers();
if (currLayers) {
for (var i = 0; i < currLayers.length; i++) {
if (keepLayer(currLayers[i])) {
layers.push(currLayers[i]);
}
}
}
db.setLayers(layers);
}
},
// unit tested functions
prefsUpdated: function(latestPrefs) {
this.prefs = latestPrefs;
//enyo.log("---------#### In ", this.name, "Dashboard, got preferences: ", this.prefs);
if (this.prefs && !this.prefs.enableNotification) {
this.clearDashboard();
}
},
appDeactivatedChanged: function() {
//enyo.log("In ", this.name, "DashboardManager, app deactivated changed: ", this.appDeactivated);
if (!this.appDeactivated) {
this.clearDashboard(this.filter);
this.bannerThrottler.clearBannerMessages(this.filter);
}
},
// maintains this.displayOff, used to determine if new message dashboards should be displayed.
displayUpdate: function (inSender, inResult) {
if (inResult.event) {
// Note, specifically checking Off and On so we ignore displayInactive,
// displayActive and displayDimmed
if (inResult.event === "displayOff") {
this.displayOff = true;
} else if (inResult.event === "displayOn" || inResult.event === "request") {
this.displayOff = false;
}
//enyo.log("displayUpdate event ", inResult.event);
}
},
processChanges: function(newMessages) {
this.pendingMessages = this.getPendingNewMessages(newMessages);
if (this.pendingMessages.length > 0) {
this.processPendingMessages();
} else if (this.shouldUpdateWatch) {
this.updateWatch();
}
},
isNewMessage: function(message) {
var isNew = message.folder === enyo.messaging.message.FOLDERS.INBOX;
isNew = isNew && (message.conversations && message.conversations.length > 0);
isNew = isNew && (message.readRevSet > this.cutoffRevSet);
isNew = isNew && (message.status === "successful" || message.status === "delayed"); // TODO: need to figure out whether we should show notification for messages with error
isNew = isNew && !message.errorCode;
isNew = isNew && (!message.flags || (!message.flags.read && !message.flags.visible && !message.flags.noNotification));
isNew = isNew &&(!message.accepted || message.accepted === "pending");
return isNew;
},
shouldNotify: function(message) {
var should = this.prefs && this.prefs.enableNotification;
should = should && (this.displayOff || (this.appDeactivated || !this.matchFilter(this.filter, message.conversations)));
return should;
},
getPendingNewMessages: function(newMessages) {
var i, msg;
var latestRev = 0;
var latestRevSet = this.cutoffRevSet;
var pendingMessages = [];
enyo.log("In ", this.name, "DashboardManager, cutoffRevset: ", this.cutoffRevSet);
//enyo.log(" current filter: ", this.filter);
for (i = 0; i < newMessages.length; i++) {
msg = newMessages[i];
//enyo.log("*********** In ", this.name, "DashboardManager, message: ", msg);
if (!this.isInited && msg.folder === enyo.messaging.message.FOLDERS.INBOX) {
latestRevSet = msg.conversations && latestRevSet < msg.readRevSet ? msg.readRevSet : latestRevSet;
} else if (this.isNewMessage(msg)) {
if (this.shouldNotify(msg)) {
pendingMessages.push(msg);
}
latestRevSet = msg.conversations && latestRevSet < msg.readRevSet ? msg.readRevSet : latestRevSet;
}
latestRev = latestRev < msg._rev ? msg._rev : latestRev;
}
if (latestRevSet > this.cutoffRevSet) {
this.cutoffRevSet = latestRevSet;
}
if (latestRev > this.lastRev) {
this.lastRev = latestRev;
this.shouldUpdateWatch = true;
}
return pendingMessages;
},
getPendingLayers: function(pendingMessages) {
var layers = [];
enyo.log("*********** In ", this.name, "DashboardManager, pending changes for notification: ", this.pendingMessages);
// notify new messages
for (var i = 0; i < pendingMessages.length; i++) {
var msg = pendingMessages[i];
// get layers to push to dashboard
layers.push(this.makeDashboardLayer(msg));
}
return layers;
},
makeDashboardLayer: function(message) {
var layer = {};
layer._message = message;
layer._messageCount = 1;
if (message.from) {
layer._from = layer.title = this.getDisplayName(message);
} else {
layer.title = "";
enyo.error("Message ", message._id, " is missing Sender information, message len= ", (message.messageText && message.messageText.length));
}
layer.text = this.getDisplayText(message);
layer.icon = "images/notification-large-messaging.png";
return layer;
},
getDisplayName: function(message) {
//enyo.log("%%%% In ", this.name, "DashboardManager, message's display name=", message.displayName, ", message.from.name= ", message.from.name, ", message.from.addr=", message.from.addr);
if (enyo.messaging.person.isNotBlank(message.displayName)) {
return message.displayName;
} else if (message.from && enyo.messaging.person.isNotBlank(message.from.name)) {
return message.from.name;
} else if (message.from && enyo.messaging.person.isNotBlank(message.from.addr)) {
return message.from.addr;
}
enyo.warn("DashboardManager: unable to find dispaly name for message '", message._id, "', so default display name to a blank string");
return "";
},
getDisplayText: function(message) {
var text = message.messageText || "";
if (enyo.messaging.message.isMMSMessage(message)) {
text = enyo.messaging.message.getMMSDisplayMessage();
} else if (message._kind === "com.palm.immessage.libpurple:1" || message._kind === "com.palm.immessage.skypem:1" || message._kind === "com.palm.iminvitation:1") {
// almost all the kinds of the messages, except the ones from libpurple and skype,
// are escaped HTML tags by transports before saving them. The two
// exception transports remove HTML when processing incoming messages.
// however, apostrophe is escaped by gtalk, so unescape it to clean the text.
text = text.replace(/&apos;/g, "'");
// remove all trusted html tags
text = enyo.messaging.message.removeHtml(text);
} else {
// other transport escaped text in DB
text = enyo.messaging.message.unescapeText(text);
}
// remove new line characters
text = text.replace(/\r|\n|\\r|\\n/g, " ");
return text;
},
matchFilter: function(filter, threads) {
if (!filter) {// || filter.filterAll) {
return true;
} else if (!threads) {
return false;
}
for (var i = 0; i < threads.length; i++) {
if (filter.thread === threads[i]) {
return true;
}
}
return false;
},
getSoundOptions: function(prefs) {
if (prefs && prefs.notificationSound === "system") {
return {
soundClass: enyo.messaging.message.SOUND_CLASSES.RINGTON,
soundPath: enyo.messaging.utils.getAppRootPath() + enyo.messaging.message.SOUND_PATHS.RECEIVED
};
} else if (prefs && prefs.notificationSound === "vibrate") {
return {
soundClass: enyo.messaging.message.SOUND_CLASSES.VIBRATE
};
} else if (prefs && prefs.notificationSound === "ringtone") {
return {
soundClass: enyo.messaging.message.SOUND_CLASSES.RINGTON,
soundPath: prefs.ringtone.fullPath
};
}
return undefined;
}
});
@@ -0,0 +1,9 @@
enyo.kind({
name: "InviteDashboardManager",
kind: DashboardManager,
create: function() {
this.name = "invite";
this.inherited(arguments);
this.$.newMessagesFinder.dbKind = "com.palm.iminvitation:1";
}
});
@@ -0,0 +1,202 @@
enyo.kind({
name: "InviteWatcher",
kind: enyo.Component,
components: [
{kind: "DbService", onFailure: "fail", components: [
{dbKind: "com.palm.imcommand:1", components: [
{name: "inviteCommandFinder", method: "find", onSuccess: "foundInviteCommands", subscribe: true, resubscribe: true, reCallWatches: true},
{name: "inviteCommandDeleter", method: "del"}
]},
{dbKind: "com.palm.iminvitation:1", components: [
{name: "invitationFinder", method: "find", onSuccess: "foundInvites"},
{name: "invitationAdder", method: "put"}
]}
]}
],
create: function() {
this.inherited(arguments);
this.queryIMCommands();
},
queryIMCommands: function() {
enyo.log("Querying IM commands to find buddy invite");
this.$.inviteCommandFinder.call({
query: {
where: [{
prop: "command",
op: "=",
val: "receivedBuddyInvite"
}, {
prop: "handler",
op: "=",
val: "application"
}]
}
}); // foundInviteCommands
},
foundInviteCommands: function(inSender, inResponse) {
enyo.log("Got invite response: ", (inResponse || inResponse.results));
delete this.pendingCommands;
this.pendingCommands = {};
if (inResponse && inResponse.results) {
for (var i = 0; i < inResponse.results.length; i++) {
var command = inResponse.results[i];
this.pendingCommands[command._id] = command;
}
this.queryImInvitations(this.pendingCommands);
}
},
queryImInvitations: function(pendingCommands) {
var commandIds = [];
for (var key in this.pendingCommands) {
commandIds.push(key);
}
// try to look up if buddy invites already exist or the given set of imcommand ids
this.$.invitationFinder.call({
query: {
where: [{
prop: "commandId",
op: "=",
val: commandIds
}]
}
}); // foundInvites
},
foundInvites: function(inSender, inResponse) {
this.removeProcessedCommands(this.pendingCommands, inResponse && inResponse.results);
this.addPendingInvites(this.pendingCommands);
},
removeProcessedCommands: function(pendingCommands, foundInvites) {
for (var i = 0; foundInvites && i < foundInvites.length; i++) {
if (pendingCommands[foundInvites[i].commandId]) {
// if buddy invite has been added already, remove it from
// the pending invite command so that we won't add the same
// buddy invite twice
delete pendingCommands[foundInvites[i].commandId];
}
}
},
addPendingInvites: function(pendingCommands) {
enyo.log("Creating IM invitations for the following pending invite commands: ", pendingCommands);
var invites = [];
for (var id in pendingCommands) {
var command = pendingCommands[id];
var account = this.getIMAccount(command);
if (account) {
invites.push(this.createInvite(command, account));
} else {
enyo.error("The imcommand for adding buddy '", command.fromUsername,
"' can not be processed since an IM account doesn't exist for serive='",
command.serviceName, "' and username='", command.targetUsername,
"'). So delete this command from database.");
this.$.inviteCommandDeleter.call({
ids: [command._id]
});
}
}
if (invites.length > 0) {
this.$.invitationAdder.call({
objects: invites
});
}
},
createInvite: function(command, account) {
return {
_kind: "com.palm.iminvitation:1",
folder: enyo.messaging.message.FOLDERS.INBOX,
status: "successful",
serviceName: command.serviceName,
localTimestamp: Date.now(),
from: {
addr: command.fromUsername
},
to: [{
addr: command.targetUsername
}],
commandId: command._id,
accepted: "pending",
messageText: this.getInviteText(command, account),
username: account.username,
accountId: account.accountId
};
},
getInviteText: function(command, account, displayName) {
var params = command.params;
var accountName = account ? ", " + account.username + "," : "";
var messageText = (params && params.message) ? enyo.string.removeHtml(params.message) : undefined;
var template, text;
if (params && messageText && displayName) {
template = new enyo.g11n.Template($L("Your account#{accountName} received an invitation from #{name} #{addr} with the message, \"#{msg}\"."));
text = template.evaluate({
accountName: accountName,
name: displayName,
addr: command.fromUsername,
msg: messageText
});
} else if (params && messageText) {
template = new enyo.g11n.Template($L("Your account#{accountName} received an invitation from #{addr} with the message, \"#{msg}\"."));
text = template.evaluate({
accountName: accountName,
addr: command.fromUsername,
msg: messageText
});
} else if (displayName) {
template = new enyo.g11n.Template($L("Your account#{accountName} received an invitation from #{name} #{addr}."));
text = template.evaluate({
accountName: accountName,
name: displayName,
addr: command.fromUsername
});
} else {
template = new enyo.g11n.Template($L("Your account#{accountName} received an invitation from #{addr}."));
text = template.evaluate({
accountName: accountName,
addr: command.fromUsername
});
}
return text;
},
getIMAccount: function(command){
var ims = enyo.application.accountService.getCleanedImAccounts();
var account = undefined;
for (var i = 0, a; ims && i < ims.length; i++) {
if (this.isSameIMAccount(command, ims[i])) {
if (account) {
// if there are more than one IM account that matches the service name
// and user name pair, we should not display the account name.
return undefined;
} else {
account = ims[i];
}
}
}
return account;
},
isSameIMAccount: function(command, im) {
if (command.serviceName !== im.serviceName) {
return false;
}
var targetUsername = command.targetUsername;
var imAccountName = im.username;
if (targetUsername === imAccountName) {
return true;
}
/**
* If target user name is not an email address, then we need to use the
* user name prefix of the email address from the IM account and compare
* it to the target user name. So 'bob.dole' should match 'bob.dole@company.com'
* but not 'bob@company.com'.
*/
var accountUsername = imAccountName.replace(/\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/, "");
return targetUsername === accountUsername;
}
});
@@ -0,0 +1,11 @@
enyo.kind({
name: "MessageDashboardManager",
kind: DashboardManager,
create: function() {
this.name = "message";
this.inherited(arguments);
},
shouldNotify: function(message) {
return message._kind !== "com.palm.iminvitation:1" && this.inherited(arguments);
}
});
@@ -0,0 +1,179 @@
/**
* A utility class that merges the new layers into existing layers. The new layers
* that are passed into this class should be ordered so that the layer with the
* oldest message is placed at the beginning of the list and the layer with the
* newest message is at the end.
*
* After all the layers are merged, the layers that are set into dashboard are
* ordered by read revision. The layer with the latest read revision, which is
* the latest message, will be placed at the end of the list. The layer with oldest
* read revision will be at the beginning of the list.
*
* Each layer in dashboard represents a chat thread. When the chat thread is
* not found in the existing dashboard layers, the layer with new chat thread is
* added to the end of the list immediately. When there are multiple new
* messages are sent to a chat thread, these new messages are merged into one single
* layer. A counter will be used to indicate how many new messages are in the
* layer this chat thread. And the layer will show the latest message text.
*/
enyo.kind({
name: "MessageDashboardUtil",
addLayers: function(dashboard, newLayers) {
if (newLayers) {
try {
var currentLayers = dashboard.getLayers() || [];
for (var i = 0; i < newLayers.length; i++) {
var layer = newLayers[i];
if (enyo.messaging.message.isMMSMessage(layer._message)) {
// update layer's title and text for MMS message layer
layer = this.getMMSMessageLayer(layer);
}
this.pushLayer(currentLayers, layer);
}
//TODO: need to hide personal data in the logging below
//enyo.log("In MessageDashboard, new layers in dashboard", currentLayers);
dashboard.setLayers(currentLayers);
} catch (e) {
// got error, most likely from framework or other logic error
enyo.error("Unable to add dashboard messages");
}
}
},
// unit tested functions
getMMSMessageLayer: function(layer) {
var mmsLayer = layer;
mmsLayer._from = mmsLayer.title = layer._from;
mmsLayer.text = enyo.messaging.message.getMMSDisplayMessage();
return mmsLayer;
},
pushLayer: function(currentLayers, layer) {
var ndx = this.findExistingLayerIndex(currentLayers, layer);
if (ndx === undefined) {
// this is a new layer
this.addNewLayer(currentLayers, layer);
} else {
enyo.log("******* found existing layer at location ", ndx, " in array of ", currentLayers.length, " elements");
if (layer._message.readRevSet > currentLayers[ndx]._message.readRevSet) {
// only update existing layer if new layer has a newer timestamp
var newLayer = this.getUpdatedLayer(currentLayers[ndx], layer);
if (ndx !== currentLayers.length - 1) {
this.moveToLatestLayer(currentLayers, newLayer, ndx);
} else {
this.mergeLatestLayer(currentLayers, newLayer, ndx);
}
}
}
},
findExistingLayerIndex: function(currentLayers, newLayer) {
var layer;
for (var i = 0; i < currentLayers.length; i++) {
layer = currentLayers[i];
if (enyo.messaging.message.isMMSMessage(newLayer._message) && enyo.messaging.message.isMMSMessage(layer._message)) {
return i;
} else {
newThreads = newLayer._message.conversations;
existingThreads = layer._message.conversations;
//enyo.log("******* is new layer ", newLayer, " a mms message: ", enyo.messaging.message.isMMSMessage(newLayer._message));
//enyo.log("******* do current layer ", layer._message._id, " and new layer ", newLayer._message._id, " have same threads: ", this.areThreadsMatched(existingThreads, newThreads));
// The same thread with sms and mms messages are not considered
// as the same layer in dashboard. They are placed in two separate
// layers. Based on the current design, all MMS messages are always
// placed in the same layer.
if (!enyo.messaging.message.isMMSMessage(newLayer._message) && this.areThreadsMatched(existingThreads, newThreads, layer, newLayer)) {
return i;
}
}
}
return undefined;
},
areThreadsMatched: function(threads1, threads2, layer1, layer2) {
if (!threads1 || !threads2) {
// THIS SCENARIO SHOULD NOT HAPPEN
// if one of the threads are not defined, then the threads are not matched.
enyo.warn("Dashboard layer for message ", (!threads1 ? (layer1._message._id + (!threads2 ? (" and " + layer2._message._id) : "")) : layer2._message._id), " doesn't belong to a chat thread.");
return false;
} else if (threads1.length === 0 && threads2.length === 0) {
enyo.warn("Dashboard layer for message ", layer1._message._id, "and ", layer2._message._id, " doesn't belong to a chat thread.");
// THIS SCENARIO SHOULD NOT HAPPEN
// if both threads are empty, they are not considered as matched either
// since messages in dashboard notification should have an associated chat thread.
return false;
}
var match = threads1.length === threads2.length;
if (match) {
for (var n = 0; n < threads1.length && n < threads2.length; n++) {
if (threads1[n] !== threads2[n]) {
match = false;
break;
}
}
}
return match;
},
addNewLayer: function(currentLayers, layer) {
currentLayers.push(layer);
if (currentLayers.length > 1) {
this.updateIcon(layer, true);
}
},
moveToLatestLayer: function(currentLayers, newLayer, ndx) {
// if existing layer is not the last element, we need to
// reshavel elements in the array to make the elements sort
// by timestamp.
var sortIdx;
enyo.log("*********** move existing layer to the end of the list");
for (sortIdx = ndx; sortIdx < currentLayers.length && newLayer._message.readRevSet > currentLayers[sortIdx]._message.readRevSet; sortIdx++) {
}
// remove existing layer from its original location
currentLayers.splice(ndx, 1);
// add it back to the new location
currentLayers.splice(sortIdx, 0, newLayer);
if (currentLayers.length > 1 && sortIdx > 0) {
// Always change existing layer to represent multi-layers since
// there are more than one layer in the dashbarod.
this.updateIcon(newLayer, true);
if (ndx === 0) {
// change the new first element's icon to represent single layer.
this.updateIcon(currentLayers[0], false);
}
}
},
mergeLatestLayer: function(currentLayers, newLayer, ndx) {
this.updateIcon(newLayer, currentLayers.length > 1);
// layer's position is not changed.
currentLayers[ndx] = newLayer;
enyo.log("************* merged latest layer: ", currentLayers[ndx]);
},
getUpdatedLayer: function(existingLayer, newLayer) {
var layer = newLayer;
// increment unread new message count
layer._messageCount = existingLayer._messageCount + 1;
// only need to check one of the layers since both layers are representing the same thread or same kind of messages
if (!enyo.messaging.message.isMMSMessage(existingLayer._message)) {
layer.title = newLayer._from + " (" + layer._messageCount + ")";
}
//enyo.log("In MessageDashboard, updated existing layer: ", layer);
return layer;
},
updateIcon: function(layer, isMultiple) {
if (layer) {
layer.icon = isMultiple ? "images/notification-large-messaging-mult.png" : "images/notification-large-messaging.png";
}
}
});
@@ -0,0 +1,69 @@
/**
* A temporary dialog for Class0 notification. Once the framework has an Alert
* widget, we should use that instead of this dialog box.
*/
enyo.kind({
name: "Class0Dialog",
kind: enyo.VFlexBox,
className: "class0",
showing: true,
published: {
/** Array of layer objects specifying contents of alert.*/
layers: null
},
components:
[
{kind: enyo.HFlexBox, components: [
{kind: "Image", src:"../../../images/notification-large-messaging.png", className:"notification-icon"},
{kind: enyo.VFlexBox, className:"notification-text-box", components: [
{name: "title", className: "notification-subject"},
{name: "text", className: "notification-body-multi"}
]}
]},
{kind: enyo.HFlexBox, components: [
{
kind:"NotificationButton", name: "dismissBtn", content: $L("Dismiss"), className: "enyo-notification-button-affirmative button-left", flex:1, onclick: "dismissClicked"
},
{
kind:"NotificationButton", name: "keepBtn", content: $L("Keep"), className: "enyo-notification-button-alternate button-right", flex:1, onclick: "keepClicked"
}
]},
{kind: "ApplicationEvents", onWindowParamsChange: "windowParamsChangeHandler"}
],
create: function() {
this.inherited(arguments);
this.layers = [];
},
dismissClicked: function() {
this.layers.pop();
this.updateWindow();
},
keepClicked: function() {
var topLayer = this.layers.pop();
this.updateWindow();
enyo.application.class0AlertManager.addClass0Message(topLayer.message);
},
addLayer: function(layer) {
this.layers.push(layer);
this.updateWindow();
},
updateWindow: function() {
if (!this.layers || !this.layers.length) {
enyo.application.class0AlertManager.closeDialog();
} else {
var len = this.layers.length;
var top = this.layers[len - 1];
this.$.title.setContent(top.title);
this.$.text.setContent(top.text);
}
},
windowParamsChangeHandler: function(inSender, event) {
var layer = event.params;
enyo.log("*&*&*&*&* Class0Dialog.windowParamsChangeHandler, window reactivated: ", layer);
this.addLayer(layer);
return true;
}
});
@@ -0,0 +1,42 @@
body {
color: #FFF;
font-family: Prelude, 'Prelude Medium', 'Segoe UI', Arial, Helvetica, sans-serif;
background:transparent;
}
.notification-icon {
margin-right:12px;
margin-left:2px;
}
.notification-text-box {
margin-bottom:15px;
}
.notification-subject {
font-size:16px;
font-weight:bold;
}
.notification-subject,
.notification-body {
width:260px;
text-overflow: ellipsis;
overflow: hidden;
white-space:nowrap;
}
.notification-body,
.notification-body-multi {
font-size:14px;
line-height:17px;
}
.notification-body-multi {
width:260px;
height:53px;
overflow:hidden;
}
.button-top {
margin-bottom:4px;
}
.button-right {
margin-left:4px;
}
.button-left {
margin-right:4px;
}
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
// console.profile && console.profile ("calendar");
var timing = {start: -Date.now()};
</script>
<title>Reminder</title>
<script type="text/javascript" launch="debug" src="../../../../../enyo/0.10/framework/enyo.js"></script>
</head>
<body>
<script type="text/javascript">
new Class0Dialog().renderInto(document.body);
(function() {
enyo.relaunch = function(inParams){
if (inParams.launchedAtBoot) {
return;
}
enyo.log("Class0 enyo.relaunch inParams: ", inParams);
enyo.windows.activate("class0.html", "class0", inParams);
};
enyo.applicationRelaunchHandler = enyo.relaunch;
})();
</script>
</body>
</html>
@@ -0,0 +1,4 @@
enyo.depends(
"Class0Dialog.js",
"class0.css"
);
@@ -0,0 +1,4 @@
FavoriteConstants= {
NO_FAVORITE_MESSAGE: $L("You do not have any favorite contacts."),
NO_IM_ACCOUNS_MESSAGE: $L("Tap here to add contact info")
};
@@ -0,0 +1,100 @@
/*globals enyo */
enyo.kind({
name: "FavoriteItem",
kind: "SwipeableItem",
confirmCaption: $L("Remove"),
layoutKind: "HFlexLayout",
components: [
{name: "imageContainer", style: "display: inline-block;", components: [
{className: "contact-image-border"},
{name: "contactImage", kind: "Image", className: "contact-image"}
]},
{name: "status", className: "status status-buddy"},
{className:"message-summary", components: [
{name: "contactName", className:"contact-name-box contact-name", align: "center"},
{name: "statusMessage", className: "status-message"},
{name: "contactMessage", className: "contact-message"}
]}
],
create: function() {
this.inherited(arguments);
this.addClass("contactItem");
},
updateContactName: function(inFavorite) {
var displayName = enyo.messaging.person.getDisplayName(inFavorite);
this.$.contactName.setContent(enyo.messaging.message.removeHtml(displayName));
},
updateStatus: function(ims) {
var bestAvailability = this.getBestAvailability(ims);
var statusClass = "";
if (bestAvailability !== undefined) {
statusClass = "status-" + enyo.messaging.im.availabilityClasses[bestAvailability];
}
this.$.status.setClassName("status status-buddy " + statusClass);
// update status message
this.$.statusMessage.setContent(enyo.messaging.message.removeHtml(this.getStatusMessage(ims)));
},
updateContactImage: function(inPerson) {
this.$.contactImage.setAttribute("src", enyo.messaging.person.getDisplayImage(inPerson));
},
updateAccounts: function(inFavorite) {
if (enyo.messaging.person.hasMessagingAccounts(inFavorite) || enyo.messaging.person.hasSMSAccounts(inFavorite)) {
this.$.contactMessage.setContent("");
} else {
// if there is no IM/SMS accounts, show the message to let user know to add contact info
this.$.contactMessage.setContent(FavoriteConstants.NO_IM_ACCOUNS_MESSAGE);
this.$.statusMessage.setContent("");
this.$.contactName.setClassName("contact-name contact-name-no-messaging-account");
}
},
setFavorite: function(inFavorite) {
//enyo.log("#@#@ favorite: ", inFavorite);
this.updateContactName(inFavorite);
this.updateContactImage(inFavorite);
this.updateAccounts(inFavorite);
this.updateStatus(inFavorite.ims);
},
/***********************************
* Functions below are unit tested *
***********************************/
getBestAvailabilityImsIndex: function(ims) {
var index;
var bestAvailability;
if (ims) {
for (var i = 0; ims && i < ims.length; i++) {
if (bestAvailability === undefined || ims[i].availability < bestAvailability) {
index = i;
bestAvailability = ims[i].availability;
}
}
}
return index;
},
getBestAvailability: function(ims) {
var bestAvailability;
if (ims) {
var index = this.getBestAvailabilityImsIndex(ims);
bestAvailability = index !== undefined ? ims[index].availability : undefined;
}
return bestAvailability;
},
getStatusMessage: function(ims) {
var statusMessage = "";
if (ims) {
var index = this.getBestAvailabilityImsIndex(ims);
if (index !== undefined) {
statusMessage = ims[index].status || "";
if (ims[index]._kind === "com.palm.imbuddystatus.libpurple:1") {
statusMessage = enyo.messaging.message.unescapeText(statusMessage);
}
}
}
return statusMessage;
}
});
@@ -0,0 +1,131 @@
/*globals enyo */
enyo.kind({
name: "FavoriteList",
kind: "VFlexBox",
className: "favoriteItem",
published: {
},
events: {
onSelectFavorite: ""
},
components: [
{kind: "FavoriteService", onSuccess: "gotFavorites", onWatch: "favoritesWatch"},
{className:"header-shadow header-favorite-shadow"},
//{kind: "SearchInput", hint: $L("Search"), className:"searchList", onSearch: "filterList"},
{name: "noFavoriteMessage", content: FavoriteConstants.NO_FAVORITE_MESSAGE, className: "messageTexts"},
{flex: 1, name: "list", kind: "DbList", desc: false, onQuery: "listQuery", className:"messaging-listsDivider", onSetupRow: "listSetupRow", components: [
{name: "favoriteItem", tapHighlight:true, kind: "FavoriteItem", onclick: "favoriteClicked", onConfirm: "removeFavorite", onCancel: "disableKeyboardMannualMode"}
]},
{name: "contactLauncher", kind: "Launcher", app: "com.palm.app.contacts"},
{name: "unfavoritePersonService", kind:"PalmService", service: "palm://com.palm.service.contacts/", method: "unfavoritePerson"}
],
favoritesWatch: function() {
//enyo.log("#@#@ favorites list is updating......");
this.$.list.reset();
},
gotFavorites: function(inSender, inResponse, inRequest) {
//enyo.log("#@#@ got favorites: ", inResponse);
this.$.list.queryResponse(inResponse, inRequest);
// by default, hide these messages
this.$.noFavoriteMessage.hide();
if ((inRequest.index === 0) && (!inResponse || !inResponse.results || inResponse.results.length === 0)) {
this.$.noFavoriteMessage.show();
}
},
filterList: function(inSender, inValue){
this.filterString = inValue;
this.$.list.reset();
},
listQuery: function(inSender, inQuery) {
//enyo.log("#@#@ quering favorites");
inQuery.where = [{
prop: "favorite",
op: "=",
val: true
}];
inQuery.select = enyo.messaging.person.selectAttributes;
inQuery.orderBy = "sortKey";
return this.$.favoriteService.call({query: inQuery});
},
listSetupRow: function(inSender, inFavorite, inIndex) {
//enyo.log("#@#@ favorite list set up row" );
this.$.favoriteItem.setFavorite(inFavorite);
// set selected row color
this.$.favoriteItem.addRemoveClass("enyo-item-selected", this.selectedRecord && inFavorite._id === this.selectedRecord._id ? true: false);
},
favoriteClicked: function(inSender, inEvent) {
var selected = this.$.list.fetch(inEvent.rowIndex);
if (selected) {
//FIXME: should comment out the following log since it contain user data
enyo.log("clicked on favorite: ", (selected.displayName || selected.username));
if (enyo.messaging.person.hasMessagingAccounts(selected)) {
// update list item selection
this.selectedRecord = selected;
this.$.list.refresh();
this.doSelectFavorite({
personId: this.selectedRecord._id,
displayName: this.selectedRecord.displayName,
username: this.selectedRecord.ims[0].username,
serviceName: this.selectedRecord.ims[0].serviceName
});
} else if (enyo.messaging.person.hasSMSAccounts(selected)) {
// update list item selection
this.selectedRecord = selected;
this.$.list.refresh();
this.doSelectFavorite({
personId: this.selectedRecord._id,
displayName: this.selectedRecord.displayName,
username: enyo.messaging.utils.normalizeAddress(this.selectedRecord.phoneNumbers[0], "sms"),
serviceName: "sms"
});
} else {
this.$.contactLauncher.launch({
contact: selected,
launchType:"editContact"
});
// update dashboard's filter
enyo.application.messageDashboardManager.setFilter({thread: undefined});
}
}
},
setSelection: function(inChatThread){
if ((this.selectedRecord && inChatThread && this.selectedRecord._id === inChatThread.personId) || (!this.selectedRecord && inChatThread && !inChatThread.personId)) {
//case selection didn't change, do nothing
return;
}
else {
this.selectedRecord = {_id: inChatThread ? inChatThread.personId : null};
if (!inChatThread) {
this.$.list.refresh();
}
}
},
disableKeyboardMannualMode: function(){
enyo.messaging.keyboard.setKeyboardAutoMode();
},
removeFavorite: function(inSender, inEvent) {
enyo.messaging.keyboard.setKeyboardAutoMode();
var favorite = this.$.list.fetch(inEvent);
this.$.unfavoritePersonService.call({"personId": favorite._id});
},
updateList: function() {
this.$.list.update();
},
resetList: function() {
this.$.list.reset();
},
windowHiddenHandler: function() {
// dump data buffer
this.$.list.punt();
// cancel service call
//this.$.favoriteService.cancel();
}
});
@@ -0,0 +1,154 @@
enyo.kind({
name: "FavoriteService",
kind: enyo.Service,
requestKind: "FavoriteRequest",
events: {
onWatch: ""
}
});
enyo.kind({
name: "FavoriteRequest",
kind: enyo.Request,
events: {
onWatch: "doWatch"
},
components: [
{kind: "DbService", onFailure: "fail", components: [
{name: "people", method: "find", dbKind: "com.palm.person:1", onSuccess: "gotPeople", subscribe: true, onWatch: "doWatch"/*, resubscribe: true, reCallWatches: true*/},
{name: "peopleSearch", method: "search", dbKind: "com.palm.person:1", onSuccess: "gotPeople", subscribe: true, onWatch: "doWatch"/*, resubscribe: true, reCallWatches: true*/},
{name: "threads", dbKind: "com.palm.chatthread:1", method: "find", onSuccess: "gotThreads", subscribe: true, onWatch: "doWatch"/*, resubscribe: true, reCallWatches: true*/},
{name: "buddy", service: "palm://com.palm.tempdb/", dbKind: "com.palm.imbuddystatus:1", method: "find", onSuccess: "gotBuddies", subscribe: true, onWatch: "doWatch"/*, resubscribe: true, reCallWatches: true*/}
]}/*,
{name: "mockPeople", kind: "MockDb", dbKind: "com.palm.people:1", method: "find", onSuccess: "gotPeople", onFailure: "fail", onWatch: "doWatch"}*/
],
create: function() {
this.inherited(arguments);
},
initComponents: function() {
this.inherited(arguments);
/*
if (!window.PalmSystem) {
this.$.people = this.$.mockPeople;
}
*/
},
call: function() {
//enyo.log(this.params);
if (this.params.query.search) {
this.params.query.search = undefined;
this.$.peopleSearch.cancel();
this.$.peopleSearch.call(this.params);
}
else {
this.$.people.cancel();
this.$.people.call(this.params);
}
},
reply: function() {
//enyo.log(this.data);
this.receive(this.data);
},
fail: function(inSender, inResponse) {
enyo.error("FavoriteRequest DbService fail: ", inResponse);
this.reply();
},
finish: function() {
// don't destroy automatically, so we can see watches
},
gotPeople: function(inSender, inResponse, inRequest) {
this.data = inResponse;
this.assembleIds();
this.queryThreads();
},
assembleIds: function() {
this.pids = [];
for (var i=0, d; d=this.data.results[i]; i++) {
this.pids.push(d._id);
}
},
queryThreads: function() {
this.$.threads.id = "favoriteRequest_threads";
this.$.threads.cancel();
this.$.threads.call({query: {
where: [{
prop: "personId",
op: "=",
val: this.pids
}],
select: [
"_id",
"_kind",
"personId",
"normalizedAddress",
"displayName",
"unreadCount"
]
}
});
},
gotThreads: function(inSender, inResponse, inRequest) {
//enyo.log("#@#@ got chat threads in ", inRequest.latency, "ms");
//enyo.log("#@#@ got chat threads: ", inResponse);
// join chat thread data to buddy data
enyo.messaging.utils.joinData(this.data, inResponse.results, "personId", "_id", "thread");
this.queryBuddies();
},
queryBuddies: function() {
this.$.buddy.id = "favoriteRequest_buddies";
this.$.buddy.cancel();
this.$.buddy.call({query: {
where: [{
prop: "personId",
op: "=",
val: this.pids
}],
select: [
"_id",
"_kind",
"personId",
"username",
"serviceName",
"availability",
"status"
]
}
});
},
gotBuddies: function(inSender, inResponse) {
var favorites = this.data.results;
var buddies = inResponse.results;
var lookup = {};
var i, j;
//enyo.log("@#@# got buddies: ", buddies);
// in Person' record, 'ims' records store only the username and service
// name for each buddy. So we need to use this pair of information to
// reverse look up the buddy record.
for (i = 0; i < favorites.length; i++) {
var favorite = favorites[i];
lookup[favorite._id] = favorite.ims;
}
//enyo.log("@#@# got lookup: ", lookup);
for (i = 0; i < buddies.length; i++) {
var ims = lookup[buddies[i].personId];
//enyo.log("@#@# looked up ims: ", ims);
if (ims) {
for (j = 0; j < ims.length; j++) {
if (buddies[i].serviceName === ims[j].type && buddies[i].username === ims[j].value) {
ims[j].availability = buddies[i].availability;
ims[j].status = buddies[i].status;
ims[j]._kind = buddies[i]._kind;
}
}
}
}
// all done now
this.reply();
}
});
@@ -0,0 +1,42 @@
enyo.kind({
name: "ConnectPhone",
className: "connect-phone",
kind: "enyo.VFlexBox",
firstLaunch: false,
events: {
onComplete: ""
},
components: [
{name: "connectPhoneMsg", content:FirstLaunchConstants.PAIR_PHONE_MESSAGE, className:"accounts-body-title"},
{name: "launchApp", kind: "Launcher", app: "com.palm.app.bluetooth"},
{name: "connectPhoneButton", kind: "Button", label: FirstLaunchConstants.PAIR_PHONE_BUTTON_LABEL, onclick: "connectPhone", className:"accounts-btn"},
{name: "telephony", kind: "PalmService", service: "palm://com.palm.telephony/", method: "platformQuery", onSuccess: "gotPlatformData", subscribe: true}
],
subscriptToTelephony: function() {
this.$.telephony.call();
},
gotPlatformData: function(inSender, inResponse) {
enyo.log("------------ platformDate: ", inResponse);
if (inResponse && inResponse.extended && inResponse.extended.capabilities && inResponse.extended.capabilities.mapcenable) {
this.connected = true;
this.$.connectPhoneMsg.setContent(FirstLaunchConstants.SHOW_PHONE_MESSAGE);
this.$.connectPhoneButton.setCaption(FirstLaunchConstants.SHOW_PHONE_BUTTON_LABEL);
} else {
this.connected = false;
this.$.connectPhoneMsg.setContent(FirstLaunchConstants.PAIR_PHONE_MESSAGE);
this.$.connectPhoneButton.setCaption(FirstLaunchConstants.PAIR_PHONE_BUTTON_LABEL);
}
},
connectPhone: function(){
if (this.connected) {
this.doComplete();
} else {
this.$.launchApp.launch();
}
},
accountsUpdated: function(ims) {
if (this.firstLaunch) {
this.setShowing(ims && ims.length === 0);
}
}
});
@@ -0,0 +1,61 @@
enyo.kind({
name: "FirstLaunch",
kind: "enyo.VFlexBox",
events: {
onFirstLaunchDone: ""
},
components: [
{
kind: "firstLaunchView",
iconSmall: "images/messaging-48x48.png", // Path to small icon used for title bar
iconLarge: "icon-256x256.png", // Path to large icon used for Welcome page
/*
components: [
{name: "connectPhoneLayer", layoutKind: "VFlexLayout", className:"box-center", style:"margin-top:0", components: [
{kind: "ConnectPhone", firstLaunch: true, onComplete: "done"}
]}
],
*/
onAccountsFirstLaunchDone: "done",
capability: "MESSAGING"
}
],
create: function() {
this.inherited(arguments);
this.accountService = enyo.application.accountService;
if (this.accountService) {
this.accountService.register(this, this.accountsUpdated.bind(this));
}
},
rendered: function() {
this.inherited(arguments);
var msgs = {
pageTitle: FirstLaunchConstants.TITLE_MESSAGE_WITH_ACCOUNTS,
welcome: FirstLaunchConstants.TITLE_MESSAGE
};
// Templates to exclude from the accounts list (can be an array if you need more than one)
// Do not exclude com.palm.palmprofile; the library will do the right thing WRT that template
var exclude = "com.palm.palmprofile";
this.$.firstLaunchView.startFirstLaunch(exclude, msgs);
/** To fix DFISH-28718
// Show your custom UI (optional)
enyo.asyncMethod(this.$.connectPhoneLayer, "show");
this.$.connectPhone.subscriptToTelephony();
*/
},
accountsUpdated: function() {
//this.$.connectPhone.accountsUpdated(this.accountService.getImAccounts());
},
done: function() {
this.unregisterServices();
this.doFirstLaunchDone();
},
unregisterServices: function() {
this.accountService.unregister(this);
}
});
@@ -0,0 +1,9 @@
FirstLaunchConstants={
TITLE_MESSAGE: $L("To get started, set up a messaging account:"),
TITLE_MESSAGE_WITH_ACCOUNTS: $L("Your Messaging Accounts"),
SETUP_IM_MESSAGE: $L("To get started, set up an IM account."),
PAIR_PHONE_MESSAGE: $L("If this device is connected with your HP webOS phone, you can also send and receive SMS text messages."),
PAIR_PHONE_BUTTON_LABEL: $L("Connect Phone"),
SHOW_PHONE_MESSAGE: $L("Your device is connected with your phone. You can send & receive text messages through your phone connection."),
SHOW_PHONE_BUTTON_LABEL: $L("Send & Receive Messages")
};
@@ -0,0 +1,40 @@
enyo.kind({
name: "FirstLaunchHandler",
kind: enyo.Component,
showFirstLaunch: true,
components: [
{name: "checkFirstLaunch", kind: "Accounts.checkFirstLaunch", onCheckFirstLaunchResult: "gotFirstLaunchResult"}
],
create: function() {
this.inherited(arguments);
this.$.checkFirstLaunch.shouldFirstLaunchBeShown({appId: "com.palm.app.messaging"});
},
gotFirstLaunchResult: function(inSender, inResponse) {
if (inResponse) {
this.showFirstLaunch = inResponse.showFirstLaunch;
}
},
shouldShowFirstLaunch: function() {
return this.showFirstLaunch;
},
migratePrefsSettingIntoAccountService: function(prefs) {
if (prefs) {
enyo.log("Migrating first launch setting from messaging app's preferences in to account service");
if (prefs.firstUseMode === false) {
// we only need to migrate the preferences setting only when
// the app was previously passed first launch and the app used
// preferences to keep track of first launch mode.
this.passedFirstLaunch(); // mark first launch shown
}
// then clear the first use mode setting
prefs.firstUseMode = null;
enyo.application.prefsHandler.setPrefs(prefs);
}
},
passedFirstLaunch: function() {
this.showFirstLaunch = false;
// tell account services that messaging app has shown first launch.
this.$.checkFirstLaunch.firstLaunchHasBeenShown(true);
}
});
@@ -0,0 +1,86 @@
enyo.kind({
name: "AccountLoginState",
kind: "enyo.Control",
layoutKind: "VFlexLayout",
className: "account-login-state",
published: {
loginState: {}
},
events: {
onAvailabilitySet: "",
onInputCustomMessage: "",
onDrawerOpened: ""
},
components: [
{name: "account", onclick: "toggleDrawer", style: "min-height: 30px; margin-top:8px; width:320px;", components:[
{name: "header", layoutKind: "HFlexLayout", components: [
{name: "status", className: "status status-buddy", style: "margin-left:6px;margin-right:10px;margin-top:3px;"},
{name: "accountText", layoutKind: "HFlexLayout", style: "width:265px;", components: [
{name: "accountName", style: "margin-right:5px;vertical-align:middle;font-size:16px;" },
{name: "username", flex: 1, style: "overflow: hidden; font-size: 14px;margin-top:2px;color:grey", className: "enyo-text-ellipsis"}
]},
{name: "arrow", className: "enyo-menuitem-arrow"}
]},
{name: "footer", layoutKind: "HFlexLayout", components: [
{className: ""}, // TODO: should set the right class name so that custom message lines up with account name
{name: "customMessage", className:"enyo-text-ellipsis", style: "width:100%;overflow: hidden; font-size: 14px; color:#333; margin-left: 32px;margin-right:14px;margin-top: 3px;padding-bottom: 8px;"}
]}
]},
{kind: "Drawer", open: false, components: [
{name: "statuses", kind: "AccountStatuses", onAvailabilityChanged: "availabilityChanged", onInputCustomMessage: "inputCustomMessage"}
]}
],
create: function() {
this.inherited(arguments);
this.$.accountName.setContent(this.loginState.accountTypeName);
this.$.username.setContent(this.loginState.username);
this.loginStateChanged();
this.createComponent({name: "db", kind: "AccountLoginStateDB", accountId: this.loginState._id});
},
loginStateChanged: function() {
var availability = enyo.messaging.imLoginState.getAvailability(this.loginState);
this.$.statuses.setAvailability([availability]);
this.$.statuses.setInvisibilitySupported(this.loginState.supportsInvisibleStatus);
if (availability <= enyo.messaging.im.availability.BUSY) {
this.$.customMessage.setContent(this.loginState.customMessage);
} else {
this.$.customMessage.setContent("");
}
this.updateStatusClass();
},
availabilityChanged: function(inSender) {
this.loginState.availability = inSender.availability;
this.doAvailabilitySet(this.$.db, this.loginState);
},
inputCustomMessage: function(inSender) {
this.doInputCustomMessage(this.$.db, this.loginState);
},
toggleDrawer: function() {
this.showDrawer = !this.showDrawer;
this.$.drawer.setOpen(this.showDrawer);
if (this.showDrawer) {
this.doDrawerOpened();
this.$.arrow.setClassName("enyo-menuitem-arrow enyo-button-down");
this.$.account.applyStyle("border-bottom", "1px solid rgba(0,0,0,0.2);");
} else {
this.$.arrow.setClassName("enyo-menuitem-arrow");
this.$.account.applyStyle("border-bottom", "none");
}
},
closeDrawer: function() {
if (this.showDrawer) {
this.toggleDrawer();
}
},
updateStatusClass: function() {
if (this.statusClass) {
this.$.status.removeClass(this.statusClass);
}
this.statusClass = "status-" + enyo.messaging.im.buddyAvailabilities[enyo.messaging.imLoginState.getAvailability(this.loginState)];
this.$.status.addClass(this.statusClass);
}
});
AccountLoginState.COMPONENT_NAME_PREFIX = "loginState-";
@@ -0,0 +1,143 @@
enyo.kind({
name: "AccountLoginStatesPopup",
kind: "enyo.Menu",
lazy: false,
style: "width:330px;",
published: {
loginStates: [],
accountsState: {}
},
events: {
onAvailabilitySet: "",
onInputCustomMessage: ""
},
components: [
{name: "scroller", kind: "BasicScroller", flex: 1, horizontal: false, autoHorizontal: false, components: [
{name: "allAccountsDivider", kind: "enyo.Divider", caption: $L("ALL ACCOUNTS"), style: "margin-top:5px;margin-bottom:5px;", showing: false},
{name: "allAccounts", kind: "AccountStatuses", onAvailabilityChanged: "changeAccountsAvailability", className: "all-accounts", onInputCustomMessage: "inputAccountsCustomMessage"},
{name: "indAccountsDivider", kind: "enyo.Divider", caption: $L("INDIVIDUAL ACCOUNTS"), style: "margin-top:5px;margin-bottom:5px;", showing: false}
]},
{name: "db", kind: "BaseLoginStateDB", loginStates: this.loginStates}
],
loginStatesChanged: function() {
this.$.allAccountsDivider.setShowing(this.loginStates.length > 1);
this.$.indAccountsDivider.setShowing(this.loginStates.length > 1);
this.checkInvisibilitySupport();
this.updateAllAccountsAvailability();
this.updateLoginStateComponents();
this.$.db.setLoginStates(this.loginStates);
},
checkInvisibilitySupport: function () {
this.$.allAccounts.setInvisibilitySupported(this.shouldShowAllAccountsInvisibleStatus(this.loginStates));
},
updateAllAccountsAvailability: function () {
var availArray = [];
var availability;
for (var i=0; i<this.loginStates.length; i++) {
availArray[i] = enyo.messaging.imLoginState.getAvailability(this.loginStates[i]);
}
this.$.allAccounts.setAvailability(availArray);
},
updateLoginStateComponents: function() {
var loginStateComponents = this.getLoginStateComponents();
var key;
if (this.loginStates.length <= 1) {
for (key in loginStateComponents) {
this.destroyLoginStateComponent(loginStateComponents[key]);
}
} else {
var component, i;
var stateIds = {};
for (i = 0; i < this.loginStates.length; i++) {
component = loginStateComponents[this.loginStates[i]._id];
stateIds[this.loginStates[i]._id] = true;
if (component) {
enyo.log("updating login state component");
component.setLoginState(this.loginStates[i]);
} else {
component = this.$.scroller.createComponent({
name: AccountLoginState.COMPONENT_NAME_PREFIX + this.loginStates[i]._id,
kind: "AccountLoginState",
loginState: this.loginStates[i],
className: "individual-account",
onAvailabilitySet: "changeAvailability",
onInputCustomMessage: "inputCustomMessage",
onDrawerOpened: "drawerOpened"
}, {owner: this});
component.render();
}
if (i != 0) component.applyStyle("border-top", "1px solid rgba(255,255,255,0.5);");
if (i != this.loginStates.length - 1) component.applyStyle("border-bottom", "1px solid rgba(0,0,0,0.2);");
}
for (key in loginStateComponents) {
if (!stateIds[key]) {
this.destroyLoginStateComponent(loginStateComponents[key]);
}
}
}
},
drawerOpened: function(inSender) {
var loginStateComponents = this.getLoginStateComponents();
var key;
for (key in loginStateComponents) {
if (loginStateComponents[key] != inSender) {
loginStateComponents[key].closeDrawer();
}
}
},
getLoginStateComponents: function() {
var components = {};
enyo.forEach(this.getComponents(), function(comp) {
var name = comp.getName();
if (name.indexOf(AccountLoginState.COMPONENT_NAME_PREFIX) >= 0) {
var id = name.substring(AccountLoginState.COMPONENT_NAME_PREFIX.length, name.length);
components[id] = comp;
}
});
return components;
},
destroyLoginStateComponent: function(component) {
component.setShowing(false);
component.destroy();
},
changeAccountsAvailability: function(inSender) {
this.accountsState.availability = inSender.availability;
this.doAvailabilitySet(this.$.db, this.accountsState);
this.close();
},
changeAvailability: function(inSender, db, loginState) {
this.doAvailabilitySet(db, loginState);
this.close();
},
inputAccountsCustomMessage: function(inSender, inEvent) {
this.doInputCustomMessage(this.$.db, this.accountsState);
this.close();
},
inputCustomMessage: function(inSender, db, loginState) {
this.doInputCustomMessage(db, loginState);
this.close();
},
close: function() {
this.inherited(arguments);
// This will cause all drawers to close
this.drawerOpened();
},
// unit tested functions
shouldShowAllAccountsInvisibleStatus: function(states) {
var show = !!states && states.length > 0 && states[0].supportsInvisibleStatus;
var i = 1, len = states ? states.length : 0;
for (; show && i < len; i++) {
show = show && states[i].supportsInvisibleStatus;
}
return show;
}
});
@@ -0,0 +1,87 @@
enyo.kind({
name: "AccountStatuses",
kind: "enyo.Control",
layoutKind: "VFlexLayout",
published: {
availability: [],
invisibilitySupported: false
},
events: {
onAvailabilityChanged: "",
onInputCustomMessage: ""
},
components: [
{name: "availabilities", className: "availabilities", components: [
{name: "available", kind: "Availability", caption: $L("Available"), value: enyo.messaging.im.availability.AVAILABLE, onclick:"updateAvailability", className: "enyo-first available-status"},
{name: "busy", kind: "Availability", caption: $L("Busy"), value: enyo.messaging.im.availability.BUSY, onclick:"updateAvailability"},
{name: "invisible", kind: "Availability", showing: false, caption: $L("Invisible"), value: enyo.messaging.im.availability.INVISIBLE, onclick:"updateAvailability"},
{name: "offline", kind: "Availability", caption: $L("Offline"), value: enyo.messaging.im.availability.OFFLINE, onclick:"updateAvailability"},
{name: "customStatus", kind: "MenuItem", caption: $L("Set custom status"), onclick:"setCustomMessage", className: "custom-status"}
]}
],
availabilityChanged: function() {
this.resetAvailabilities();
var showCustomStatus = false;
for (var i=0; i<this.availability.length; i++) {
this.setAvailabilityCheckMark(this.availability[i]);
if (this.availability[i] <= enyo.messaging.im.availability.BUSY) {
showCustomStatus = true;
}
}
this.$.customStatus.setShowing(showCustomStatus);
this.$.customStatus.$.item.addRemoveClass("enyo-last", showCustomStatus);
this.$.offline.$.item.addRemoveClass("enyo-last", !showCustomStatus);
},
setAvailabilityCheckMark: function(availability) {
if (availability === enyo.messaging.im.availability.AVAILABLE) {
this.$.available.setChecked(true);
} else if (availability === enyo.messaging.im.availability.BUSY) {
this.$.busy.setChecked(true);
} else if (availability === enyo.messaging.im.availability.INVISIBLE) {
this.$.invisible.setChecked(true);
} else if (availability === enyo.messaging.im.availability.OFFLINE) {
this.$.offline.setChecked(true);
}
},
invisibilitySupportedChanged: function() {
this.$.invisible.setShowing(this.invisibilitySupported);
},
resetAvailabilities: function() {
this.$.available.setChecked(false);
this.$.busy.setChecked(false);
this.$.invisible.setChecked(false);
this.$.offline.setChecked(false);
},
updateAvailability: function(inSender, inEvent) {
this.availability = inSender.value;
//this.availabilityChanged(); // TODO: this may be done by the parent component
this.doAvailabilityChanged(this.availability);
},
setCustomMessage: function(inSender, inEvent) {
this.doInputCustomMessage(this.accountId);
}
});
enyo.kind({
name: "Availability",
className: "availability",
kind: "MenuCheckItem",
tapHighlight: true,
itemChrome: [
{name: "status", className: "av-status"},
{name: "caption", className: "av-caption", flex: 1},
{name: "arrow", className: "av-arrow"}
],
create: function() {
this.inherited(arguments);
if (!this.$.status) {
this.createItemChrome();
}
if (this.value === enyo.messaging.im.availability.BUSY) {
this.$.status.addClass("status status-buddy status-away");
} else {
this.$.status.addClass("status status-buddy status-"+enyo.messaging.im.buddyAvailabilities[this.value]);
}
}
});
@@ -0,0 +1,277 @@
enyo.kind({
name: "ImStatus",
kind: "HFlexBox",
events: {
onLoginStatesChange: "",
onAddAccount: ""
},
components: [
{name: "availabilityContainer", flex:1, components: [
{layoutKind: "HFlexLayout", onclick: "availabilityClicked", align:"center" , components: [
{name: "status", className: "status status-buddy", style: "margin-top:-1px;margin-left:0px;"},
{name: "spinner", className: "status-spinner", kind: "Spinner", style: "margin-left:-10px;", spinning: true, shownWhenSpinning: true},
{name: "availability", content: "Offline", className:"availability-text enyo-text-ellipsis"},
{name: "arrow", className: "enyo-listselector-arrow"}
]}
]},
{name: "customStatusContainer", flex: 1, showing: false, components: [
{name: "inputBox", kind:"InputBox", className:"enyo-middle", style:"width:320px;margin-left:-4px;",/*style:"-webkit-border-image:none;border-width:0;",*/ components: [
{name: "customMessageStatus", style: "margin-top:2px;", className: "status status-buddy"},
{name: "customMessage", kind: "Input", onblur: "customMessageBlur", onchange: "customMessageChanged", className:"custom-status-input", style:"margin-left:-20px;vertical-align:middle;", flex:1, styled: false, changeOnKeypress: true, selectAllOnFocus: true}
]},
{layoutKind: "HFlexLayout", style:"margin-left:-6px;padding-left: 10px;padding-right:10px;", components: [
{name: "cancelButton", kind: "Button", flex: 1, caption: "Cancel", onclick: "cancelButtonClicked"},
{name: "setButton", kind: "Button", flex: 1, className:"enyo-button-dark", caption: "Set Status", onclick: "setButtonClicked"}
]}
]},
{name: "accounts", kind: "AccountLoginStatesPopup", onAvailabilitySet: "availabilitySet", onInputCustomMessage: "inputCustomMessage"},
{name: "loginStateFinder", kind: "DbService", dbKind: enyo.messaging.imLoginState.dbKind, method: "find", onSuccess: "gotLoginStates", subscribe: true, resubscribe: true, reCallWatches: true},
{name: "loginStateSetter", kind: "DbService", dbKind: enyo.messaging.imLoginState.dbKind, method: "merge", onSuccess: "changedLoginState"},
{name: "networkAlerts", kind: "NetworkAlerts", onTap: "onNetworkAlertsTapFn"},
{name: "mockDbFinder", kind: "MockDb", dbKind: "loginstates/com.palm.imloginstate:3", method: "find", onSuccess: "gotLoginStates", subscribe: true, resubscribe: true, reCallWatches: true}
],
create: function() {
this.inherited(arguments);
this.fetchLoginStates();
},
showHideStatusSpinner: function(inShow) {
this.$.status.setShowing(!inShow);
this.$.spinner.setShowing(inShow);
this.$.availability.addRemoveClass("adjust-text-for-no-spinner", !inShow);
},
fetchLoginStates: function() {
this.$.loginStateFinder.cancel();
if (window.PalmSystem) {
this.$.loginStateFinder.call();
} else {
this.$.mockDbFinder.call();
}
},
gotLoginStates: function(inSender, inResponse) {
this.loginStates = this.getLoginStates(inResponse);
//enyo.log("%%%%%%%%%%%%%%% this.loginStates", JSON.stringify(this.loginStates));
this.doLoginStatesChange(this.loginStates);
this.accountsState = enyo.messaging.imLoginState.getAggregatedLoginState(this.loginStates);
this.setStatusIndicator(this.accountsState);
this.$.accounts.setLoginStates(this.loginStates);
this.$.accounts.setAccountsState(this.accountsState);
},
getLoginStates: function(inResponse) {
if (!inResponse || !inResponse.results) {
return [];
}
var loginStates = inResponse.results;
for (var i = 0; i < loginStates.length; i++) {
loginStates[i].accountTypeName = this.getAccountTypeName(loginStates[i]);
// The check for type_yahoo can be removed once the supportsInvisibleStatus flag is added to the Yahoo account template
loginStates[i].supportsInvisibleStatus = (this.supportsInvisibleStatus(loginStates[i]) || loginStates[i].serviceName === "type_yahoo");
}
loginStates.sort(function(a, b) {
return a.accountTypeName > b.accountTypeName || (a.accountTypeName === b.accountTypeName && a.username > b.username);
});
return loginStates;
},
getAccountTypeName: function(loginState) {
//enyo.log("%%%%%%%%%%%% loginState: ", loginState);
var account = enyo.application.accountService.getAccount(loginState.serviceName, loginState.username);
return account ? account.loc_name : "";
},
supportsInvisibleStatus: function(loginState) {
var account = enyo.application.accountService.getAccount(loginState.serviceName, loginState.username);
return !!account && account.supportsInvisibleStatus;
},
changedLoginState: function(inSender, inResponse) {
if (inResponse.count > 0) {
this.showHideStatusSpinner(false);
this.$.availability.setContent(this.status);
this.fetchLoginStates();
}
},
setStatusIndicator: function(accountsState) {
if (!accountsState) {
this.setStatusNoAccount();
} else {
this.setAllAccountsStatus(accountsState);
}
},
setStatusNoAccount: function() {
this.status = enyo.messaging.im.availabilityCaptions["offline"];
this.showHideStatusSpinner(false);
this.updateStatusClass("status-offline");
this.$.availability.setContent(this.status);
},
setAllAccountsStatus: function(accountsState) {
var currentStatus = enyo.messaging.im.availabilityClasses[accountsState.bestAvailability];
this.status = enyo.messaging.im.availabilityCaptions[currentStatus];
if (accountsState.hasPending) {
this.setStatusPendingAccount();
} else {
this.setStatusHaveAccount(accountsState, currentStatus);
}
},
setStatusPendingAccount: function() {
this.showHideStatusSpinner(true);
this.$.availability.setContent($L("Signing in..."));
this.startLoginTimer();
// if AccountLoginStatesPopup is opened, close it.
if (this.$.accounts.isOpen) {
this.$.accounts.close();
}
},
// This function can be used to get a sign in caption for individual or all accounts sign in.
getSignInCaption: function() {
var accountName = undefined;
for (var i=0; i<this.loginStates.length; i++) {
if (this.loginStates[i].state === enyo.messaging.imLoginState.TRANSPORT_STATE.LOGGING_ON ||
this.loginStates[i].state === enyo.messaging.imLoginState.TRANSPORT_STATE.RETRIEVING_DATA) {
if (accountName === undefined) {
accountName = this.loginStates[i].accountTypeName;
} else {
return $L("Signing into all accounts...");
}
}
}
if (accountName === undefined) {
return $L("Signing in...");
} else {
//FIXME: the following line can't be localized correctly since it uses the wrong method for localization.
return $L("Signing into "+accountName+"...");
}
},
setStatusHaveAccount: function(accountsState, currentStatus) {
this.showHideStatusSpinner(false);
this.updateStatusClass(this.getStatusClass(currentStatus));
if (accountsState.bestAvailability === enyo.messaging.im.availability.OFFLINE) {
this.$.availability.setContent(this.status);
} else if (!accountsState.identicalAvailabilities || !accountsState.identicalStates || !accountsState.identicalCustomMessages) {
this.$.availability.setContent($L("Custom Availability"));
} else {
if (accountsState.customMessage) {
this.$.availability.setContent(accountsState.customMessage);
} else {
this.$.availability.setContent(this.status);
}
}
this.stopLoginTimer();
},
updateStatusClass: function(newStatusClass) {
if (this.statusClass) {
this.$.status.removeClass(this.statusClass);
}
this.statusClass = newStatusClass;
this.$.status.addClass(this.statusClass);
},
startLoginTimer: function() {
if (!this.loginTimer) {
// login will timeout after 1.5 minutes. this is really long, but intended to handle transports failing
this.loginTimer = setTimeout(function() {
this.loginTimer = undefined;
this.saveAvailability(enyo.messaging.im.availability.OFFLINE);
}.bind(this), 90000);
}
},
stopLoginTimer: function() {
if (this.loginTimer) {
clearTimeout(this.loginTimer);
this.loginTimer = undefined;
}
},
saveAvailability: function(value) {
this.$.loginStateSetter.call({
"props": {
"availability": value
},
"query": {
"from": "com.palm.imloginstate:1"
}
});
},
onNetworkAlertsTapFn:function(inSender, inResponse){
enyo.log("ImStatus:onNetworkAlertsTapFn:inResponse:", inResponse);
},
availabilityClicked: function(inSender, inEvent) {
enyo.messaging.keyboard.setKeyboardAutoMode();
if (this.loginStates.length === 0) {
this.doAddAccount();
} else if (!this.$.spinner.getShowing()) {
this.$.accounts.openAtControl(inSender);
this.$.accounts.setLoginStates(this.loginStates);
this.$.accounts.setAccountsState(this.accountsState);
}
},
availabilitySet: function(inSender, db, loginState) {
this.$.networkAlerts.push({type: "data"});
db.updateAvailability(loginState.availability);
},
inputCustomMessage: function(inSender, db, loginState) {
this.db = db;
if (loginState._id === undefined) {
var status = enyo.messaging.im.availabilityClasses[loginState.bestAvailability];
this.updateCustomMessageStatusClass(this.getStatusClass(status));
} else {
this.updateCustomMessageStatusClass(this.getStatusClass(enyo.messaging.im.buddyAvailabilities[enyo.messaging.imLoginState.getAvailability(loginState)]));
}
if (this.accountsState.customMessage && this.accountsState.customMessage !== "") {
this.$.customMessage.setValue(this.accountsState.customMessage);
} else if (loginState.customMessage && loginState.customMessage !== "") {
this.$.customMessage.setValue(loginState.customMessage);
} else {
this.$.customMessage.setValue("");
}
this.showCustomMessageInput(true);
this.$.customMessage.forceFocus();
},
updateCustomMessageStatusClass: function(newStatusClass) {
if (this.customMessageStatusClass) {
this.$.customMessageStatus.removeClass(this.customMessageStatusClass);
}
this.customMessageStatusClass = newStatusClass;
this.$.customMessageStatus.addClass(this.customMessageStatusClass);
},
customMessageBlur: function(saveMsg) {
if (saveMsg === true) {
this.db.updateCustomMessage(this.$.customMessage.getValue());
this._handledCustomMessage();
} else {
this.showCustomMessageInput(false);
}
},
customMessageChanged: function() {
if(this.$.customMessage.hasFocus()) {
this.customMessageBlur(true);
}
},
cancelButtonClicked: function(inSender, inEvent) {
this._handledCustomMessage();
},
setButtonClicked: function(inSender, inEvent) {
this.customMessageBlur(true);
},
_handledCustomMessage: function() {
this.$.customMessage.setValue("");
this.$.customMessage.forceBlur();
this.db = undefined;
this.showCustomMessageInput(false);
},
showCustomMessageInput: function(show) {
this.$.availabilityContainer.setShowing(!show);
this.$.customStatusContainer.setShowing(show);
},
windowHiddenHandler: function() {
if(this.$.customStatusContainer.showing) {
this._handledCustomMessage();
}
},
/***********************************
* Functions below are unit tested *
***********************************/
getStatusClass: function(value) {
return value && value !== "offline" ?
"status-" + value :
"status-offline";
}
});
@@ -0,0 +1,48 @@
enyo.kind({
name: "BaseLoginStateDB",
kind: "enyo.Component",
published: {
loginStates: []
},
events: {
onMerged: ""
},
components: [
{kind: "DbService", onFailure: "fail", components: [
{name: "stateMerger", method: "merge", onSuccess: "doMerged"}
]}
],
updateAvailability: function(avail) {
this.$.stateMerger.call(this._getAvailabilityMergeString(avail));
},
updateCustomMessage: function(message) {
var mergeItems = [];
for(var i=0; i<this.loginStates.length; i++) {
var loginState = this.loginStates[i];
var availability = enyo.messaging.imLoginState.getAvailability(loginState);
if(availability <= enyo.messaging.im.availability.BUSY) {
mergeItems.push({_id: loginState._id, customMessage: message});
}
}
if (mergeItems.length > 0) {
this.$.stateMerger.call({objects: mergeItems});
}
},
_getAvailabilityMergeString: function(avail) {
return {query: {from: enyo.messaging.imLoginState.dbKind}, props: {availability: avail}};
}
});
enyo.kind({
name: "AccountLoginStateDB",
kind: BaseLoginStateDB,
published: {
accountId: undefined
},
updateCustomMessage: function(message) {
this.$.stateMerger.call({objects: [{_id: this.accountId, customMessage: message}]});
},
_getAvailabilityMergeString: function(avail) {
return {objects: [{_id: this.accountId, availability: avail}]};
}
});
@@ -0,0 +1,164 @@
/*globals enyo */
enyo.kind({
name: "PreferencesView",
className: "enyo-bg",
kind: "VFlexBox",
published: {
accounts: null
},
events: {
onClosePreferences: ""
},
components: [
{kind: "Pane", flex:1, components: [
{name: "prefsAndAccounts", kind: "VFlexBox", components: [
{kind: "Toolbar", className:"enyo-toolbar-light accounts-header", pack:"center", components: [
{kind: "Image", src: "images/messaging-48x48.png"},
{kind: "Control", content: $L("Preferences & Accounts")}
]},
{className:"accounts-header-shadow"},
{kind: "Scroller",flex:1, components:[
{kind:"Control", className:"box-center", components: [
{kind: "RowGroup", caption: $L("New Message"), className:"accounts-group", components: [
{layoutKind: "enyo.HFlexLayout", align: "center", tapHighlight: false, components: [
{content: $L("Show Notifications"), flex: 1},
{kind: "ToggleButton", onChange: "showNotificationChange"}
]},
{kind: "ListSelector", label: $L("ALERT"), onChange: "notificationTypeChange", items: [
{caption: $L("Vibrate"), value:"vibrate"},
{caption: $L("System Sound"), value: "system"},
{caption: $L("Ringtone"), value: "ringtone"},
{caption: $L("Mute"), value: "mute"}
]},
{tapHighlight:true, kind: "HFlexBox", onclick:"showRingtonePicker", components: [
{name:"ringtoneName", content: $L("Tap to pick a ringtone"), flex: 1},
{content: $L("ringtone"), style: "color: rgb(31, 117, 191);text-transform: uppercase; "}
]}
]},
{name: "accountgroup", kind: "RowGroup", caption: $L("Accounts"), className:"accounts-group", components: [
{kind: "Accounts.accountsList", name: "accountsList", onAccountsList_AccountSelected: "editAccount"}
]},
{kind: "Button", caption: AccountsUtil.BUTTON_ADD_ACCOUNT, onclick: "AddAccount", className:"accounts-btn"}/*,
{kind: "ConnectPhone", onComplete: "exitAccountPrefs"}*/
]}
]},
{className:"accounts-footer-shadow"},
{kind:"Toolbar", className:"enyo-toolbar-light", components:[
{kind: "Button", caption: $L("Done"), className:"enyo-button-dark accounts-toolbar-btn", onclick: "exitAccountPrefs"}
]}
]},
{kind: "AccountsUI", name: "accountsView", capability: "MESSAGING", onAccountsUI_Done: "accountsDone"},
{kind: "AccountsModify", name: "accountsModify", capability: "MESSAGING", onAccountsModify_Done: "accountsDone"}
]},
{name: "ringtonepicker", kind: "FilePicker", fileType: ["ringtone"], onPickFile: "ringtoneChosen"}
],
create: function() {
this.inherited(arguments);
this.$.rowGroup.hide();
//this.$.connectPhone.subscriptToTelephony();
},
rendered: function(){
this.$.accountsList.getAccountsList(this.capability, "com.palm.palmprofile");
if (enyo.application.prefsHandler) {
enyo.application.prefsHandler.register(this, this.prefsUpdated.bind(this));
}
},
exitAccountPrefs: function(){
this.doClosePreferences();
},
//account stuff
capability: "MESSAGING",
// "Add Account" button was tapped
AddAccount: function(button) {
this.$.accountsView.AddAccount(enyo.application.accountService.getAccountTemplates());
this.$.pane.selectViewByName("accountsView");
},
// User tapped on account to edit
editAccount: function(inSender, inResults) {
this.$.accountsModify.ModifyAccount(inResults.account, inResults.template, this.capability);
this.$.pane.selectViewByName("accountsModify");
},
// Go to the prefs and accounts view
accountsDone: function(inSender, e) {
this.$.pane.selectViewByName("prefsAndAccounts");
},
prefsUpdated: function(inPrefs) {
//enyo.log("---------##### preferences view: " ,inPrefs);
if (!inPrefs) {
return;
}
this._prefs = inPrefs;
this.updatePrefsUI();
},
updatePrefsUI: function(){
this.$.toggleButton.setState(this._prefs.enableNotification);
this.$.listSelector.setValue(this._prefs.notificationSound);
if (this._prefs.enableNotification) {
this._showNotification();
}
else {
this._hideNotification();
}
this.$.rowGroup.show();
},
_hideNotification: function(){
this.$.rowGroup.hideRow(1);
this.$.rowGroup.hideRow(2);
},
_showNotification: function(){
this.$.rowGroup.showRow(1);
if (this._prefs.notificationSound === "ringtone") {
if (this._prefs.ringtone && this._prefs.ringtone.name) {
this.$.ringtoneName.setContent(this._prefs.ringtone.name);
}
this.$.rowGroup.showRow(2);
}
else {
this.$.rowGroup.hideRow(2);
}
},
showNotificationChange: function(inSender, inState){
if(inState){
this._showNotification();
}
else{
this._hideNotification();
}
this._prefs.enableNotification = inState;
//this._dbMerge({enableNotification:inState});
this.mergePrefs();
},
notificationTypeChange: function(inSender, inNewValue, inOldValue){
if(inNewValue === "ringtone"){
this.$.rowGroup.showRow(2);
}
else{
this.$.rowGroup.hideRow(2);
}
this._prefs.notificationSound = inNewValue;
this.mergePrefs();
//this._dbMerge({notificationSound:inNewValue});
},
mergePrefs: function() {
enyo.application.prefsHandler.setPrefs(this._prefs);
},
showRingtonePicker: function(){
this.$.ringtonepicker.pickFile();
},
ringtoneChosen: function(inSender, inRingtone){
if (inRingtone.length > 0) {
this.$.ringtoneName.setContent(inRingtone[0].name);
this._prefs.ringtone.name = inRingtone[0].name;
this._prefs.ringtone.fullPath = inRingtone[0].fullPath;
this.mergePrefs();
}
},
unloadHandler: function(){
// enyo.log("-------unregistering PreferencesView from PrefsHandler");
enyo.application.prefsHandler.unregister(this);
}
});
@@ -0,0 +1,82 @@
enyo.kind({
name: "PrefsHandler",
kind: enyo.Component,
published: {
prefs: undefined
},
components: [
{kind: "DbService", dbKind: "com.palm.app.messagingprefs:1", components: [
{name: "prefsGetter", method: "find", onSuccess: "_gotPrefs", subscribe: true, resubscribe: true, reCallWatches: true},
{name: "prefsPutter", method: "put"},
{name: "prefsMerger", method: "merge"}
]},
{name: "mockDbFinder", kind: "MockDb", dbKind: "prefs/com.palm.app.messagingprefs:1", method: "find", onSuccess: "_gotPrefs", subscribe: true, resubscribe: true, reCallWatches: true}
],
create: function() {
this.inherited(arguments);
this._getPrefs();
this._notifier = new Notifier(this.prefs);
},
_getPrefs: function() {
if (window.PalmSystem) {
this.$.prefsGetter.cancel();
this.$.prefsGetter.call();
} else {
this.$.mockDbFinder.call();
}
},
_gotPrefs: function(inSender, inResponse){
if (!inResponse || !inResponse.results || inResponse.results.length === 0) {
// no preferences exist, so create a default preferences record in db.
enyo.log("******** Messaging prefs doesn't exist in database, so create default prefs in database");
this.prefs = this._getDefaultPrefs();
this._putPrefs();
} else {
this.prefs = inResponse.results[0];
// remove db system properties
delete this.prefs._kind;
delete this.prefs._rev;
delete this.prefs._sync;
// migrate first launch mode setting from preferences into account service
if (this.prefs.firstUseMode !== undefined && this.prefs.firstUseMode !== null) {
enyo.application.firstLaunchHandler.migratePrefsSettingIntoAccountService(this.prefs);
}
this._notify();
}
},
_getDefaultPrefs: function() {
return {
"_kind": "com.palm.app.messagingprefs:1",
"enableNotification": true,
"notificationSound":"system",
"ringtone": {},
"isHistoryViewSelected": true,
"showOnlineBuddiesOnly": true,
"useImmediateMmsRetrieval": true,
"firstUseMode": true
};
},
prefsChanged: function() {
this.$.prefsMerger.call({
objects: [this.prefs]
});
},
_putPrefs: function() {
this.$.prefsPutter.call({
objects: [this.prefs]
});
},
_notify: function() {
//enyo.log("------------##### notifying prefs changes: ", this.prefs);
this._notifier.notify(this.prefs);
},
register: function(listener, callback) {
this._notifier.register(listener, callback);
},
unregister: function(listener) {
this._notifier.unregister(listener);
}
});
@@ -0,0 +1,87 @@
enyo.kind({
name: "DeleteThreadService",
kind: "enyo.Component",
published: {
id: null
},
components:[
{name: "dbDelete", kind: "DbService", dbKind: "com.palm.db", method: "del"},
{name: "dbMerge", kind: "DbService", dbKind: "com.palm.db", method: "merge"},
{name: "messageServiceFind", kind: "DbService", dbKind: "com.palm.message:1", method: "find", onSuccess: "gotMessageForDelete"}
],
deleteThread: function() {
if (!this.id) {
enyo.log("Unable to delete chat thread since no id is provided");
return;
}
// Inform the DashboardManagers that this thread has been removed.
enyo.application.messageDashboardManager.removeThread(this.id);
enyo.application.inviteDashboardManager.removeThread(this.id);
this.$.dbDelete.call({
ids: [this.id]
});
this.deleteMessageForThread();
},
deleteMessageForThread: function(){
this.$.messageServiceFind.call({
query: {
where: [{
prop: "conversations",
op: "=",
val: this.id
}],
select: [
"_id",
"_kind",
"conversations"
]
}
});
},
gotMessageForDelete: function(inSender, inResponse){
if (inResponse.returnValue && inResponse.results && inResponse.results.length > 0) {
var message;
for (var i = 0; i < inResponse.results.length; i++) {
message = inResponse.results[i];
var params = this.getMergeParams(message);
if (params && params.objects) {
this.$.dbMerge.call(params);
}
else {
if (message._kind === "com.palm.mmsmessage:1") {
//todo: delete attachments for mms on phone
//future = enyo.messaging.MMS.deleteAttachments(message._id);
}
//delete message
this.$.dbDelete.call({
ids: [message._id]
});
}
}
}
},
/***********************************
* Functions below are unit tested *
***********************************/
getMergeParams: function(message){
if (message.conversations.length > 1) {
//need merge the db by remove chatThreadId
var newConversations = [];
for (var j = 0; j < message.conversations.length; j++) {
if (message.conversations[j] !== this.id) {
newConversations.push(message.conversations[j]);
}
}
return {
objects: [{
"_id": message._id,
"conversations": newConversations
}]
};
}
return undefined;
}
});
@@ -0,0 +1,91 @@
/*globals enyo */
enyo.kind({
name: "ThreadItem",
kind: "SwipeableItem",
confirmCaption: $L("Delete"),
layoutKind: "HFlexLayout",
components: [
{components: [
{className: "contact-image-border"},
{name: "contactImage", kind: "Image", className: "contact-image"}
]},
{kind: "VFlexBox", className: "status-box", pack: "justify", align: "center", components: [
{name: "status", className: "status"},
{name: "outgoing", className: "sent-received"}
]},
{kind: "VFlexBox", className: "message-summary", flex: 1, components: [
{layoutKind: "HFlexLayout", align: "center", className:"contact-name-box", components: [
{name: "displayName", className: "contact-name"},
{name: "unreadCount", className: "unread-count", showing: false}
]},
{name: "summary", className: "message-preview", allowHtml:true}
]}
],
create: function() {
this.inherited(arguments);
this.addClass("contactItem");
},
updateStatus: function(inStatus) {
this.$.status.setClassName(this.getStatusClassName(inStatus));
},
updateUnreadCount: function(inThread){
var inUnreadCount = inThread.unreadCount;
var hasUnread = !this.isThreadSelected(inThread) && Number(inUnreadCount) > 0;
this.$.unreadCount.setContent(inUnreadCount || 0);
this.$.unreadCount.setShowing(hasUnread);
this.$.displayName.setClassName("contact-name" + (hasUnread ? " contact-name-unread" : ""));
},
updateContactImage: function(inPerson) {
this.$.contactImage.setAttribute("src", enyo.messaging.person.getDisplayImage(inPerson));
},
updateOutgoing: function(inFlags) {
this.$.outgoing.setClassName("sent-received " + (Boolean(inFlags && inFlags.outgoing) ? "message-outgoing" : ""));
},
setThread: function(inThread) {
this.updateStatus(inThread.status);
this.updateContactImage(inThread.person);
this.updateUnreadCount(inThread);
this.updateOutgoing(inThread.flags);
if (inThread.person) {
var displayName = enyo.messaging.person.getDisplayName(inThread.person);
this.$.displayName.setContent(displayName);
}
else {
this.$.displayName.setContent(inThread.displayName ? inThread.displayName : inThread.replyAddress);
}
var summary = this.getThreadSummary(inThread);
this.$.summary.setContent(summary);
},
/***********************************
* Functions below are unit tested *
***********************************/
getStatusClassName: function(inStatus) {
var status = inStatus && (inStatus.offline ? "offline" : enyo.messaging.im.availabilityClasses[inStatus.availability]);
var statusClass = !status ? "" : " status-" + status;
return "status" + statusClass;
},
getThreadSummary: function(inThread){
var summary;
if (enyo.messaging.message.isMMSThread(inThread)) {
summary = enyo.messaging.message.getMMSThreadSummary();
} else if(inThread.summary){
if (inThread.flags && inThread.flags.outgoing) {
// only outgoing messages needs to be escaped since it is not
// sanitized.
summary = enyo.string.escapeHtml(inThread.summary);
} else {
summary = inThread.summary;
}
}
else {
summary = "";
}
return summary;
},
isThreadSelected: function(inThread) {
return (enyo.application.selectedThread && inThread._id === enyo.application.selectedThread._id && enyo.application.messageDashboardManager.getAppDeactivated() === false);
}
});
@@ -0,0 +1,149 @@
enyo.kind({
name: "ThreadList",
kind: "VFlexBox",
events: {
onSelectThread: "",
onDeleteThread: "",
onThreadLocked: "",
onUnreadCountChanged: ""
},
components: [
{kind: "ThreadService", onSuccess: "gotThreads", onWatch: "threadsWatch"},
{name: "deleteService", kind: "DeleteThreadService"},
{name: "search", kind: "SearchInput", hint: $L("Search"), className:"enyo-middle", onchange: "filterList", onCancel: "filterList", changeOnInput: true, autoCapitalize: "lowercase", onfocus: "disableKeyboardMannualMode"},
{className:"header-shadow header-app-shadow"},
{name: "emptyMessage", content: "", className:"messageTexts", showing: false},
{flex: 1, name: "list", kind: "DbList", desc: true, onQuery: "listQuery", className:"messaging-listsDivider", onSetupRow: "listSetupRow", components: [
{kind: "Divider", className:"threadList-Divider"},
{kind: "ThreadItem", tapHighlight:true, onConfirm: "swipeDelete", onclick: "selectThread", onCancel: "disableKeyboardMannualMode"}
]}
],
disableKeyboardMannualMode: function(){
enyo.messaging.keyboard.setKeyboardAutoMode();
},
filterList: function(inSender, inValue){
this.filterString = this.$.search.getValue();
this.$.list.punt();
},
listQuery: function(inSender, inQuery) {
if (this.filterString && this.filterString.length > 0) {
inQuery.where = [{prop: "flags.visible", op: "=", val: true},{prop: "displayName", op:"%", val:this.filterString, collate: "primary", "tokenize":"all"}];
inQuery.search = true;
}
else{
inQuery.where = [{
prop: "flags.visible",
op: "=",
val: true
}];
}
inQuery.orderBy = "timestamp";
return this.$.threadService.call({query: inQuery});
},
gotThreads: function(inSender, inResponse, inRequest) {
this.$.list.queryResponse(inResponse, inRequest);
if ((inRequest.index === 0) && (inResponse.returnValue && inResponse.results && inResponse.results.length === 0)){
if (this.filterString && this.filterString.length > 0) {
// No threads found using search
this.showEmptyMessage(enyo.messaging.CONSTANTS.NO_SEARCH_RESULTS);
} else {
this.showEmptyMessage($L("Your conversation list is empty."));
}
} else {
this.$.emptyMessage.hide();
}
var cnt = 0;
if (inResponse && inResponse.results) {
for (var i = 0; i < inResponse.results.length; i++) {
var thread = inResponse.results[i];
if (!enyo.application.selectedThread || thread._id !== enyo.application.selectedThread._id || enyo.application.messageDashboardManager.getAppDeactivated() === true) {
cnt += thread.unreadCount;
}
}
}
//enyo.log("***************** total unread count in thread list: ", cnt);
this.doUnreadCountChanged(cnt);
},
showEmptyMessage: function(message) {
this.$.emptyMessage.setContent(message);
this.$.emptyMessage.show();
},
threadsWatch: function() {
this.$.list.reset();
},
selectThread: function(inSender, inEvent) {
this.selectedRecord = this.$.list.fetch(inEvent.rowIndex);
if(enyo.application.selectedThread && this.selectedRecord && enyo.application.selectedThread._id === this.selectedRecord._id){
//tap on current selected record, do nothing
return;
}
if (this.selectedRecord) {
this.$.list.refresh();
this.doSelectThread(this.selectedRecord);
}
},
setupDivider: function(inThread, inIndex) {
var date = new Date(inThread.timestamp);
var caption = BucketDateFormatter.getDateBucket(date);
var pt = this.$.list.fetch(inIndex - 1);
var previousCaption = pt && BucketDateFormatter.getDateBucket(new Date(pt.timestamp));
var showDivider = caption != previousCaption;
if (showDivider) {
this.$.divider.setCaption(caption);
this.$.threadItem.addClass("enyo-first");
}
this.$.divider.canGenerate = showDivider;
// this.$.threadItem.applyStyle("border-top", Boolean(showDivider) ? "none" : "1px solid silver;");
},
listSetupRow: function(inSender, inThread, inIndex) {
// Do not perform model changes in this rendering method
if (this.selectedRecord && this.selectedRecord._id === inThread._id && this.selectedRecord.flags.locked !== inThread.flags.locked){
this.doThreadLocked(inThread);
}
this.setupDivider(inThread, inIndex);
this.$.threadItem.setThread(inThread);
// make to the row to be selected if it is selected by user
this.$.threadItem.addRemoveClass("enyo-item-selected", this.selectedRecord && this.selectedRecord._id === inThread._id ? true : false);
},
swipeDelete: function(inSender, inIndex) {
enyo.messaging.keyboard.setKeyboardAutoMode();
var record = this.$.list.fetch(inIndex);
if (record && record._id) {
this.$.deleteService.setId(record._id);
this.$.deleteService.deleteThread();
this.doDeleteThread(record);
}
},
setSelection: function(inChatThread){
if (this.selectedRecord && inChatThread && this.selectedRecord._id === inChatThread._id) {
//case selection didn't change, do nothing
return;
}
else {
this.selectedRecord = inChatThread;
// if (!inChatThread) {
this.$.list.refresh();
// }
}
},
updateList: function() {
this.$.list.update();
},
resetList: function() {
this.$.list.reset();
},
windowHiddenHandler: function(){
// dump data buffer
this.$.list.punt();
// cancel service call
//this.$.threadService.cancel();
// clear out filter search
this.filterString = "";
this.$.search.setValue(this.filterString);
this.$.search.forceBlur();
}
});
@@ -0,0 +1,118 @@
enyo.kind({
name: "ThreadService",
kind: enyo.Service,
requestKind: "ThreadRequest",
events: {
onWatch: ""
}});
enyo.kind({
name: "ThreadRequest",
kind: enyo.Request,
events: {
onWatch: "doWatch"
},
components: [
{kind: "DbService", onFailure: "fail", components: [
{dbKind: "com.palm.chatthread:1", onSuccess: "gotThreads", components:[
{name: "threads", method: "find", subscribe: true, onWatch: "doWatch"/*, resubscribe: true, reCallWatches: true*/},
{name: "threadsSearch", method: "search", subscribe: true, onWatch: "doWatch"/*, resubscribe: true, reCallWatches: true*/}
]},
{name: "people", dbKind: "com.palm.person:1", method: "find", onSuccess: "gotPeople", onFailure: "gotPeople", subscribe: true, onWatch: "doWatch"/*, resubscribe: true, reCallWatches: true*/}
]},
{name: "status", kind: enyo.TempDbService, dbKind: "com.palm.imbuddystatus:1", method: "find", onSuccess: "gotStatus", subscribe: true, onWatch: "doWatch"/*, resubscribe: true, reCallWatches: true*/},
{name: "mockThreads", kind: "MockDb", dbKind: "chatthreads/com.palm.chatthread:1", method: "find", onSuccess: "gotThreads", onFailure: "fail", onWatch: "doWatch"},
{name: "mockBuddies", kind: "MockDb", dbKind: "buddies/com.palm.imbuddystatus:1", method: "find", onSuccess: "gotStatus", onFailure: "fail", onWatch: "doWatch"},
{name: "mockPeople", kind: "MockDb", dbKind: "persons/com.palm.person:1", method: "find", onSuccess: "gotPeople", onFailure: "fail", onWatch: "doWatch"}
],
create: function() {
this.inherited(arguments);
},
initComponents: function() {
this.inherited(arguments);
if (!window.PalmSystem) {
this.$.threads = this.$.mockThreads;
this.$.buddies = this.$.mockBuddies;
this.$.people = this.$.mockPeople;
}
},
finish: function() {
// don't destroy automatically, so we can see watches
},
call: function() {
if (this.params && this.params.query && this.params.query.search) {
this.params.query.search = undefined;
this.$.threadsSearch.cancel();
this.$.threadsSearch.call(this.params);
}
else{
this.$.threads.cancel();
this.$.threads.call(this.params);
}
},
reply: function() {
this.receive(this.data);
},
fail: function(inSender, inResponse) {
enyo.log(inResponse);
this.reply();
},
gotThreads: function(inSender, inResponse) {
this.data = inResponse;
this.assembleIds();
//query people and status at same time, so the list will be updated which ever returned early, might cause to refresh list one more time though
this.queryPeople();
this.queryStatus();
},
assembleIds: function() {
// acquire ids for merging person data
this.pids = [];
for (var i=0, d; d=this.data.results[i]; i++) {
if (d.personId) {
this.pids.push(d.personId);
}
}
},
queryPeople: function() {
// query person data
var where = [{
prop: "_id",
op: "=",
val: this.pids
}];
this.$.people.id = "threadRequest_people";
this.$.people.cancel();
this.$.people.call({query: {
where: where,
select: enyo.messaging.person.selectAttributes
}});
},
gotPeople: function(inSender, inResponse){
if (inResponse.results && inResponse.results.length > 0) {
// merge linked data
enyo.messaging.utils.joinData(this.data, inResponse.results, "_id", "personId", "person");
}
this.reply();
},
queryStatus: function() {
// query buddy status data
var where = [{
prop: "personId",
op: "=",
val: this.pids
}];
this.$.status.id = "threadRequest_status";
this.$.status.cancel();
this.$.status.call({query: {
where: where
}});
},
gotStatus: function(inSender, inResponse){
// merge linked data
if (inResponse.results && inResponse.results.length > 0) {
enyo.messaging.utils.joinData(this.data, inResponse.results, "personId", "personId", "status");
}
this.reply();
}
});
+22
View File
@@ -0,0 +1,22 @@
{
"id": "com.palm.app.messaging",
"version": "3.0.6606",
"uiRevision": 2,
"vendor": "HP",
"type": "web",
"main": "nowindow.html",
"noWindow": true,
"removable": false,
"title": "Messaging",
"icon": "icon.png",
"splashicon": "icon-256x256.png",
"keywords": ["Text","SMS","MMS","IM","Instant","Chat"],
"universalSearch":{
"action":{
"displayName": "New Message",
"url":"com.palm.app.messaging",
"launchParam":{"compose":{"messageText":"#{searchTerms}"}}
}
},
"requiredPermissions": ["activities.manage", "applications.internal", "database", "database.internal", "networking", "networking.internal", "system", "services", "settings"]
}
Binary file not shown.
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash -e
#cd trunk
source $HOME/.rvm/scripts/rvm && source .rvmrc
# install bundler if necessary
gem list --local bundler | grep bundler || gem install bundler || exit 1
# debugging info
echo USER=$USER && ruby --version && which ruby && which bundle
# conditionally install project gems from Gemfile
bundle check || bundle install || exit 1
IS_CI_BOX=true bundle exec rake jasmine:ci
@@ -0,0 +1,28 @@
enyo.kind({
name: "TelephonyService",
kind: enyo.Component,
connected: false,
components: [
{name: "telephony", kind: "PalmService", service: "palm://com.palm.telephony/", method: "platformQuery", onSuccess: "gotPlatformData", subscribe: true}
],
create: function() {
this.inherited(arguments);
this.$.telephony.call();
this._notifier = new Notifier(this.connected);
},
gotPlatformData: function(inSender, inResponse) {
enyo.log("------------ platformDate: ", inResponse);
this.connected = inResponse && inResponse.extended && inResponse.extended.capabilities && inResponse.extended.capabilities.mapcenable;
this._notify();
},
_notify: function() {
//enyo.log("------------##### notifying phone connected changes: ", this.connected);
this._notifier.notify(this.connected);
},
register: function(listener, callback) {
this._notifier.register(listener, callback);
},
unregister: function(listener) {
this._notifier.unregister(listener);
}
});
@@ -0,0 +1,256 @@
enyo.kind({
name: "accountService",
kind: "PalmService",
components: [
{kind: "Accounts.getAccounts", name: "listAccounts", onGetAccounts_AccountsAvailable: "gotAccounts"}
],
create: function() {
this.inherited(arguments);
this.data = [];
this._cleanedImAccounts = undefined;
this._accountTypes = undefined;
this._accountTemplates = undefined;
this._notifier = new Notifier();
},
getCleanedImAccounts: function(){
this.marshallCleanedImAccounts();
return this._cleanedImAccounts;
},
marshallCleanedImAccounts: function(){
// enyo.log("------------accountService::marshallCleanedImAccounts this._cleanedImAccounts: ", this._cleanedImAccounts);
if (!this._cleanedImAccounts) {
// enyo.log("------------accountService::marshallCleanedImAccounts::this.data:", this.data);
if(!this.data || this.data.length === 0){
enyo.warn("------------accountService::marshallCleanedImAccounts::should wait for app to load and get accounts info");
return;
}
this._cleanedImAccounts = [];
this._accountTypes = {};
for (var i=0, a; a=this.data[i]; i++) {
this.cleanAccount(a);
}
// enyo.log("------------accountService::marshallCleanedImAccounts before return, got: ", this._cleanedImAccounts.length);
}
},
getChatWithNonBuddies: function(serviceName){
var accountTypes = this.getMyAccountTypesHash();
if(!accountTypes){
return null;
}
var account = accountTypes[serviceName];
if (account) {
return account.chatWithNonBuddies;
}
else {
return null;
}
},
getIcons: function(serviceName){
var accountTypes = this.getMyAccountTypesHash();
if(!accountTypes){
return null;
}
var account = accountTypes[serviceName];
if (account && account.icon) {
return account.icon;
}
else {
return null;
}
},
getDbKinds: function(serviceName) {
var dbkind;
var accountTypes = this.getMyAccountTypesHash();
if(!accountTypes){
return null;
}
var account = accountTypes[serviceName];
if (account === undefined || account.dbkinds === undefined) {
enyo.warn("accountService::getDbKinds::Warning, no account for ", serviceName, " returning empty object");
return {};
} else {
return account.dbkinds;
}
},
getAccountTemplates: function(){
return this._accountTemplates;
},
cleanAccount: function(account) {
id = account._id;
for (var i=0, c; c = account.capabilityProviders[i]; i++) {
if (c.capability === "MESSAGING") {
this.cleanCapability(account, c);
break;
}
}
},
cleanCapability: function(account, cap) {
cap.accountId = account._id; // "2+Mr",
cap.templateId = account.templateId; // "com.palm.aol",
cap.username = account.username; // "flyingsquirrel",
cap.alias = account.alias; // "flyingsquirrel @ AIM",
if (!cap.loc_name) {
cap.loc_name = account.loc_name || "";
}
if (cap.loc_shortName === undefined) {
//enyo.log("-------capability.loc_shortName undefined. using part of loc_name", cap.loc_name);
cap.loc_shortName = cap.loc_name.substr(0, 5);
}
this._cleanedImAccounts.push(cap);
//enyo.log("----------capability.serviceName:", cap.serviceName);
if (cap.capabilitySubtype === "SMS") {
//ensure that a serviceName is specified for the SMS capability
cap.serviceName = "sms";
this._accountTypes.sms = cap;
this._accountTypes.mms = cap; // MMS is also this capability
} else if (cap.serviceName !== undefined && this._accountTypes[cap.accountId] === undefined) {
if (this._accountTypes[cap.serviceName] === undefined) {//for generic info such icons for service type
this._accountTypes[cap.serviceName] = cap;
}
// enyo.log("----------add accountId:", cap.accountId, " to this._accountTypes[]");
this._accountTypes[cap.accountId] = cap;
}
else{
enyo.warn("Duplicate capabilities: ---------cap.serviceName should be undefined:", cap.serviceName, " this._accountTypes[cap.accontId] exist:", this._accountTypes[cap.accountId]);
}
},
getAccount: function(serviceName, username){
//enyo.log("---------acountService::getAccount:servicename:", serviceName, " username:", username, " this._clearnedImAccounts:", this._cleanedImAccounts.length)
if (this._cleanedImAccounts) {
// Phone numbers can have multiple servicenames so normalize it to "sms"
if (enyo.messaging.utils.isTextMessage(serviceName)) {
serviceName = "sms";
}
for (var i=0, a; a=this._cleanedImAccounts[i]; i++) {
// enyo.log("--------------accountService::getAccount::account.serviceName:", a.serviceName);
if (serviceName === a.serviceName) {
//enyo.log("--------------accountService::getAccount::account:", a);
if (username === undefined || username === this._cleanedImAccounts[i].username) {
return this._cleanedImAccounts[i];
}
}
}
}
return undefined;
},
getMyAccountTypesHash: function(){
//enyo.log("---------acountService::getMyAccountTypesHash::this._accountTypes:", this._accountTypes);
this.marshallCleanedImAccounts();
return this._accountTypes;
},
getAccounts: function(){
// enyo.log("-----------------messagingAccountService::getAccounts");
//include com.palm.palmprofile here, so transportpicker will include sms
if (window.PalmSystem || enyo.messaging.utils.getAppRootPath().indexOf("http") !== -1) {
this.$.listAccounts.getAccounts({
capability: "MESSAGING"
});
} else {
this.getMockAccounts();
}
},
gotAccounts: function(inSender, inResponse) {
// enyo.log("-----------------messagingAccountService::gotAccounts inResponse.accounts.length:", inResponse.accounts.length);
if (inResponse.accounts.length > 0) {
this.data = inResponse.accounts;
this._cleanedImAccounts = undefined;
this._accountTypes = undefined;
this.getCleanedImAccounts();
}
if(inResponse.templates){
this._accountTemplates = inResponse.templates;
}
this.notify();
},
getMockAccounts: function() {
var data = enyo.g11n.Utils.getNonLocaleFile({
root: enyo.messaging.utils.getAppRootPath(),
path: "mock/accounts/com.palm.messaging.accounts-1.json"
});
this.gotAccounts(this, data);
},
getImCommandKind: function(serviceName){
var dbkinds = this.getDbKinds(serviceName);
if (dbkinds.imcommand) {
return dbkinds.imcommand;
} else {
enyo.warn("No imcommand dbkind for ", serviceName ,", so using default");
return "com.palm.imcommand:1";
}
},
getImAccounts: function() {
var accounts = this.getCleanedImAccounts(); // may contain SMS account
var ims = [];
if (accounts) {
for (var i = 0; i < accounts.length; i++) {
if (accounts[i].capabilitySubtype === "IM") {
ims.push(accounts[i]);
}
}
}
// enyo.log("-----------------messagingAccountService::gotImAccounts: ", ims);
return ims;
},
notify: function() {
this._notifier.notify();
},
register: function(listener, callback) {
this._notifier.register(listener, callback);
},
unregister: function(listener) {
this._notifier.unregister(listener);
}
});
/*
IMAccounts:[]
from capabilityProviders:
"_id": "260",
"capability": "MESSAGING",
"id": "com.palm.yahoo.im",
"capabilitySubtype": "IM",
"dbkinds": {
"imcommand": "com.palm.imcommand.yahoo:1",
"immessage": "com.palm.immessage.yahoo:1"
},
"icon": {
"loc_32x32": "\/usr\/palm\/public\/accounts\/com.palm.yahoo\/images\/yim-32x32.png",
"loc_48x48": "\/usr\/palm\/public\/accounts\/com.palm.yahoo\/images\/yim-48x48.png",
"splitter": "\/usr\/palm\/public\/accounts\/com.palm.yahoo\/images\/yim-32x32.png"
},
"implementation": "palm:\/\/com.palm.imyahoo\/",
"invites": {
"supportsCustomMessage": true
},
"loc_shortName": "Yahoo!",
"onCredentialsChanged": "palm:\/\/com.palm.imyahoo\/onCredentialsChanged",
"onEnabled": "palm:\/\/com.palm.imyahoo\/onEnabled",
"serviceName": "type_yahoo"
from accounts:
capability.accountId = account._id; // "2+Mr",
capability.templateId = account.templateId; // "com.palm.aol",
capability.username = account.username; // "flyingsquirrel",
capability.alias = account.alias; // "flyingsquirrel @ AIM",
if (!capability.loc_name) {
capability.loc_name = account.loc_name || "";
}
if (capability.loc_shortName === undefined) {
Mojo.Log.warn("capability.loc_shortName undefined. using part of loc_name", capability.loc_name);
capability.loc_shortName = capability.loc_name.substr(0,5);
}
this._cleanedImAccounts.push(capability);
if (capability.capabilitySubtype === "SMS") {
//ensure that a serviceName is specified for the SMS capability
if (capability.serviceName !== "sms") {
capability.serviceName = "sms";
}
this._accountTypes.sms = capability;
this._accountTypes.mms = capability; // MMS is also this capability
*/
+3
View File
@@ -0,0 +1,3 @@
[0612/142510.117:ERROR:exit_code_watcher_win.cc(87)] Failed to wait for process exit or stop event
[0612/142510.245:ERROR:registration_protocol_win.cc(131)] TransactNamedPipe: The pipe has been ended. (0x6D)
[0612/142638.699:ERROR:registration_protocol_win.cc(131)] TransactNamedPipe: The pipe has been ended. (0x6D)
+83
View File
@@ -0,0 +1,83 @@
enyo.depends(
"$enyo/palm/list2/",
"$enyo/g11n/phone/",
"$enyo-lib/systemui/",
"$enyo-lib/accounts/",
"$enyo-lib/contactsui/",
"$enyo-lib/addressing/",
"$enyo-lib/networkalerts/",
"data/accountService.js",
"data/TelephonyService.js",
"utilities/utils.js",
"app/AppInit.js",
"app/AddBuddyDialog.js",
"app/ChatButton.js",
"app/DefaultView.js",
// "app/ImStatus.js",
"app/MessagingApp.js",
"app/Messaging.js",
"app/PopupDialog.js",
"app/ChatView.js",
"app/buddies/BuddyConstants.js",
"app/buddies/BuddyItem.js",
"app/buddies/BuddyList.js",
"app/buddies/BuddyService.js",
"app/buddies/DeleteBuddyService.js",
"app/compose/ComposeView.js",
"app/compose/ConnectPhoneDialog.js",
"app/compose/ImTransportList.js",
"app/conversations/BlockPersonService.js",
"app/conversations/ConversationItem.js",
"app/conversations/ConversationList.js",
"app/conversations/ConversationService.js",
"app/conversations/InviteResponseService.js",
"app/conversations/TransportSelector.js",
"app/conversations/transportPicker.js",
"app/dashboards/BannerThrottler.js",
"app/dashboards/Class0AlertManager.js",
"app/dashboards/DashboardManager.js",
"app/dashboards/InviteDashboardManager.js",
"app/dashboards/InviteWatcher.js",
"app/dashboards/MessageDashboardManager.js",
"app/dashboards/MessageDashboardUtil.js",
"app/firstlaunch/FirstLaunchConstants.js",
"app/firstlaunch/ConnectPhone.js",
"app/firstlaunch/FirstLaunch.js",
"app/firstlaunch/FirstLaunchHandler.js",
"app/favorites/FavoriteConstants.js",
"app/favorites/FavoriteItem.js",
"app/favorites/FavoriteList.js",
"app/favorites/FavoriteService.js",
"app/imstatus/AccountStatuses.js",
"app/imstatus/AccountLoginState.js",
"app/imstatus/AccountLoginStatesPopup.js",
"app/imstatus/ImStatus.js",
"app/imstatus/LoginStateServices.js",
"app/preferences/PrefsHandler.js",
"app/preferences/PreferencesView.js",
"app/threads/DeleteThreadService.js",
"app/threads/ThreadItem.js",
"app/threads/ThreadList.js",
"app/threads/ThreadService.js",
"utilities/UtilsFormatter.js",
"utilities/BucketDateFormatter.js",
"utilities/Notifier.js",
"utilities/Launcher.js",
"stylesheets/app.css",
"stylesheets/conversation.css",
"stylesheets/preferences.css"
);
+4
View File
@@ -0,0 +1,4 @@
{
"localEnyoRoot": "../",
"ciEnyoRoot": "/usr/palm/frameworks/"
}
@@ -0,0 +1,3 @@
{
"logLevel": 0
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 848 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Loaded 100 of 137 files, more files were not shown because too many files have changed in this diff. Show more