/** * Application logic that isn't specific to cards, specifically entailing * startup and mozSetMessageHandler message listening. **/ /*global globalOnAppMessage */ 'use strict'; define(function(require, exports, module) { var mozL10n = require('l10n!'), activityComposerData = require('activity_composer_data'), cards = require('cards'), evt = require('evt'), model = require('model_create').defaultModel, HeaderCursor = require('header_cursor'), htmlCache = require('html_cache'), waitingRawActivity, activityCallback; require('font_size_utils'); require('metrics'); require('wake_locks'); var started = false; function pushStartCard(id, addedArgs) { var args = { model: model }; // Mix in addedArgs to the args object that is passed to pushCard. Use a new // object in case addedArgs is reused again by the caller. if (addedArgs) { Object.keys(addedArgs).forEach(function(key) { args[key] = addedArgs[key]; }); } if (!started) { var cachedNode = cards._cardsNode.children[0]; // Add in cached node to use, if it matches the ID type. if (cachedNode && id === htmlCache.nodeToKey(cachedNode)) { // l10n may not see this as it was injected before l10n.js was loaded, // so let it know it needs to translate it. mozL10n.translateFragment(cachedNode); args.cachedNode = cachedNode; } //Set body class to a solid background, see bug 1077605. document.body.classList.add('content-visible'); } cards.pushCard(id, 'immediate', args); started = true; } // Handles visibility changes: if the app becomes visible after starting up // hidden because of an alarm, start showing some UI. document.addEventListener('visibilitychange', function onVisibilityChange() { if (!document.hidden && !started && startupData && startupData.entry === 'alarm') { pushStartCard('message_list'); } }, false); /* * Determines if current card is a nonsearch message_list * card, which is the default kind of card. */ function isCurrentCardMessageList() { var cardType = cards.getCurrentCardType(); return (cardType && cardType === 'message_list'); } // The add account UI flow is requested. evt.on('addAccount', function() { cards.removeAllCards(); // Show the first setup card again. pushStartCard('setup_account_info', { allowBack: true }); }); function resetApp() { // Clear any existing local state and reset UI/model state. activityCallback = waitingRawActivity = undefined; cards.removeAllCards(); model.init(false, function() { var cardId = model.hasAccount() ? 'message_list' : 'setup_account_info'; pushStartCard(cardId); }); } // An account was deleted. Burn it all to the ground and rise like a phoenix. // Prefer a UI event vs. a slice listen to give flexibility about UI // construction: an acctsSlice splice change may not warrant removing all the // cards. evt.on('accountDeleted', resetApp); evt.on('resetApp', resetApp); // Called when account creation canceled, most likely from setup_account_info. // Need to complete the activity postError flow if an activity is waiting, then // update the UI to the latest state. evt.on('setupAccountCanceled', function(fromCard) { if (waitingRawActivity) { waitingRawActivity.postError('cancelled'); } if (!model.foldersSlice) { // No account has been formally initialized, but one likely exists given // that this back button should only be available for cases that have // accounts. Likely just need the app to reset to load model. evt.emit('resetApp'); } else { cards.removeCardAndSuccessors(fromCard, 'animate', 1); } }); // A request to show the latest account in the UI. Usually triggered after an // account has been added. evt.on('showLatestAccount', function() { cards.removeAllCards(); model.latestOnce('acctsSlice', function(acctsSlice) { var account = acctsSlice.items[acctsSlice.items.length - 1]; model.changeAccount(account, function() { pushStartCard('message_list', { // If waiting to complete an activity, do so after pushing the message // list card. onPushed: function() { if (activityCallback) { var activityCb = activityCallback; activityCallback = null; activityCb(); return true; } return false; } }); }); }); }); evt.on('apiBadLogin', function(account, problem, whichSide) { switch (problem) { case 'bad-user-or-pass': cards.pushCard('setup_fix_password', 'animate', { account: account, whichSide: whichSide, restoreCard: cards.activeCardIndex }, 'right'); break; case 'imap-disabled': case 'pop3-disabled': cards.pushCard('setup_fix_gmail', 'animate', { account: account, restoreCard: cards.activeCardIndex }, 'right'); break; case 'needs-app-pass': cards.pushCard('setup_fix_gmail_twofactor', 'animate', { account: account, restoreCard: cards.activeCardIndex }, 'right'); break; case 'needs-oauth-reauth': cards.pushCard('setup_fix_oauth2', 'animate', { account: account, restoreCard: cards.activeCardIndex }, 'right'); break; } }); // Start init of main view/model modules now that all the registrations for // top level events have happened, and before triggering of entry points start. cards.init(); // If config could have already started up the model if there was no cache set // up, so only trigger init if it is not already started up, for efficiency. if (!model.inited) { model.init(); } /** * Register setMozMessageHandler listeners with the plumbing set up in * html_cache_restore */ var startupData = globalOnAppMessage({ activity: function(rawActivity) { // Remove previous cards because the card stack could get weird if inserting // a new card that would not normally be at that stack level. Primary // concern: going to settings, then trying to add a compose card at that // stack level. More importantly, the added card could have a "back" // operation that does not mean "back to previous state", but "back in // application flowchart". Message list is a good known jump point, so do // not needlessly wipe that one out if it is the current one. if (!isCurrentCardMessageList()) { cards.removeAllCards(); } function activityCompose() { var cardArgs = { activity: rawActivity, composerData: activityComposerData(rawActivity) }; pushStartCard('compose', cardArgs); } if (globalOnAppMessage.hasAccount()) { activityCompose(); } else { activityCallback = activityCompose; waitingRawActivity = rawActivity; pushStartCard('setup_account_info', { allowBack: true, launchedFromActivity: true }); } }, notification: function(data) { data = data || {}; var type = data.type || ''; var folderType = data.folderType || 'inbox'; model.latestOnce('foldersSlice', function latestFolderSlice() { function onCorrectFolder() { // Remove previous cards because the card stack could get weird if // inserting a new card that would not normally be at that stack level. // Primary concern: going to settings, then trying to add a reader or // message list card at that stack level. More importantly, the added // card could have a "back" operation that does not mean "back to // previous state", but "back in application flowchart". Message list is // a good known jump point, so do not needlessly wipe that one out if it // is the current one. if (!isCurrentCardMessageList()) { cards.removeAllCards(); } if (type === 'message_list') { pushStartCard('message_list', {}); } else if (type === 'message_reader') { var headerCursor = new HeaderCursor(model); headerCursor.setCurrentMessageBySuid(data.messageSuid); pushStartCard(type, { messageSuid: data.messageSuid, headerCursor: headerCursor }); } else { console.error('unhandled notification type: ' + type); } } var acctsSlice = model.acctsSlice, accountId = data.accountId; if (model.account.id === accountId) { // folderType will often be 'inbox' (in the case of a new message // notification) or 'outbox' (in the case of a "failed send" // notification). return model.selectFirstFolderWithType(folderType, onCorrectFolder); } else { var newAccount; acctsSlice.items.some(function(account) { if (account.id === accountId) { newAccount = account; return true; } }); if (newAccount) { model.changeAccount(newAccount, function() { model.selectFirstFolderWithType(folderType, onCorrectFolder); }); } } }); } }); console.log('startupData: ' + JSON.stringify(startupData, null, ' ')); // If not a mozSetMessageHandler entry point, start up the UI now. Or, if // an alarm started the app, but the app became visible during the // startup. In that case, make sure we show something to the user. if (startupData.entry === 'default' || (startupData.entry === 'alarm' && !document.hidden)) { pushStartCard(startupData.view); } });