define('application/app', [
'rofl/application',
'rofl/widgets/container',
'rofl/router',
'rofl/lib/l10n',
'application/widgets/loader',
'antie/widgets/horizontallist',
'application/models/configuration',
'rofl/analytics/web/google',
'rofl/lib/utils',
'application/managers/session',
'application/managers/api',
'rofl/events/keyevent',
'antie/runtimecontext',
'rofl/lib/date',
'application/managers/halo',
'application/components/modals/error',
'rofl/devices/misc/setcorrectdate'
], function (
Application,
Container,
Router,
L10N,
Loader,
HorizontalList,
KPNConfig,
GoogleAnalytics,
Utils,
SessionManager,
ApiManager,
KeyEvent,
RuntimeContext,
DateUtils,
HaloManager
) {
'use strict';
var appContainerElement,
l10n = L10N.getInstance(),
UPDATE_CHECK_INTERVAL = 1800000, // 30 min
application,
configuration,
device,
KEY_HOLD_TIMEOUT,
haloManager;
return Application.extend({
_hasInternetConnection: true,
_internetTimeout: null,
_initialNetworkCheck: true,
_appStartTimeout: null,
/**
* The init method initializes the Application object, here it is only
* used to call the method on rofl/application, but can be used in your
* application for setting some initial application values.
*
* @param {HTMLElement} appDiv - The application element which should be handled
* as root element.
* @param {Object} styleDir - The directory used for stylesheets.
* @param {Object} imgDir - The directory used for images (can be used for preloading).
* @param {Function} callback - Function to call when application.ready() is called
* (usually used to destroy splash screens).
*/
init: function init (appDiv, styleDir, imgDir, callback) {
init.base.call(this, appDiv, styleDir, imgDir, callback);
appContainerElement = appDiv;
this._appRunningTime = this.getDate();
this._onNetworkStatusChangeBound = Utils.bind(this._onNetworkStatusChange, this);
this._onVisibilityChangeBound = Utils.bind(this._onVisibilityChange, this);
this._onCheckUpdateBound = Utils.bind(this._onCheckUpdate, this);
this._onKeyDownBound = Utils.bind(this._onKeyDown, this);
this._onKeyUpBound = Utils.bind(this._onKeyUp, this);
application = RuntimeContext.getCurrentApplication();
configuration = application.getConfiguration();
KEY_HOLD_TIMEOUT = configuration.player.keyHoldTimeout;
},
/**
* Run is the method used by the application after the application is
* initialized, here we create our root widget (onto which everything is added).
*/
run: function () {
var list = new HorizontalList(),
loader = this._loader = new Loader();
device = RuntimeContext.getDevice();
haloManager = HaloManager.getInstance();
list.outputElement = appContainerElement;
this.setRootWidget(list);
this.addComponentContainer('menu');
this.addComponentContainer('main');
this.addComponentContainer('player');
this.addComponentContainer('detail');
this.addComponentContainer('modal');
list.appendChildWidget(loader);
this.getComponent('menu').addClass('invisible');
if (this.getCurrentAppURLParameters()['reset'] === 'true') {
SessionManager.getInstance().resetEntry();
}
this.addEventListener('networkstatuschange', this._onNetworkStatusChangeBound);
document.addEventListener('visibilitychange', this._onVisibilityChangeBound);
this.addEventListener('keydown', this._onKeyDownBound);
this.addEventListener('keyup', this._onKeyUpBound);
this._versionUpdateInterval = setInterval(this._onCheckUpdateBound, UPDATE_CHECK_INTERVAL);
this._deviceBrand = device.getBrand();
haloManager.getConfig()
.then(Utils.bind(l10n.setLanguage, l10n, 'nl-NL'))
.then(Utils.bind(this.ready, this));
},
/**
* Application ready handler.
*/
ready: function ready () {
ready.base.call(this);
this._onCheckUpdate();
if (this._readyState) {
this._onSuccessfulAppstart();
} else {
this._onFailedAppstart();
}
},
/**
* KeyDown event.
*
* @param {Object} e - The event data.
* @private
*/
_onKeyDown: function (e) {
var activeElement = this._getActiveFocusElement(),
activeElementID = activeElement.id,
player,
main,
menu,
menuComponent,
activeComponent,
handleChannelKey;
// If component is login don't do anything
if (activeElementID === 'main' && activeElement.getChildWidgets()[0].id === 'login') {
return;
}
player = this.getComponent('player');
main = this.getComponent('main');
menu = this.getComponent('menu');
menuComponent = activeComponent = activeElement.getChildWidgets()[0];
handleChannelKey = this._handleChannelKey(e, activeComponent.id);
// Handle the channel key for Samsung remote control.
if (handleChannelKey) {
this._setKeyHoldData(e, this._onKeyHoldSamsungHandler, activeComponent);
return;
}
// Only handle the buttons for these components
if ((activeElementID === 'menu' ||
activeElementID === 'main' ||
activeElementID === 'player') &&
menuComponent.id !== 'landingPage') {
e.stopPropagation();
switch (e.keyCode) {
case KeyEvent.VK_GUIDE:
// Check if guide isn't already opened
if (activeElement.getChildWidgets()[0].id !== 'guide') {
this.hidePlayer();
this.route('guide', {});
main.setStyleTo('display', 'block');
} else {
main.back();
if (main.getChildWidgetCount() === 0) {
player.focus();
this.showPlayer();
} else {
main.focus();
}
}
break;
case KeyEvent.VK_LIST:
this.hidePlayer();
if (menu.hasClass('invisible') || menu.hasClass('collapse')) {
application.focusMenu(activeElementID);
} else {
this.hideMenu(activeElementID);
if (menuComponent.getCallingComponent()) {
this.getComponent(menuComponent.getCallingComponent()).focus();
} else if (main.getChildWidgets().length) {
main.focus();
} else {
player.focus();
}
}
break;
}
}
},
/**
* KeyUp event.
*
* @param {Object} e - The keyEvent data.
* @private
*/
_onKeyUp: function (e) {
if (this._keyHoldTimeout) {
this._onKeyHoldCancelled(e);
}
},
/**
* Sets the keyHold data.
*
* @param {Object} e - The keyEvent data.
* @param {Function} keyHoldHandler - Function that handles the keyEvent.
* @param {Object} activeComponent - Currently active component.
*/
_setKeyHoldData: function (e, keyHoldHandler, activeComponent) {
var keyCode = e.keyCode;
this._keyHoldData = {};
this._keyHoldData[keyCode] = {
keyHoldHandler: Utils.bind(keyHoldHandler, this, e),
activeComponent: activeComponent
};
this._keyHoldTimeout = setTimeout(Utils.bind(this._onKeyHoldTriggered, this, e), KEY_HOLD_TIMEOUT);
},
/**
* This function is triggered when keyHold times out. KeyHoldHandler executed with boolean meaning keyhold
* behaviour should be executed.
*
* @param {Object} e - The keyEvent data.
* @private
**/
_onKeyHoldTriggered: function (e) {
var keyCode = e.keyCode,
keyHoldData = this._keyHoldData[keyCode];
this._keyHoldTimeout = null;
if (keyHoldData && Utils.isFunction(keyHoldData.keyHoldHandler)) {
keyHoldData.keyHoldHandler(true, keyHoldData.activeComponent);
delete this._keyHoldData[keyCode];
}
},
/**
* This function is triggered when keyHold is cancelled. KeyHoldHandler executed with boolean meaning keyhold
* behaviour should not be executed.
*
* @param {Object} e - The keyEvent data.
* @private
**/
_onKeyHoldCancelled: function (e) {
var keyCode = e.keyCode,
keyHoldData = this._keyHoldData[keyCode];
clearTimeout(this._keyHoldTimeout);
this._keyHoldTimeout = null;
if (keyHoldData && Utils.isFunction(keyHoldData.keyHoldHandler)) {
keyHoldData.keyHoldHandler(false, keyHoldData.activeComponent);
delete this._keyHoldData[keyCode];
}
},
/**
* KeyDown event for Samsung rc.
*
* @param {Object} e - The event data.
* @param {boolean} keyHoldTriggered - True if triggered by keyHold timeout or cancel by keyup event.
* @param {Object} activeComponent - Currently active component.
* @private
*/
_onKeyHoldSamsungHandler: function (e, keyHoldTriggered, activeComponent) {
var activeComponentId = activeComponent.id,
isModal = activeComponent.getIsModal(),
modalEscapeCallback = activeComponent.getEscapeCallback && activeComponent.getEscapeCallback(),
isDetail = activeComponentId === 'voddetail',
newActiveComponent;
if (isModal || isDetail) {
// Close modal or detail and focus on main.
activeComponent.parentWidget.back();
if (Utils.isFunction(modalEscapeCallback)) {
modalEscapeCallback();
}
// Check if new active component is a detail, as the closed modal could have been the seasons dropdown.
newActiveComponent = this._getActiveFocusElement();
if (newActiveComponent.id === 'detail' && isModal) {
newActiveComponent.back();
}
application.getComponent('main').focus();
// Get the new active component, as the modal or detail has been closed.
newActiveComponent = this._getActiveFocusElement();
activeComponentId = newActiveComponent.getChildWidgets()[0].id;
}
switch (e.keyCode) {
case KeyEvent.VK_CHANNEL_UP:
case KeyEvent.VK_CHANNEL_DOWN:
if (this.isSessionLoggedIn()) {
if (keyHoldTriggered) {
if (activeComponentId !== 'channel-list') {
application.route('channellist');
}
} else {
// Guide has its own behaviour when channel up/down is used, so bubble event.
if (activeComponentId === 'guide') {
// Bubble event only if the active component wasn't modal (filters for example) or details.
if (!isModal && !isDetail) {
activeComponent.bubbleEvent(e);
}
} else {
application.route('guide');
}
}
}
break;
}
},
/**
* Determines if Samsung's channel key will be handled by the main app or the component.
*
* @param {Object} e - The keyEvent data.
* @param {string} activeElementId - The active component ID.
*
* @returns {boolean} - True if the channel key will be handled by main app instead of component.
*/
_handleChannelKey: function (e, activeElementId) {
return (e.keyCode === KeyEvent.VK_CHANNEL_UP || e.keyCode === KeyEvent.VK_CHANNEL_DOWN) &&
activeElementId !== 'player' && this._deviceBrand === 'samsung';
},
/**
* Get the active focused element.
*
* @returns {Object} - The focused element.
* @private
*/
_getActiveFocusElement: function () {
var activeElement = null;
Utils.each(this.getRootWidget().getChildWidgets(), function (key) {
if (key._isFocussed) {
activeElement = key;
}
});
return activeElement;
},
/**
* Failed app start handler.
*/
_onFailedAppstart: function () {
var GA = GoogleAnalytics.getInstance();
GA.onEvent('startup', 'failed');
},
/**
* OnReadyHandler.
*/
_onSuccessfulAppstart: function () {
var GA = GoogleAnalytics.getInstance();
GA.onEvent('startup', 'successful');
this._applicationStartTime = this.getDate();
},
/**
* The application route method, this is executed by the application
* on startup (_default route) and can be used to route to different
* screens within the application.
*
* @param {String|Array} path - The route you want to used, defined in the config.
* Can also be an array in the case of deeplinks.
* @param {Object} opts - The options object can contain information you want to use
* in the route.
*/
route: function (path, opts) {
var instance = Router.getInstance(),
player = this.getComponent('player'),
menu = this.getComponent('menu').getActiveChildWidget(),
main = application.getComponent('main'),
routingComponent = this.getRoutingComponent(path);
if (path === 'details') {
switch (opts.data.getContentType()) {
case 'PROGRAM':
path = 'epgdetail';
break;
case 'GROUP_OF_BUNDLES':
path = 'seriesdetail';
break;
case 'VOD':
path = 'voddetail';
break;
// No default.
}
}
if (typeof path === 'string') {
// Show or hide menu if the routing component is not modal.
if (routingComponent && routingComponent.container !== 'modal' && path !== 'onboarding') {
if (path.indexOf('player') >= 0) {
this.showLoader(true);
this.hideMenu(path);
this.showPlayer();
} else if (menu && menu.showCollapsed && SessionManager.getInstance().isLoggedIn()) {
this.showMenu(path, false, true);
}
// Will hide player and show main component.
if (player.isFocussed() || routingComponent.container === 'main' && !main.isVisible()) {
this.hidePlayer();
}
}
instance.route(path, opts);
} else {
instance.deeplink(path);
}
},
/**
* Call show menu.
*
* @param {string} value - Parent component name passed from component which called the function.
* @param {boolean} blockedMenu - True if the menu should be blocked.
* @param {boolean} collapsed - True if the menu should be shown collapsed (lint menu).
*/
showMenu: function (value, blockedMenu, collapsed) {
var menu = this.getComponent('menu').getActiveChildWidget();
if (collapsed) {
menu.showCollapsed(value);
} else {
menu.openMenu(value, blockedMenu);
}
},
/**
* Shows and focus the menu.
*
* @param {string} callingComponent - The calling component.
*/
focusMenu: function (callingComponent) {
var menuComponent = this.getComponent('menu'),
menuComponentWidget = menuComponent.getActiveChildWidget();
menuComponentWidget.openMenu(callingComponent);
menuComponent.focus();
},
/**
* Call hide menu.
*/
hideMenu: function () {
var menu = this.getComponent('menu').getActiveChildWidget();
if (menu) {
menu.closeMenu();
}
},
/**
* Show the application loader.
*
* @param {boolean} [preventFocus] - Prevents focus on the loader.
* @param {boolean} [blockInput] - Prevents user input.
*/
showLoader: function (preventFocus, blockInput) {
this._loader.show({duration: 200});
if (!preventFocus) {
this._loader.focus();
}
if (blockInput) {
application.blockUserInput = true;
}
},
/**
* Hide the application loader.
*
* @param {Integer} [delay] - Specify whether or not to delay the unblocking of user input.
* @param {Object} [options] - The options.
*/
hideLoader: function (delay, options) {
var hide = function () {
var opts = options || {};
opts.duration = opts.duration || 200;
application.blockUserInput = false;
this._loader.hide(opts);
};
if (this._blockedTimeout) {
clearTimeout(this._blockedTimeout);
}
if (Utils.isNumber(delay)) {
this._blockedTimeout = setTimeout(Utils.bind(hide, this), delay);
} else {
hide.call(this);
}
},
/**
* Show the player.
*/
showPlayer: function () {
var player = this.getComponent('player'),
main = application.getComponent('main'),
activeComponent = this._lastActiveFocusElement = this._getActiveFocusElement(),
playerWidget;
// Check if player has been has been instantiated before so we can show it.
if (player) {
if (player.getChildWidgetCount()) {
playerWidget = player.getChildWidgetByIndex(0);
}
player.setStyleTo('display', 'block');
}
if (playerWidget) {
playerWidget.setStyleTo('display', 'block');
}
if (main) {
main.setStyleTo('display', 'none');
}
if (activeComponent && activeComponent !== main && activeComponent !== player) {
activeComponent.setStyleTo('display', 'none');
}
},
/**
* Hide the player.
*
* @param {boolean} useLastActiveComponent - True if component to show should be the last active.
*/
hidePlayer: function (useLastActiveComponent) {
var player = this.getComponent('player'),
main = application.getComponent('main'),
details = application.getComponent('detail'),
activeComponent = useLastActiveComponent ? this._lastActiveFocusElement : this._getActiveFocusElement(),
detailsWidget = details && details.getChildWidgetByIndex(0),
playerWidget;
if (player && player.getChildWidgetCount()) {
playerWidget = player.getChildWidgetByIndex(0);
player.setStyleTo('display', 'none');
}
if (playerWidget) {
playerWidget.setStyleTo('display', 'none');
if (!useLastActiveComponent) {
playerWidget.closePlayer();
if (detailsWidget) {
details.back();
}
}
}
if (main) {
activeComponent = detailsWidget || activeComponent;
main.setStyleTo('display', 'block');
main.focus();
}
if (activeComponent && activeComponent !== main && activeComponent !== player) {
if (details) {
details.setStyleTo('display', 'block');
}
activeComponent.setStyleTo('display', 'block');
activeComponent.focus();
}
},
/**
* Sets the API settings.
*/
setAPISettings: function () {
var params = this.getCurrentAppURLParameters();
if (params && params.api === KPNConfig.APIS.BETA) {
this._api = KPNConfig.APIS.BETA;
} else if (params && params.api === KPNConfig.APIS.STAGING) {
this._api = KPNConfig.APIS.STAGING;
} else {
this._api = KPNConfig.APIS.PROD;
}
},
/**
* Returns the api setting.
*
* @returns {string} - The api setting.
*/
getAPISetting: function () {
if (!this._api) {
this.setAPISettings();
}
return this._api || KPNConfig.APIS.PROD;
},
// /**
// * Attempts to enable the GIP mode.
// */
// enableGIPMode: function () {
//
// if (window.webOS && window.webOS.service) {
//
// window.webOS.service.request('luna://com.webos.service.eim', {
// method: 'addDevice',
// parameters: {
// 'appId': (window.webOS.fetchAppId && window.webOS.fetchAppId()) || 'kpn.poc',
// 'pigImage': '700x394.png',
// 'label': 'KPN',
// 'icon': '70x70.png',
// 'largeIcon': '140x140.png',
// 'description': 'KPN',
// 'showPopup': true
// },
// onComplete: function () {
//
// // Do nothing.
// }
// });
// }
// },
/**
* Add GA tracking for application time.
*/
appDurationEvent: function () {
var appDuration = ((this.getDate() - this._applicationStartTime) / 60000).toFixed(2),
GA = GoogleAnalytics.getInstance();
GA.onEvent('application', 'time', {eventValue: appDuration});
},
/**
* Compares stored version number and received from server.
*
* @private
*/
_onCheckUpdate: function () {
ApiManager.getVersion().then(Utils.bind(function (serverVersion) {
if (serverVersion !== this.getConfiguration().version) {
clearInterval(this._versionUpdateInterval);
this._onUpdateAvailable(serverVersion);
}
}, this));
},
/**
* Function fired after new version is available.
*
* @param {string} version - The version.
* @private
*/
_onUpdateAvailable: function (version) {
this.hideLoader();
this._shouldUpdateApp = true;
this.route('version', {
title: l10n.get('update.title'),
text: l10n.get('update.text'),
buttons: [
{
label: l10n.get('update.button.text'),
class: 'confirm_button',
button: 'confirmbutton',
labelname: 'confirmButtonLabel'
}
],
callback: function () {
window.location.href = window.location.origin + '?ftc=' + version;
},
showGoBackButton: false
});
},
/**
* Check if we don't have internet access, set a timeout for if the internet maybe returns.
*
* @param {boolean} status - The internet connection status.
* @private
*/
_checkNetworkStatus: function (status) {
var internet = this._hasInternetConnection = status,
timeout = this._internetTimeout,
self = this;
if (!internet && timeout === null) {
this._internetTimeout = setTimeout(function () {
self.route('error', {
type: 'fullscreen',
title: l10n.get('errors.lost_network'),
button: {id: 'error-close-app-button', label: l10n.get('errors.close')},
imgUrl: 'src/assets/images/error-icon.png',
closeApp: true
});
clearTimeout(timeout);
self._internetTimeout = null;
}, configuration.NETWORK_DIALOG_TIMEOUT || 10000);
} else if (internet && timeout) {
clearTimeout(timeout);
self._internetTimeout = null;
if (this._initialNetworkCheck) {
location.reload();
}
} else {
location.reload();
}
},
/**
* OnNetworkStatusChange event handler.
*
* @param {Event} e - Network status change event.
* @private
*/
_onNetworkStatusChange: function (e) {
this.hideLoader();
this._checkNetworkStatus(e.networkStatus);
},
/**
* Focused widget function.
*
* @param {Object} widget - The widget to set.
* @private
*/
_setFocussedWidget: function _setFocussedWidget (widget) {
if (!this._shouldUpdateApp) {
_setFocussedWidget.base.call(this, widget);
}
},
/**
* OnNetworkStatusChange event handler.
*
* @private
*/
_onVisibilityChange: function () {
var self = this;
if (document.visibilityState === 'hidden') {
self._initialNetworkCheck = true;
}
if (!this._appStartTimeout && document.visibilityState === 'visible') {
this._appStartTimeout = setTimeout(function () {
self._initialNetworkCheck = false;
}, configuration.STARTUP_NETWORK_CHECK_TIMEOUT || 5000);
}
},
/**
* Return the whole route component.
*
* @param {string} route - The route name.
* @returns {Object} - The whole route component.
*/
getRoutingComponent: function (route) {
var config = this.getConfiguration();
if (config.route) {
return config.route[route] || null;
}
return null;
},
/**
* Sets the user.
*
* @param {Object} user - The user.
*/
setUser: function (user) {
this._user = user;
},
/**
* Returns the user.
*
* @returns {Object} - The user.
*/
getUser: function () {
return this._user;
},
/**
* Returns the date.
*
* @returns {Date} - The current date/time.
*/
getDate: function () {
return DateUtils.getDate();
},
/**
* Checks the session manager if user is logged in.
*
* @returns {boolean} - True if logged in.
*/
isSessionLoggedIn: function () {
return SessionManager.getInstance().isLoggedIn();
}
});
});