Source: app.js

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();
        }
    });
});