Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,641 @@
'use strict';
/* jshint nonew: false */
/* global ActivityHandler */
/* global COMMS_APP_ORIGIN */
/* global Contacts */
/* global ContactsButtons */
/* global ContactPhotoHelper */
/* globals ContactToVcardBlob */
/* global fb */
/* global NFC */
/* global ICEData */
/* global LazyLoader */
/* global MozActivity */
/* global Normalizer */
/* global SCALE_RATIO */
/* global TAG_OPTIONS */
/* global utils */
/* global VcardFilename */
/* global MatchService */
/* global WebrtcClient */
/* global MainNavigation */
/* global ContactsService */
var contacts = window.contacts || {};
contacts.Details = (function() {
var photoPos = 7;
var initMargin = 8;
var DEFAULT_TEL_TYPE = 'other';
// If it is a favourite on/off change, I cancel the render
var isAFavoriteChange = false;
var contactData,
contactDetails,
listContainer,
detailsName,
detailsNameText,
orgTitle,
datesTemplate,
addressesTemplate,
socialTemplate,
duplicateTemplate,
notesTemplate,
isFbContact,
isFbLinked,
editContactButton,
cover,
favoriteMessage,
detailsInner,
dom,
currentSocial,
header,
_;
var init = function cd_init(currentDom) {
_ = navigator.mozL10n.get;
dom = currentDom || document;
header = dom.querySelector('#details-view-header');
contactDetails = dom.querySelector('#contact-detail');
listContainer = dom.querySelector('#details-list');
detailsName = dom.querySelector('#contact-name-title');
detailsNameText = dom.querySelector('#contact-name-title bdi');
orgTitle = dom.querySelector('#org-title');
datesTemplate = dom.querySelector('#dates-template-\\#i\\#');
addressesTemplate = dom.querySelector('#address-details-template-\\#i\\#');
socialTemplate = dom.querySelector('#social-template-\\#i\\#');
duplicateTemplate = dom.querySelector('#duplicate-contacts-template');
editContactButton = dom.querySelector('#edit-contact-button');
cover = dom.querySelector('#cover-img');
detailsInner = dom.querySelector('#contact-detail-inner');
favoriteMessage = dom.querySelector('#toggle-favorite');
notesTemplate = dom.querySelector('#note-details-template-\\#i\\#');
initPullEffect(cover);
// to avoid race conditions with NFC, we load it before handleDetails
LazyLoader.load('/contacts/js/nfc.js', () => {
favoriteMessage.addEventListener('click', toggleFavorite);
editContactButton.addEventListener('click', showEditContact);
header.addEventListener('action', handleDetailsBack);
});
ContactsButtons.init(listContainer, contactDetails, ActivityHandler);
};
var getWebrtcClientResources = function getWebrtcClientResources(cb) {
if (typeof cb !== 'function') {
return;
}
LazyLoader.load(
[
'/contacts/style/webrtc-client/webrtc_client.css',
'/contacts/js/webrtc-client/webrtc_client.js'
],
cb
);
};
var handleDetailsBack = function handleDetailsBack() {
// disable NFC listeners when going out of Details view
stopNFC();
if (WebrtcClient) {
getWebrtcClientResources(WebrtcClient.stop);
}
if (ActivityHandler.currentActivityIsNot(['import'])) {
ActivityHandler.postCancel();
MainNavigation.home();
}
else if (window.ICEView && window.ICEView.iceListDisplayed) {
ICEData.getActiveIceContacts().then(function(list) {
if (!Array.isArray(list) || list.length === 0) {
MainNavigation.home();
}
else {
doHandleDetailsBack();
}
}, doHandleDetailsBack);
}
else {
doHandleDetailsBack();
}
};
var doHandleDetailsBack = function() {
var hashParams = window.location.hash.split('?');
var params = hashParams.length > 1 ?
utils.extractParams(hashParams[1]) : -1;
// post message to parent page included Contacts app.
if (params.back_to_previous_tab === '1') {
var message = { 'type': 'contactsiframe', 'message': 'back' };
window.parent.postMessage(message, COMMS_APP_ORIGIN);
} else {
MainNavigation.back(resetPhoto);
}
};
var showEditContact = function showEditContact() {
// Disable NFC listeners when editing a contact
stopNFC();
Contacts.showForm(true, contactData);
};
var setContact = function cd_setContact(currentContact) {
contactData = currentContact;
startNFC(currentContact);
};
// Needed for now because of external call from contacts.js
var startNFC = function(contact) {
LazyLoader.load('/contacts/js/nfc.js', () => {
NFC.startListening(contact);
});
};
var stopNFC = function() {
LazyLoader.load('/contacts/js/nfc.js', () => {
NFC.stopListening();
});
};
var initPullEffect = function cd_initPullEffect(cover) {
var maxPosition = Math.round(150 * SCALE_RATIO);
var startPosition = 0;
function onTouchStart(e) {
if (contactDetails.classList.contains('no-photo')) {
return;
}
e.preventDefault();
startPosition = e.changedTouches[0].clientY;
contactDetails.classList.add('up');
cover.classList.add('up');
window.addEventListener('touchmove', onTouchMove, true);
window.addEventListener('touchend', onTouchEnd, true);
}
function onTouchEnd(e) {
e.preventDefault();
contactDetails.style.transform = null;
contactDetails.classList.add('up');
cover.style.transform = null;
cover.classList.add('up');
window.removeEventListener('touchmove', onTouchMove, true);
window.removeEventListener('touchend', onTouchEnd, true);
}
function onTouchMove(e) {
e.preventDefault();
var deltaY = e.changedTouches[0].clientY - startPosition;
deltaY = Math.min(maxPosition, Math.max(0, deltaY));
var calc = 'calc(' + initMargin + 'rem + ' + deltaY + 'px)';
contactDetails.style.transform = 'translateY(' + calc + ')';
contactDetails.classList.remove('up');
// Divide by 40 (4 times slower and in rems)
var coverPosition = (-photoPos + (deltaY / 40)) + 'rem';
cover.style.transform = 'translateY(' + coverPosition + ')';
cover.classList.remove('up');
}
cover.addEventListener('touchstart', onTouchStart, true);
};
// readOnly tells us if we should allow editing the rendered contact.
var render = function cd_render(currentContact, fbContactData, readOnly) {
if(isAFavoriteChange){
isAFavoriteChange = false;
return Promise.resolve(isAFavoriteChange);
}
contactData = currentContact || contactData;
startNFC(contactData);
isFbContact = fb.isFbContact(contactData);
isFbLinked = fb.isFbLinked(contactData);
// Initially enabled and only disabled if necessary
editContactButton.removeAttribute('disabled');
editContactButton.classList.remove('hide');
header.setAttribute('action', 'back');
socialTemplate.classList.remove('hide');
if (readOnly) {
editContactButton.classList.add('hide');
header.setAttribute('action', 'close');
socialTemplate.classList.add('hide');
}
if (!fbContactData && isFbContact) {
var fbContact = new fb.Contact(contactData);
var req = fbContact.getData();
req.onsuccess = function do_reload() {
doReloadContactDetails(req.result);
};
req.onerror = function() {
window.console.error('FB: Error while loading FB contact data');
doReloadContactDetails(contactData);
};
} else {
doReloadContactDetails(fbContactData || contactData);
}
};
// Fills the contact data to display if no givenName and familyName
var getDisplayName = function getDisplayName(contact) {
var name = _('noName');
if (hasName(contact)) {
name = contact.name[0];
} else if (hasContent(contact.tel)) {
name = contact.tel[0].value;
} else if (hasContent(contact.email)) {
name = contact.email[0].value;
}
return name;
};
function hasContent(field) {
return (Array.isArray(field) &&
field.length > 0 &&
field[0].value &&
field[0].value.trim());
}
function hasName(contact) {
return (Array.isArray(contact.name) &&
contact.name[0] &&
contact.name[0].trim());
}
//
// Method that generates HTML markup for the contact
//
var doReloadContactDetails = function doReloadContactDetails(contact) {
detailsNameText.textContent = getDisplayName(contact);
contactDetails.classList.remove('no-photo');
contactDetails.classList.remove('fb-contact');
contactDetails.classList.remove('up');
utils.dom.removeChildNodes(listContainer);
renderFavorite(contact);
renderOrg(contact);
ContactsButtons.renderPhones(contact);
ContactsButtons.renderEmails(contact);
renderWebrtcClient(contactData);// Don't share the FB info
renderAddresses(contact);
renderDates(contact);
renderNotes(contact);
renderShareButton(contact);
if (!fb.isFbContact(contact) || fb.isFbLinked(contact)) {
renderDuplicate(contact);
}
renderPhoto(contact);
};
var renderFavorite = function cd_renderFavorite(contact) {
var favorite = isFavorite(contact);
toggleFavoriteMessage(favorite);
header.classList.toggle('favorite', !!favorite);
};
var isFavorite = function isFavorite(contact) {
return contact != null && contact.category != null &&
contact.category.indexOf('favorite') != -1;
};
var toggleFavorite = function toggleFavorite() {
var contact = contactData;
var favorite = !isFavorite(contact);
toggleFavoriteMessage(favorite);
if (favorite) {
contact.category = contact.category || [];
contact.category.push('favorite');
} else {
if (!contact.category) {
return;
}
var pos = contact.category.indexOf('favorite');
if (pos > -1) {
contact.category.splice(pos, 1);
}
}
// Disabling button while saving the contact
favoriteMessage.style.pointerEvents = 'none';
var promise = new Promise(function(resolve, reject) {
ContactsService.save(
utils.misc.toMozContact(contact),
function(e) {
if (e) {
favoriteMessage.style.pointerEvents = 'auto';
console.error('Error saving favorite');
reject('Error saving favorite');
resolve(false);
return;
}
isAFavoriteChange = true;
ContactsService.get(
contact.id,
function onSuccess(savedContact) {
renderFavorite(savedContact);
setContact(savedContact);
favoriteMessage.style.pointerEvents = 'auto';
},
function onError() {
console.error('Error reloading contact');
favoriteMessage.style.pointerEvents = 'auto';
}
);
resolve(isAFavoriteChange);
}
);
}).then();
return promise;
};
var toggleFavoriteMessage = function toggleFavMessage(isFav) {
var cList = favoriteMessage.classList;
var l10nId = isFav ? 'removeFavorite' : 'addFavorite';
favoriteMessage.setAttribute('data-l10n-id', l10nId);
isFav ? cList.add('on') : cList.remove('on');
};
var renderOrg = function cd_renderOrg(contact) {
if (contact.org && contact.org.length > 0 && contact.org[0] !== '') {
orgTitle.textContent = contact.org[0];
orgTitle.classList.remove('hide');
} else {
orgTitle.classList.add('hide');
orgTitle.textContent = '';
}
};
var renderDates = function cd_renderDates(contact) {
if (!contact.bday && !contact.anniversary) {
return;
}
var dateString = '';
var fields = ['bday', 'anniversary'];
var l10nIds = ['birthday', 'anniversary'];
var rendered = 0;
for (var j = 0; j < fields.length; j++) {
var value = contact[fields[j]];
if (!value) {
continue;
}
try {
dateString = utils.misc.formatDate(value);
} catch (err) {
console.error('Error parsing date');
continue;
}
var element = utils.templates.render(datesTemplate, {
i: ++rendered,
date: dateString,
type: _(l10nIds[j])
});
listContainer.appendChild(element);
}
};
var renderShareButton = function cd_renderShareButton(contact) {
var social = utils.templates.render(socialTemplate, {
i: contact.id
});
currentSocial = social;
var shareButton = social.querySelector('#share_button');
shareButton.addEventListener('click', shareContact);
shareButton.classList.remove('hide');
listContainer.appendChild(social);
};
var renderWebrtcClient = function renderWebrtcClient(contact) {
getWebrtcClientResources(function onLoaded() {
WebrtcClient.start(contact);
});
};
var renderAddresses = function cd_renderAddresses(contact) {
if (!contact.adr) {
return;
}
for (var i = 0; i < contact.adr.length; i++) {
var currentAddress = contact.adr[i];
// Sanity check
if (utils.mozContact.haveEmptyFields(currentAddress,
['streetAddress', 'postalCode', 'locality', 'countryName'])) {
continue;
}
var address = currentAddress.streetAddress || '';
var escapedStreet = Normalizer.escapeHTML(address, true);
var locality = currentAddress.locality;
var escapedLocality = Normalizer.escapeHTML(locality, true);
var escapedType = Normalizer.escapeHTML(currentAddress.type, true);
var country = currentAddress.countryName || '';
var escapedCountry = Normalizer.escapeHTML(country, true);
var postalCode = currentAddress.postalCode || '';
var escapedPostalCode = Normalizer.escapeHTML(postalCode, true);
var addressField = {
streetAddress: escapedStreet,
postalCode: escapedPostalCode,
locality: escapedLocality || '',
countryName: escapedCountry,
type: _(escapedType) || escapedType ||
TAG_OPTIONS['address-type'][0].value,
'type_l10n_id': currentAddress.type,
i: i
};
var template = utils.templates.render(addressesTemplate, addressField);
listContainer.appendChild(template);
}
};
var renderDuplicate = function cd_renderDuplicate(contact) {
var dupItem = utils.templates.render(duplicateTemplate, {});
var findMergeButton = dupItem.querySelector('#find-merge-button');
findMergeButton.disabled = true;
if (contacts.List.total > 1) {
// Only have this active if contact list has more than one entry
findMergeButton.disabled = false;
findMergeButton.addEventListener('click', function finding() {
MatchService.match(contact.id);
});
}
listContainer.appendChild(dupItem);
};
var renderNotes = function cd_rederNotes(contact) {
if (!contact.note || contact.note.length === 0) {
return;
}
var container = document.createElement('li');
var title = document.createElement('h2');
title.setAttribute('data-l10n-id', 'comments');
container.appendChild(title);
for (var i = 0; i < contact.note.length; i++) {
var currentNote = contact.note[i];
var noteField = {
note: Normalizer.escapeHTML(currentNote, true) || '',
i: i
};
var template = utils.templates.render(notesTemplate, noteField);
container.appendChild(template);
listContainer.appendChild(container);
}
};
var calculateHash = function calculateHash(photo, cb) {
var START_BYTES = 127;
var BYTES_HASH = 16;
var out = [photo.type, photo.size];
// We skip the first bytes that typically are headers
var chunk = photo.slice(START_BYTES, START_BYTES + BYTES_HASH);
var reader = new FileReader();
reader.onloadend = function() {
out.push(reader.result);
cb(out.join(''));
};
reader.onerror = function() {
window.console.error('Error while calculating the hash: ',
reader.error.name);
cb(out.join(''));
};
reader.readAsDataURL(chunk);
};
var updateHash = function updateHash(photo, cover) {
calculateHash(photo, function(hash) {
cover.dataset.imgHash = hash;
});
};
var renderPhoto = function cd_renderPhoto(contact) {
contactDetails.classList.remove('up');
if (isFbContact) {
contactDetails.classList.add('fb-contact');
}
var photo = ContactPhotoHelper.getFullResolution(contact);
if (photo) {
var currentHash = cover.dataset.imgHash;
if (!currentHash) {
utils.dom.updatePhoto(photo, cover);
updateHash(photo, cover);
}
else {
// Need to recalculate the hash and see whether the images changed
calculateHash(photo, function(newHash) {
if (currentHash !== newHash) {
utils.dom.updatePhoto(photo, cover);
cover.dataset.imgHash = newHash;
}
else {
// Only for testing purposes
cover.dataset.photoReady = 'true';
}
});
}
contactDetails.classList.add('up');
cover.classList.add('translated');
contactDetails.classList.add('translated');
var clientHeight = contactDetails.clientHeight -
(initMargin * 10 * SCALE_RATIO);
if (detailsInner.offsetHeight < clientHeight) {
cover.style.overflow = 'hidden';
} else {
cover.style.overflow = 'auto';
}
} else {
resetPhoto();
}
};
var resetPhoto = function cd_resetPhoto() {
cover.classList.remove('translated');
contactDetails.classList.remove('translated');
cover.style.backgroundImage = '';
cover.style.overflow = 'auto';
contactDetails.style.transform = '';
contactDetails.classList.add('no-photo');
cover.dataset.imgHash = '';
};
var shareContact = function cd_shareContact() {
const VCARD_DEPS = [
'/shared/js/text_normalizer.js',
'/shared/js/contact2vcard.js',
'/shared/js/setImmediate.js'
];
LazyLoader.load(VCARD_DEPS,function vcardLoaded() {
ContactToVcardBlob([contactData], function blobReady(vcardBlob) {
VcardFilename(contactData).then(filename => {
new MozActivity({
name: 'share',
data: {
type: 'text/vcard',
number: 1,
blobs: [new window.File([vcardBlob], filename, {
type: 'text/x-vcard'
})],
filenames: [filename]
}
});
});
// The MIME of the blob should be this for some MMS gateways
}, { type: 'text/x-vcard'} );
});
};
return {
'init': init,
'setContact': setContact,
'toggleFavorite': toggleFavorite,
'render': render,
'defaultTelType': DEFAULT_TEL_TYPE,
'startNFC': startNFC
};
})();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,108 @@
/* global ICEData */
/* global ImageLoader */
/* global LazyLoader */
/* global MainNavigation */
'use strict';
(function(exports) {
var contactsIds = null;
var rowBuilderFn = null;
var imgLoader = null;
var initialized = false;
var iceListDisplayed = false;
/**
* Initialises the component by passing a list of ICE ids,
* a function that knows how to build contact list rows and
* another function to perform actions when clicking on a
* ICE contact.
* This function should be the same than the contacts list one
* to keep the same behavior (activities, select, etc.)
* @param ids (Array) array of contact ids
* @param rowBuilder (function) function that paints contac list
* rows, shared with the contact list
* @param clickHandler (function) action to perform when clicked
* on a item
*/
function init(ids, rowBuilder, clickHandler) {
rowBuilderFn = rowBuilder;
contactsIds = ids;
imgLoader = new ImageLoader('#ice-list', 'li');
buildICEContactsList();
if (initialized) {
return;
}
initialized = true;
// Initialise common controls
document.getElementById('ice-header').addEventListener(
'action', hideICEList);
document.getElementById('ice-list').addEventListener('click', clickHandler);
LazyLoader.load(['/contacts/js/utilities/ice_data.js'], function() {
listenForChanges();
});
}
// Once the list is built, we need to be aware of possible changes in
// contacts. In that case rebuild the whole list, since ICE list is meant
// to be a small set (2 currently) of contacts.
function listenForChanges() {
ICEData.listenForChanges(function(newList) {
contactsIds = [];
newList.forEach(function(iceContact) {
if (iceContact.id && iceContact.active) {
contactsIds.push(iceContact.id);
}
});
buildICEContactsList();
});
}
// Clone the nodes on the contacts list, also rebuild them with
// the row builder function passed on initialization.
// Due to the nature of the contacts list, despite of having the
// nodes, those may have not being initialized, that's why we need
// the use of this builder function
function buildICEContactsList() {
var iceList = document.getElementById('ice-group');
iceList.innerHTML = '';
contactsIds.forEach(function(id) {
var node = document.querySelector('[data-uuid="' + id + '"]');
if (!node) {
return;
}
node = rowBuilderFn(id, node);
iceList.appendChild(node);
});
imgLoader.reload();
}
function showICEList() {
MainNavigation.go('ice-view', 'right-left');
iceListDisplayed = true;
}
function hideICEList() {
MainNavigation.back();
iceListDisplayed = false;
}
var ICEView = {
init: init,
showICEList: showICEList,
hideICEList: hideICEList,
get iceListDisplayed() {
return iceListDisplayed;
}
};
exports.ICEView = ICEView;
})(window);

View file

@ -0,0 +1,354 @@
/* global Contacts */
/* global ICEData */
/* global ConfirmDialog */
/* global ContactsService */
/* global HeaderUI */
/* global Search */
/**
* ICE Settings view. In charge of selecting
* the contacts for emergency and keeping sync
* the datastore to share those contacts with
* dialer and lockscreen.
*/
'use strict';
var contacts = window.contacts || {};
(function(exports) {
var iceSettingsPanel,
iceSettingsHeader,
iceContactItems = [],
iceContactCheckboxes = [],
iceContactButtons = [],
iceScreenInitialized = false,
currentICETarget;
/**
* Redraws the ICE contact list. This is not an expensive
* operation since we have a maximun of 2 contacts.
* The first time executed will attach listeners for the
* frame ui.
*/
var init = function ice_init() {
if (iceScreenInitialized) {
return;
}
// ICE DOM elements
iceSettingsPanel = document.getElementById('ice-settings');
iceSettingsHeader = document.getElementById('ice-settings-header');
iceContactItems.push(document.getElementById('ice-contacts-1-switch'));
iceContactItems.push(document.getElementById('ice-contacts-2-switch'));
iceContactCheckboxes.push(iceContactItems[0]
.querySelector('[name="ice-contact-1-enabled"]'));
iceContactCheckboxes.push(iceContactItems[1]
.querySelector('[name="ice-contact-2-enabled"]'));
iceContactButtons.push(document.getElementById('select-ice-contact-1'));
iceContactButtons.push(document.getElementById('select-ice-contact-2'));
iceContactButtons[0].dataset.contactId = '';
iceContactButtons[1].dataset.contactId = '';
// ICE Events handlers
iceSettingsHeader.addEventListener('action', function(){
contacts.Settings.navigation.back();
});
// All the controls do the same, just modifications on the
// specific order.
iceContactItems.forEach(function(item, index) {
iceContactCheckboxes[index].addEventListener('change', function(i) {
return function(evt) {
var localIceContacts = ICEData.iceContacts;
var wasActive = !iceContactCheckboxes[i].checked;
if (wasActive) {
resetIceGroupState(i);
if (localIceContacts[i] && localIceContacts[i].id) {
setICEContact(null, i, !wasActive);
}
}
else {
iceContactButtons[i].disabled = false;
}
};
}(index));
});
iceContactButtons.forEach(function(element){
element.addEventListener('click', function(evt) {
showSelectList(evt.target.id);
});
// Bug 1071064 If users clicked on disabled button, the switch was checked
element.parentNode.addEventListener('click', (evt) => {
evt.stopPropagation();
});
});
iceScreenInitialized = true;
};
function reloadButtonsState(cb) {
ICEData.load().then(retrieveContactData.bind(null, cb));
}
function retrieveContactData(cb) {
var iceContactsData = [];
var iceContactsIds = ICEData.iceContacts;
var numRetrievedContacts = 0;
iceContactsIds.forEach(function(iceContact, index) {
ContactsService.get(iceContact.id, function(cindex, contact) {
var theContact = {
active: iceContactsIds[cindex].active,
mozContact: contact
};
iceContactsData[cindex] = theContact;
numRetrievedContacts++;
if (numRetrievedContacts === 2) {
setButtonsState(iceContactsData, cb);
}
}.bind(null, index));
});
}
function refresh(done) {
if (!iceScreenInitialized) {
init();
}
reloadButtonsState(done);
}
/**
* Given an object representing the internal state
* fills the UI elements.
* @params iceContactsIds (Array) list of contacts and state
*/
function setButtonsState(iceContactsData, done) {
iceContactsData.forEach(setIceButtonState);
typeof done === 'function' && done();
}
function setIceButtonState(iceContactData, index) {
if (!iceContactData) {
return;
}
if (iceContactData.mozContact) {
var iceContact = iceContactData.mozContact;
var givenName = (Array.isArray(iceContact.givenName) &&
iceContact.givenName[0]) || '';
var familyName = (Array.isArray(iceContact.familyName) &&
iceContact.familyName[0]) || '';
var display = [givenName, familyName];
var iceLabel = display.join(' ').trim();
// If contact has no name we the first tel number will be used
if (!iceLabel) {
if (Array.isArray(iceContact.tel) && iceContact.tel[0]) {
iceLabel = iceContact.tel[0].value.trim();
}
}
buildIceContactUI(index, iceLabel, iceContact.id,
iceContactData.active);
}
else {
resetIceGroupState(index);
}
}
function buildIceContactUI(index, label, contactId, active) {
iceContactCheckboxes[index].checked = active;
iceContactButtons[index].disabled = !active;
var bdi = document.createElement('bdi');
bdi.classList.add('ice-contact');
bdi.textContent = label;
iceContactButtons[index].innerHTML = '';
iceContactButtons[index].appendChild(bdi);
iceContactButtons[index].dataset.contactId = contactId;
}
function resetIceGroupState(index) {
iceContactCheckboxes[index].checked = false;
iceContactButtons[index].disabled = true;
iceContactButtons[index].innerHTML = '';
iceContactButtons[index].dataset.contactId = '';
iceContactButtons[index].setAttribute('data-l10n-id', 'ICESelectContact');
}
function resetIceGroupStates() {
for(var j = 0; j < iceContactCheckboxes.length; j++) {
resetIceGroupState(j);
}
}
function goBack() {
contacts.List.clearClickHandlers();
contacts.List.handleClick(Contacts.showContactDetail);
HeaderUI.setNormalHeader();
var hasICESet = ICEData.iceContacts.find(function(x) {
return x.active === true;
});
if (!hasICESet) {
resetIceGroupStates();
}
if (window.Search && Search.isInSearchMode()) {
Search.exitSearchMode();
}
contacts.Settings.navigation.back(() => {
hasICESet && contacts.List.toggleICEGroup(true);
});
}
/**
* Given a contact id, saves it internally. Also restores the contact
* list default handler.
* In case of not valid contact it will display a message on the screen
* indicating the cause of the error.
* @param id (string) contact id
*/
function selectICEHandler(id) {
checkContact(id).then(function() {
setICEContact(id, currentICETarget, true, goBack);
}, function error(l10nId) {
var dismiss = {
title: 'ok',
callback: function() {
ConfirmDialog.hide();
}
};
ConfirmDialog.show(null, l10nId || 'ICEUnknownError', dismiss);
});
}
/**
* Will perform a series of checks to validate the selected
* contact as a valid ICE contact
* @param id (string) contact id
* @return (Promise) fulfilled if contact is valid
*/
function checkContact(id) {
return ICEData.load().then(function() {
return contactNotICE(id).then(contactNotAllowed);
});
}
/**
* Checks if a contacts is already set as ICE
* @param id (string) contact id
* @return (Promise) fulfilled if contact is not repeated,
* rejected otherwise
*/
function contactNotICE(id) {
return new Promise(function(resolve, reject) {
var isICE = ICEData.iceContacts.some(function(x) {
return x.id === id;
});
if (isICE) {
reject('ICERepeatedContact');
} else {
resolve(id);
}
});
}
/**
* Filter to avoid selecting contacts as ICE if they don't have a phone number
* or they are a Facebook contact
* @param id (String) contact id
* @returns (Promise) Fulfilled if contact has phone
*/
function contactNotAllowed(id) {
return new Promise(function(resolve, reject) {
ContactsService.get(id, function(contact) {
if(Array.isArray(contact.tel) && contact.tel[0] &&
contact.tel[0].value && contact.tel[0].value.trim()) {
resolve(id);
} else {
reject('ICEContactNoNumber');
}
});
});
}
/**
* We are using the contact list to choose a contact as ICE. For doing
* this, we need to setup our own click handler.
* @para target (HTMLButton) Button click to select an ICE contact
*/
function showSelectList(target) {
contacts.List.toggleICEGroup(false);
HeaderUI.setCancelableHeader(goBack, 'selectContact');
contacts.Settings.navigation.go('view-contacts-list', 'right-left');
currentICETarget = target === 'select-ice-contact-1' ? 0 : 1;
contacts.List.clearClickHandlers();
contacts.List.handleClick(selectICEHandler);
}
/**
* Set the values for ICE contacts, both in local and in the
* datastore
* @param id (string) contact id
* @param pos (int) current position (0,1)
* @param active (boolean) ice contact is active or not
* @param cb (function) callback when ready
*/
function setICEContact(id, pos, active, cb) {
ICEData.setICEContact(id, pos, active).then(function() {
// Only reload contact info in case there is a change in the contact
if (id === iceContactButtons[pos].dataset.contactId) {
return;
}
ContactsService.get(id, function(contact) {
var theContact = {
active: active,
mozContact: contact
};
var iceContactData = [];
if (pos === 0) {
iceContactData[1] = null;
}
else {
iceContactData[0] = null;
}
iceContactData[pos] = theContact;
setButtonsState(iceContactData);
typeof cb === 'function' && cb();
});
});
}
function reset() {
iceScreenInitialized = false;
iceContactItems = [];
iceContactCheckboxes = [];
iceContactButtons = [];
currentICETarget = null;
}
exports.ICE = {
init: init,
refresh: refresh,
reset: reset,
get initialized() { return iceScreenInitialized; }
};
})(window);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,148 @@
/* global Contacts, contacts, utils, LazyLoader */
/* global ContactsService */
/* global Matcher */
'use strict';
window.Contacts = window.Contacts || {};
Contacts.MultipleSelect = (function() {
const STATUS_TIMEOUT = 3000; // ms before closing view which shows status msg
var contactsToImport = [];
var header, saveButton, container, template, title;
/**
* Initializes references to DOM elements. This is needed before calling
* render.
*/
function init() {
header = document.getElementById('multiple-select-view-header');
saveButton = document.getElementById('save-button');
container = document.getElementById('multiple-select-container');
template = document.getElementById('contactToImportTemplate');
title = document.getElementById('multiple-select-view-title');
saveButton.addEventListener('click', importAll);
header.addEventListener('action', cancelHandler);
}
function cancelHandler() {
header.removeEventListener('action', cancelHandler);
saveButton.removeEventListener('click', importAll);
Contacts.cancel();
}
function importAll() {
var numDupsMerged = 0,
importedContacts = 0,
parsedContacts = 0;
const DEPENDENCIES = [
'/shared/js/contacts/import/utilities/status.js',
'/shared/js/simple_phone_matcher.js',
'/shared/js/contacts/contacts_matcher.js',
'/shared/js/contacts/contacts_merger.js',
'/shared/js/contacts/merger_adapter.js'
];
LazyLoader.load(DEPENDENCIES, function() {
contactsToImport.forEach((contact, index) => {
Matcher.match(contact, 'passive', {
onmatch: (matches) => {
var callbacks = {
success: () => {
numDupsMerged++;
doContinue(true);
},
error: doContinue
};
contacts.adaptAndMerge(contact, matches, callbacks);
},
onmismatch: () => {
ContactsService.save(
contact,
function(e) {
if (e) {
console.error(e);
doContinue();
return;
}
doContinue(true);
}
);
}
});
});
});
function doContinue(isContactImported) {
parsedContacts++;
if (isContactImported) {
importedContacts++;
}
if (contactsToImport.length <= parsedContacts) {
utils.status.show({
id: 'vCardContacts-imported',
args: {
n: importedContacts
}
}, {
id: 'contactsMerged',
args: {
numDups: numDupsMerged
}
});
window.setTimeout(cancelHandler, STATUS_TIMEOUT);
}
}
}
/**
* Allows to preview which contacts will be imported. Requires to call init
* first when DOM is ready.
*
* @param {mozContact[]} contacts - Every contact already read from vCard.
* @param cursor - Reader object to continue reading from vCard.
* @param {string} filename - Name of the vcf file we are reading from.
*/
var render = function(contacts, cursor, filename) {
container.innerHTML = ''; // Avoids rendering the same content twice.
title.textContent = filename;
contacts.forEach(doRenderContact);
cursor.onsuccess = function(event) {
var contact = event.target.result;
if (contact) {
doRenderContact(contact);
cursor.continue();
}
};
function doRenderContact(contact) {
var clone = document.importNode(template.content, true);
var contactData = clone.querySelectorAll('p');
contactData[0].textContent =
Array.isArray(contact.name) && contact.name[0];
var org = (Array.isArray(contact.org) && contact.org[0]) ?
contact.org[0] : '';
contactData[1].textContent = org;
if (Array.isArray(contact.photo) && contact.photo[0] instanceof Blob) {
var picture = clone.querySelector('aside span');
picture.style.backgroundImage = 'url(' +
window.URL.createObjectURL(contact.photo[0]) +
')';
}
contactsToImport.push(contact);
container.appendChild(clone);
}
};
return {
render: render,
init: init
};
})();

View file

@ -0,0 +1,856 @@
'use strict';
/* global _ */
/* global Cache */
/* global ConfirmDialog */
/* global Contacts */
/* global ContactsBTExport */
/* global ContactsExporter */
/* global ContactsSDExport */
/* global ContactsSIMExport */
/* global IccHandler */
/* global LazyLoader */
/* global navigationStack */
/* global Rest */
/* global SimContactsImporter */
/* global SimDomGenerator */
/* global utils */
/* global VCFReader */
/* global ContactsService */
/* global ExtServices */
/* global Loader */
/* global BulkDelete */
/* global ICE */
/* global Telemetry */
var contacts = window.contacts || {};
/***
This class handles all the activity regarding
the settings screen for contacts
**/
contacts.Settings = (function() {
var navigationHandler,
importSettingsHeader,
orderCheckBox,
orderItem,
orderByLastName,
setICEButton,
importSettingsPanel,
importSettingsTitle,
importContacts,
exportContacts,
importOptions,
exportOptions,
importLiveOption,
importGmailOption,
importSDOption,
exportSDOption,
newOrderByLastName = null,
PENDING_LOGOUT_KEY = 'pendingLogout',
bulkDeleteButton;
var EXPORT_TRANSITION_LEVEL = 2, DELETE_TRANSITION_LEVEL = 1;
// Initialise the settings screen (components, listeners ...)
var init = function initialize() {
// Create the DOM for our SIM cards and listen to any changes
IccHandler.init(new SimDomGenerator(), contacts.Settings.cardStateChanged);
initContainers();
// To avoid any race condition we listen for online events once
// containers have been initialized
window.addEventListener('online', checkOnline);
window.addEventListener('offline', checkOnline);
// Subscribe to events related to change state in the sd card
utils.sdcard.subscribeToChanges('check_sdcard', function(value) {
updateStorageOptions(utils.sdcard.checkStorageCard());
});
window.addEventListener('timeformatchange', updateTimestamps);
};
var hideSettings = function hideSettings() {
contacts.Settings.close();
};
// Get the different values that we will show in the app
var getData = function getData() {
var config = utils.cookie.load();
var order = config ? config.order : false;
orderByLastName = order;
newOrderByLastName = null;
updateOrderingUI();
};
var updateOrderingUI = function updateOrderingUI() {
var value = newOrderByLastName === null ? orderByLastName :
newOrderByLastName;
orderCheckBox.checked = value;
orderCheckBox.setAttribute('aria-checked', value);
};
var updateImportTitle = function updateImportTitle(l10nString) {
importSettingsTitle.setAttribute('data-l10n-id', l10nString);
};
// Initialises variables and listener for the UI
var initContainers = function initContainers() {
orderItem = document.getElementById('settingsOrder');
orderCheckBox = orderItem.querySelector('[name="order.lastname"]');
orderCheckBox.addEventListener('change', onOrderingChange.bind(this));
// Creating a navigation handler from this view
navigationHandler = new navigationStack('view-settings');
// Init panel & elements for caching them
importSettingsPanel = document.getElementById('import-settings');
importSDOption = document.getElementById('import-sd-option');
exportSDOption = document.getElementById('export-sd-option');
importSettingsTitle = document.getElementById('import-settings-title');
importLiveOption = document.getElementById('import-live-option');
importGmailOption = document.getElementById('import-gmail-option');
// ICE view
setICEButton = document.getElementById('set-ice');
/*
* Adding listeners
*/
document.getElementById('settings-close').addEventListener(
'click',
hideSettings
);
// Listener for updating the timestamp based on extServices
window.addEventListener('message', function updateList(e) {
if (e.data.type === 'import_updated') {
updateTimestamps();
checkNoContacts();
}
});
// Navigation back
importSettingsHeader = document.getElementById('import-settings-header');
importSettingsHeader.addEventListener('action', importSettingsBackHandler);
// Handlers for the navigation through the panels
importContacts = document.getElementById('importContacts');
importContacts.firstElementChild.
addEventListener('click', importContactsHandler);
exportContacts = document.getElementById('exportContacts');
exportContacts.firstElementChild.
addEventListener('click', exportContactsHandler);
// Handlers for the actions related with EXPORT/IMPORT
importOptions = document.getElementById('import-options');
importOptions.addEventListener('click', importOptionsHandler);
exportOptions = document.getElementById('export-options');
exportOptions.addEventListener('click', exportOptionsHandler);
// ICE view
setICEButton.addEventListener('click', showICEScreen);
// Bulk delete
bulkDeleteButton = document.getElementById('bulkDelete');
bulkDeleteButton.addEventListener('click', bulkDeleteHandler);
};
// UI event handlers
function importSettingsBackHandler() {
navigationHandler.back(function navigateBackHandler() {
// Removing the previous assigned style for having
// a clean view
importSettingsPanel.classList.remove('export');
importSettingsPanel.classList.remove('import');
});
}
function importContactsHandler() {
// Hide elements for export and transition
LazyLoader.load(['/contacts/js/utilities/telemetry.js'], () => {
importSettingsPanel.classList.remove('export');
importSettingsPanel.classList.add('import');
updateImportTitle('importContactsTitle');
navigationHandler.go('import-settings', 'right-left');
});
}
function exportContactsHandler() {
// Hide elements for import and transition
LazyLoader.load(['/contacts/js/export/contacts_exporter.js'], loadSearch);
function loadSearch() {
Loader.view('search', function() {
importSettingsPanel.classList.remove('import');
importSettingsPanel.classList.add('export');
updateImportTitle('exportContactsTitle');
navigationHandler.go('import-settings', 'right-left');
});
}
}
function showICEScreen(cb) {
LazyLoader.load([
'/contacts/js/utilities/ice_data.js',
'/contacts/js/views/ice_settings.js',
'/shared/js/contacts/utilities/ice_store.js'], function(){
ICE.refresh();
navigationHandler.go('ice-settings', 'right-left');
if (typeof cb === 'function') {
cb();
}
});
}
// Given an event, select wich should be the targeted
// import/export source
function getSource(e) {
var source = e.target.parentNode.dataset.source;
// Check special cases
if (source && source.indexOf('-') != -1) {
source = source.substr(0, source.indexOf('-'));
}
return source;
}
function logImportUsage(name) {
return Telemetry.logImportUsage(name);
}
function importOptionsHandler(e) {
/* jshint validthis:true */
var source = getSource(e);
switch (source) {
case 'sim':
var iccId = e.target.parentNode.dataset.iccid;
window.setTimeout(requireSimImport.bind(this,
onSimImport.bind(this, iccId)), 0);
break;
case 'sd':
window.setTimeout(requireOverlay.bind(this, onSdImport), 0);
break;
case 'gmail':
ExtServices.onContactsImported = logImportUsage.bind(null, 'gmail');
ExtServices.importGmail();
break;
case 'live':
ExtServices.onContactsImported = logImportUsage.bind(null, 'live');
ExtServices.importLive();
break;
}
}
var bulkDeleteHandler = function bulkDeleteHandler() {
LazyLoader.load(
[
'/contacts/js/contacts_bulk_delete.js',
'/contacts/js/contacts_remover.js'
],
function() {
Loader.view('search', function() {
contacts.List.selectFromList(_('DeleteTitle'),
function onSelectedContacts(promise, done) {
BulkDelete.performDelete(promise, done);
},
null,
navigationHandler,
{
transitionLevel: DELETE_TRANSITION_LEVEL
}
);
});
}
);
};
function exportOptionsHandler(e) {
var source = getSource(e);
switch (source) {
case 'sim':
var iccId = e.target.parentNode.dataset.iccid;
LazyLoader.load(['/contacts/js/export/sim.js'],
function() {
doExport(new ContactsSIMExport(IccHandler.getIccById(iccId)));
}
);
break;
case 'sd':
LazyLoader.load(
[
'/shared/js/device_storage/get_storage_if_available.js',
'/shared/js/device_storage/get_unused_filename.js',
'/shared/js/contact2vcard.js',
'/shared/js/setImmediate.js',
'/contacts/js/export/sd.js'
],
function() {
doExport(new ContactsSDExport());
}
);
break;
case 'bluetooth':
LazyLoader.load(
[
'/shared/js/device_storage/get_storage_if_available.js',
'/shared/js/device_storage/get_unused_filename.js',
'/shared/js/contact2vcard.js',
'/shared/js/setImmediate.js',
'/contacts/js/export/bt.js'
],
function() {
doExport(new ContactsBTExport());
}
);
break;
}
}
function doExport(strategy) {
// Launch the selection mode in the list, and then invoke
// the export with the selected strategy.
contacts.List.selectFromList(_('exportContactsAction'),
function onSelectedContacts(promise) {
// Resolve the promise, meanwhile show an overlay to
// warn the user of the ongoin operation, dismiss it
// once we have the result
requireOverlay(function _loaded() {
utils.overlay.show('preparing-contacts', null, 'spinner');
promise.onsuccess = function onSuccess(ids) {
// Once we start the export process we can exit from select mode
// This will have to evolve once export errors can be captured
contacts.List.exitSelectMode();
var exporter = new ContactsExporter(strategy);
exporter.init(ids, function onExporterReady() {
// Leave the contact exporter to deal with the overlay
exporter.start();
});
};
promise.onerror = function onError() {
contacts.List.exitSelectMode();
utils.overlay.hide();
};
});
},
null,
navigationHandler,
{
isDanger: false,
transitionLevel: EXPORT_TRANSITION_LEVEL
}
);
}
// Options checking & updating
var checkSIMCard = function checkSIMCard() {
var statuses = IccHandler.getStatus();
statuses.forEach(function onStatus(status) {
enableSIMOptions(status.iccId, status.cardState);
});
};
// Disables/Enables an option and show the error if needed
var updateOptionStatus =
function updateOptionStatus(domOption, disabled, error) {
if (domOption === null) {
return;
}
var optionButton = domOption.firstElementChild;
if (disabled) {
optionButton.setAttribute('disabled', 'disabled');
if (error) {
domOption.classList.add('error');
} else {
domOption.classList.remove('error');
}
} else {
optionButton.removeAttribute('disabled');
domOption.classList.remove('error');
}
};
// Disables/Enables the actions over the sim import functionality
var enableSIMOptions = function enableSIMOptions(iccId, cardState) {
var importSimOption = document.getElementById('import-sim-option-' + iccId);
var exportSimOption = document.getElementById('export-sim-option-' + iccId);
var disabled = (cardState !== 'ready' && cardState !== 'illegal');
updateOptionStatus(importSimOption, disabled, true);
updateOptionStatus(exportSimOption, disabled, true);
};
/**
* Disables/Enables the actions over the sdcard import/export functionality
* @param {Boolean} cardAvailable Whether functions should be enabled or not.
*/
var updateStorageOptions = function updateStorageOptions(cardAvailable) {
// Enable/Disable button and shows/hides error message
updateOptionStatus(importSDOption, !cardAvailable, true);
updateOptionStatus(exportSDOption, !cardAvailable, true);
var importSDErrorL10nId = null;
var exportSDErrorL10nId = null;
var cardShared = utils.sdcard.status === utils.sdcard.SHARED;
if (!cardAvailable) {
importSDErrorL10nId = 'noMemoryCardMsg';
exportSDErrorL10nId = 'noMemoryCardMsgExport';
if (cardShared) {
importSDErrorL10nId = exportSDErrorL10nId = 'memoryCardUMSEnabled';
}
}
// update the message
var importSDErrorNode = importSDOption.querySelector('p.error-message');
if (importSDErrorL10nId) {
importSDErrorNode.setAttribute('data-l10n-id', importSDErrorL10nId);
} else {
importSDErrorNode.removeAttribute('data-l10n-id');
importSDErrorNode.textContent = '';
}
var exportSDErrorNode = exportSDOption.querySelector('p');
if (exportSDErrorL10nId) {
exportSDErrorNode.setAttribute('data-l10n-id', exportSDErrorL10nId);
} else {
exportSDErrorNode.removeAttribute('data-l10n-id');
exportSDErrorNode.textContent = '';
}
};
/**
* Loads the overlay class before showing
*/
function requireOverlay(callback) {
Loader.utility('Overlay', callback);
}
/**
* Loads required libraries for sim import
*/
function requireSimImport(callback) {
var libraries = ['Overlay', 'Import_sim_contacts'];
var pending = libraries.length;
libraries.forEach(function onPending(library) {
Loader.utility(library, next);
});
function next() {
if (!(--pending)) {
callback();
}
}
}
function resetWait(wakeLock) {
Contacts.hideOverlay();
if (wakeLock) {
wakeLock.unlock();
}
}
// Listens for any change in the ordering preferences
var onOrderingChange = function onOrderingChange(evt) {
newOrderByLastName = orderCheckBox.checked;
updateOrderingUI();
};
// Import contacts from SIM card and updates ui
var onSimImport = function onSimImport(iccId, done) {
var icc = IccHandler.getIccById(iccId);
if (icc === null) {
return;
}
var progress = Contacts.showOverlay('simContacts-reading',
'activityBar');
var wakeLock = navigator.requestWakeLock('cpu');
var cancelled = false, contactsRead = false;
var importer = new SimContactsImporter(icc);
utils.overlay.showMenu();
utils.overlay.oncancel = function oncancel() {
cancelled = true;
importer.finish();
if (contactsRead) {
// A message about canceling will be displayed while the current chunk
// is being cooked
progress.setClass('activityBar');
utils.overlay.hideMenu();
progress.setHeaderMsg('messageCanceling');
} else {
importer.onfinish(); // Early return while reading contacts
}
};
var totalContactsToImport;
var importedContacts = 0;
// Delay for showing feedback to the user after importing
var DELAY_FEEDBACK = 200;
importer.onread = function import_read(n) {
contactsRead = true;
totalContactsToImport = n;
if (totalContactsToImport > 0) {
progress.setClass('progressBar');
progress.setHeaderMsg('simContacts-importing');
progress.setTotal(totalContactsToImport);
}
};
importer.onfinish = function import_finish(numDupsMerged) {
window.setTimeout(function onfinish_import() {
resetWait(wakeLock);
if (importedContacts > 0) {
var source = 'sim-' + iccId;
utils.misc.setTimestamp(source, function() {
// Once the timestamp is saved, update the list
updateTimestamps();
checkNoContacts();
});
}
if (!cancelled) {
utils.status.show({
id: 'simContacts-imported3',
args: {
n: importedContacts
}
},
!numDupsMerged ? null : {
id: 'contactsMerged',
args: {
numDups: numDupsMerged
}
});
} else {
logImportUsage('sim');
}
typeof done === 'function' && done();
}, DELAY_FEEDBACK);
importer.onfinish = null;
};
importer.onimported = function imported_contact() {
importedContacts++;
if (!cancelled) {
progress.update();
}
};
importer.onerror = function import_error() {
var cancel = {
title: 'cancel',
callback: function() {
ConfirmDialog.hide();
}
};
var retry = {
title: 'retry',
isRecommend: true,
callback: function() {
ConfirmDialog.hide();
// And now the action is reproduced one more time
window.setTimeout(requireSimImport.bind(this,
onSimImport.bind(this, iccId)), 0);
}
};
ConfirmDialog.show(null, 'simContacts-error', cancel, retry);
resetWait(wakeLock);
};
importer.start();
};
var onSdImport = function onSdImport(cb) {
var cancelled = false;
var importer = null;
var progress = Contacts.showOverlay(
'memoryCardContacts-importing',
'activityBar',
'infiniteProgress'
);
utils.overlay.showMenu();
utils.overlay.oncancel = function() {
cancelled = true;
importer ? importer.finish() : Contacts.hideOverlay();
};
var wakeLock = navigator.requestWakeLock('cpu');
var importedContacts = 0;
// Delay for showing feedback to the user after importing
var DELAY_FEEDBACK = 200;
utils.sdcard.retrieveFiles([
'text/vcard',
'text/x-vcard',
'text/directory;profile=vCard',
'text/directory'
], ['vcf', 'vcard'], function(err, fileArray) {
if (err) {
return import_error(err, cb);
}
if (cancelled) {
return;
}
if (fileArray.length) {
var promises = [];
fileArray.forEach(file => {
promises.push(utils.sdcard.getTextFromFile(file, onContacts));
});
Promise.all(promises).then(results => {
var numDupsMerged = results.reduce((sum, current) => {
return sum + current;
});
window.setTimeout(() => {
utils.misc.setTimestamp('sd', () => {
// Once the timestamp is saved, update the list
updateTimestamps();
checkNoContacts();
resetWait(wakeLock);
if (!cancelled) {
var msg1 = {
id: 'memoryCardContacts-imported3',
args: {
n: importedContacts
}
};
var msg2 = !numDupsMerged ? null : {
id: 'contactsMerged',
args: {
numDups: numDupsMerged
}
};
utils.status.show(msg1, msg2);
logImportUsage('sd');
if (typeof cb === 'function') {
cb();
}
}
});
}, DELAY_FEEDBACK);
}).catch(error => {
import_error(error);
});
} else {
import_error('No contacts were found.', cb);
}
});
function onContacts(text) {
if (cancelled) {
return Promise.reject();
}
return new Promise((resolve, reject) => {
importer = new VCFReader(text);
if (!text || !importer) {
var error = 'No contacts were found';
import_error(error);
reject(error);
return;
}
importer.onimported = imported_contact;
importer.onerror = error => {
import_error(error);
reject(error);
};
importer.process((unused, numDupsMerged) => {
if (cancelled) {
reject('Cancelled');
Contacts.hideOverlay();
return;
}
resolve(numDupsMerged);
});
});
}
function imported_contact() {
importedContacts++;
progress.update();
}
function import_error(e, cb) {
var cancel = {
title: 'cancel',
callback: function() {
ConfirmDialog.hide();
}
};
var retry = {
title: 'retry',
isRecommend: true,
callback: function() {
ConfirmDialog.hide();
// And now the action is reproduced one more time
window.setTimeout(requireOverlay.bind(this, onSdImport), 0);
}
};
ConfirmDialog.show(null, 'memoryCardContacts-error', cancel,
retry);
resetWait(wakeLock);
if (typeof cb === 'function') {
cb();
}
}
};
// Dismiss settings window and execute operations if values got modified
var close = function close() {
if (newOrderByLastName != null &&
newOrderByLastName != orderByLastName && contacts.List) {
contacts.List.setOrderByLastName(newOrderByLastName);
utils.cookie.update({order: newOrderByLastName});
Cache.evict();
// Force the reset of the dom, we know that we changed the order
contacts.List.load(null, true);
orderByLastName = newOrderByLastName;
}
Contacts.goBack();
};
var checkOnline = function() {
// Perform pending automatic logouts
window.setTimeout(automaticLogout, 0);
// Other import services settings
updateOptionStatus(importGmailOption, !navigator.onLine, true);
updateOptionStatus(importLiveOption, !navigator.onLine, true);
};
var checkNoContacts = function checkNoContacts() {
var exportButton = exportContacts.firstElementChild;
ContactsService.isEmpty(function(error, isEmpty) {
if (error) {
window.console.warn(
'Error while trying to know the contact number',
error
);
// In case of error is safer to leave enabled
exportButton.removeAttribute('disabled');
bulkDeleteButton.removeAttribute('disabled');
return;
}
if (isEmpty) {
exportButton.setAttribute('disabled', 'disabled');
bulkDeleteButton.setAttribute('disabled', 'disabled');
setICEButton.setAttribute('disabled', 'disabled');
} else {
exportButton.removeAttribute('disabled');
bulkDeleteButton.removeAttribute('disabled');
setICEButton.removeAttribute('disabled');
}
});
};
function saveStatus(data) {
window.asyncStorage.setItem(PENDING_LOGOUT_KEY, data);
}
function automaticLogout() {
if (navigator.offLine === true) {
return;
}
LazyLoader.load(['/shared/js/contacts/utilities/http_rest.js'],
function() {
window.asyncStorage.getItem(PENDING_LOGOUT_KEY, function(data) {
if (!data) {
return;
}
var services = Object.keys(data);
var numResponses = 0;
services.forEach(function(service) {
var url = data[service];
var callbacks = {
success: function logout_success() {
numResponses++;
window.console.log('Successfully logged out: ', service);
delete data[service];
if (numResponses === services.length) {
saveStatus(data);
}
},
error: function logout_error() {
numResponses++;
if (numResponses === services.length) {
saveStatus(data);
}
},
timeout: function logout_timeout() {
numResponses++;
if (numResponses === services.length) {
saveStatus(data);
}
}
};
Rest.get(url, callbacks);
});
});
});
}
var updateTimestamps = function updateTimestamps() {
// TODO Add the same functionality to 'EXPORT' methods when ready.
var importSources =
document.querySelectorAll('#import-options li[data-source]');
Array.prototype.forEach.call(importSources, function(node) {
utils.misc.getTimestamp(node.dataset.source,
function(time) {
var spanID = 'notImported';
if (time) {
spanID = 'imported';
var timeElement = node.querySelector('p > time');
timeElement.setAttribute('datetime',
(new Date(time)).toLocaleString());
timeElement.textContent = utils.time.pretty(time);
}
node.querySelector('p > span').setAttribute('data-l10n-id', spanID);
});
});
};
var refresh = function refresh() {
getData();
checkOnline();
checkSIMCard();
utils.sdcard.getStatus(function statusUpdated() {
updateStorageOptions(utils.sdcard.checkStorageCard());
});
updateTimestamps();
checkNoContacts();
};
return {
'init': init,
'close': close,
'refresh': refresh,
'cardStateChanged': checkSIMCard,
'updateTimestamps': updateTimestamps,
'showICEScreen' : showICEScreen,
get navigation() { return navigationHandler; },
'importFromSDCard': onSdImport,
'importFromSIMCard': onSimImport
};
})();