diff --git a/src/main/app/config.test.js b/src/main/app/config.test.js index 73de4645..896b6bf7 100644 --- a/src/main/app/config.test.js +++ b/src/main/app/config.test.js @@ -44,6 +44,7 @@ jest.mock('main/badge', () => ({ jest.mock('main/tray/tray', () => ({ refreshTrayImages: jest.fn(), })); +jest.mock('main/views/loadingScreen', () => ({})); jest.mock('main/windows/windowManager', () => ({ handleUpdateConfig: jest.fn(), sendToRenderer: jest.fn(), diff --git a/src/main/app/config.ts b/src/main/app/config.ts index 90b206a6..d8f50f83 100644 --- a/src/main/app/config.ts +++ b/src/main/app/config.ts @@ -12,6 +12,7 @@ import Config from 'common/config'; import AutoLauncher from 'main/AutoLauncher'; import {setUnreadBadgeSetting} from 'main/badge'; import {refreshTrayImages} from 'main/tray/tray'; +import LoadingScreen from 'main/views/loadingScreen'; import WindowManager from 'main/windows/windowManager'; import {handleMainWindowIsShown} from './intercom'; @@ -80,7 +81,7 @@ export function handleDarkModeChange(darkMode: boolean) { refreshTrayImages(Config.trayIconTheme); WindowManager.sendToRenderer(DARK_MODE_CHANGE, darkMode); - WindowManager.updateLoadingScreenDarkMode(darkMode); + LoadingScreen.setDarkMode(darkMode); ipcMain.emit(EMIT_CONFIGURATION, true, Config.data); } diff --git a/src/main/views/loadingScreen.test.js b/src/main/views/loadingScreen.test.js new file mode 100644 index 00000000..7822d729 --- /dev/null +++ b/src/main/views/loadingScreen.test.js @@ -0,0 +1,56 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import MainWindow from 'main/windows/mainWindow'; + +import {LoadingScreen} from './loadingScreen'; + +jest.mock('electron', () => ({ + ipcMain: { + on: jest.fn(), + }, +})); + +jest.mock('main/windows/mainWindow', () => ({ + get: jest.fn(), +})); + +describe('main/views/loadingScreen', () => { + describe('show', () => { + const mainWindow = { + getBrowserViews: jest.fn(), + setTopBrowserView: jest.fn(), + addBrowserView: jest.fn(), + }; + const loadingScreen = new LoadingScreen(); + loadingScreen.create = jest.fn(); + loadingScreen.setBounds = jest.fn(); + const view = {webContents: {send: jest.fn(), isLoading: () => false}}; + + beforeEach(() => { + mainWindow.getBrowserViews.mockImplementation(() => []); + MainWindow.get.mockReturnValue(mainWindow); + }); + + afterEach(() => { + delete loadingScreen.view; + jest.resetAllMocks(); + }); + + it('should create new loading screen if one doesnt exist and add it to the window', () => { + loadingScreen.create.mockImplementation(() => { + loadingScreen.view = view; + }); + loadingScreen.show(); + expect(loadingScreen.create).toHaveBeenCalled(); + expect(mainWindow.addBrowserView).toHaveBeenCalled(); + }); + + it('should set the browser view as top if already exists and needs to be shown', () => { + loadingScreen.view = view; + mainWindow.getBrowserViews.mockImplementation(() => [view]); + loadingScreen.show(); + expect(mainWindow.setTopBrowserView).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/main/views/loadingScreen.ts b/src/main/views/loadingScreen.ts new file mode 100644 index 00000000..0cb35cc7 --- /dev/null +++ b/src/main/views/loadingScreen.ts @@ -0,0 +1,115 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {BrowserView, app, ipcMain} from 'electron'; +import log from 'electron-log'; + +import {DARK_MODE_CHANGE, LOADING_SCREEN_ANIMATION_FINISHED, TOGGLE_LOADING_SCREEN_VISIBILITY} from 'common/communication'; + +import {getLocalPreload, getLocalURLString, getWindowBoundaries} from 'main/utils'; +import MainWindow from 'main/windows/mainWindow'; + +enum LoadingScreenState { + VISIBLE = 1, + FADING = 2, + HIDDEN = 3, +} + +export class LoadingScreen { + private view?: BrowserView; + private state: LoadingScreenState; + + constructor() { + this.state = LoadingScreenState.HIDDEN; + + ipcMain.on(LOADING_SCREEN_ANIMATION_FINISHED, this.handleAnimationFinished); + } + + /** + * Loading Screen + */ + + setBounds = () => { + if (this.view) { + const mainWindow = MainWindow.get(); + if (!mainWindow) { + return; + } + this.view.setBounds(getWindowBoundaries(mainWindow)); + } + } + + setDarkMode = (darkMode: boolean) => { + this.view?.webContents.send(DARK_MODE_CHANGE, darkMode); + } + + isHidden = () => { + return this.state === LoadingScreenState.HIDDEN; + } + + show = () => { + const mainWindow = MainWindow.get(); + if (!mainWindow) { + return; + } + + if (!this.view) { + this.create(); + } + + this.state = LoadingScreenState.VISIBLE; + + if (this.view?.webContents.isLoading()) { + this.view.webContents.once('did-finish-load', () => { + this.view!.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, true); + }); + } else { + this.view!.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, true); + } + + if (mainWindow.getBrowserViews().includes(this.view!)) { + mainWindow.setTopBrowserView(this.view!); + } else { + mainWindow.addBrowserView(this.view!); + } + + this.setBounds(); + } + + fade = () => { + if (this.view && this.state === LoadingScreenState.VISIBLE) { + this.state = LoadingScreenState.FADING; + this.view.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, false); + } + } + + private create = () => { + const preload = getLocalPreload('desktopAPI.js'); + this.view = new BrowserView({webPreferences: { + preload, + + // Workaround for this issue: https://github.com/electron/electron/issues/30993 + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + transparent: true, + }}); + const localURL = getLocalURLString('loadingScreen.html'); + this.view.webContents.loadURL(localURL); + } + + private handleAnimationFinished = () => { + log.debug('handleLoadingScreenAnimationFinished'); + + if (this.view && this.state !== LoadingScreenState.HIDDEN) { + this.state = LoadingScreenState.HIDDEN; + MainWindow.get()?.removeBrowserView(this.view); + } + + if (process.env.NODE_ENV === 'test') { + app.emit('e2e-app-loaded'); + } + } +} + +const loadingScreen = new LoadingScreen(); +export default loadingScreen; diff --git a/src/main/views/viewManager.test.js b/src/main/views/viewManager.test.js index 48356ec0..fab45b22 100644 --- a/src/main/views/viewManager.test.js +++ b/src/main/views/viewManager.test.js @@ -16,6 +16,7 @@ import MainWindow from 'main/windows/mainWindow'; import {MattermostView} from './MattermostView'; import {ViewManager} from './viewManager'; +import LoadingScreen from './loadingScreen'; jest.mock('electron', () => ({ app: { @@ -57,7 +58,10 @@ jest.mock('main/i18nManager', () => ({ jest.mock('main/server/serverInfo', () => ({ ServerInfo: jest.fn(), })); - +jest.mock('main/views/loadingScreen', () => ({ + show: jest.fn(), + fade: jest.fn(), +})); jest.mock('main/windows/mainWindow', () => ({ get: jest.fn(), })); @@ -78,7 +82,6 @@ describe('main/views/viewManager', () => { const destroyFn = jest.fn(); beforeEach(() => { - viewManager.createLoadingScreen = jest.fn(); viewManager.showByName = jest.fn(); viewManager.getServerView = jest.fn().mockImplementation((srv, tabName) => ({name: `${srv.name}-${tabName}`})); MattermostView.mockImplementation((tab) => ({ @@ -92,7 +95,6 @@ describe('main/views/viewManager', () => { afterEach(() => { jest.resetAllMocks(); - viewManager.loadingScreen = undefined; viewManager.closedViews = new Map(); viewManager.views = new Map(); }); @@ -112,7 +114,6 @@ describe('main/views/viewManager', () => { it('should add view to views map and add listeners', () => { viewManager.loadView({name: 'server1'}, {}, {name: 'tab1', isOpen: true}, 'http://server-1.com/subpath'); expect(viewManager.views.has('server1-tab1')).toBe(true); - expect(viewManager.createLoadingScreen).toHaveBeenCalled(); expect(onceFn).toHaveBeenCalledWith(LOAD_SUCCESS, viewManager.activateView); expect(loadFn).toHaveBeenCalledWith('http://server-1.com/subpath'); }); @@ -218,7 +219,6 @@ describe('main/views/viewManager', () => { afterEach(() => { jest.resetAllMocks(); - delete viewManager.loadingScreen; delete viewManager.currentView; viewManager.closedViews = new Map(); viewManager.views = new Map(); @@ -582,8 +582,6 @@ describe('main/views/viewManager', () => { beforeEach(() => { viewManager.getCurrentView = jest.fn(); - viewManager.showLoadingScreen = jest.fn(); - viewManager.fadeLoadingScreen = jest.fn(); }); afterEach(() => { @@ -641,7 +639,7 @@ describe('main/views/viewManager', () => { view.needsLoadingScreen.mockImplementation(() => true); viewManager.views.set('view1', view); viewManager.showByName('view1'); - expect(viewManager.showLoadingScreen).toHaveBeenCalled(); + expect(LoadingScreen.show).toHaveBeenCalled(); }); it('should show the view when not errored', () => { @@ -655,44 +653,6 @@ describe('main/views/viewManager', () => { }); }); - describe('showLoadingScreen', () => { - const window = { - getBrowserViews: jest.fn(), - setTopBrowserView: jest.fn(), - addBrowserView: jest.fn(), - }; - const viewManager = new ViewManager(); - const loadingScreen = {webContents: {send: jest.fn(), isLoading: () => false}}; - - beforeEach(() => { - MainWindow.get.mockReturnValue(window); - viewManager.createLoadingScreen = jest.fn(); - viewManager.setLoadingScreenBounds = jest.fn(); - window.getBrowserViews.mockImplementation(() => []); - }); - - afterEach(() => { - jest.resetAllMocks(); - delete viewManager.loadingScreen; - }); - - it('should create new loading screen if one doesnt exist and add it to the window', () => { - viewManager.createLoadingScreen.mockImplementation(() => { - viewManager.loadingScreen = loadingScreen; - }); - viewManager.showLoadingScreen(); - expect(viewManager.createLoadingScreen).toHaveBeenCalled(); - expect(window.addBrowserView).toHaveBeenCalled(); - }); - - it('should set the browser view as top if already exists and needs to be shown', () => { - viewManager.loadingScreen = loadingScreen; - window.getBrowserViews.mockImplementation(() => [loadingScreen]); - viewManager.showLoadingScreen(); - expect(window.setTopBrowserView).toHaveBeenCalled(); - }); - }); - describe('getViewByURL', () => { const viewManager = new ViewManager({}); viewManager.getServers = () => [ diff --git a/src/main/views/viewManager.ts b/src/main/views/viewManager.ts index 7d880471..1bf69951 100644 --- a/src/main/views/viewManager.ts +++ b/src/main/views/viewManager.ts @@ -14,7 +14,6 @@ import { UPDATE_TARGET_URL, LOAD_SUCCESS, LOAD_FAILED, - TOGGLE_LOADING_SCREEN_VISIBILITY, LOADSCREEN_END, SET_ACTIVE_VIEW, OPEN_TAB, @@ -22,7 +21,6 @@ import { UPDATE_LAST_ACTIVE, UPDATE_URL_VIEW_WIDTH, MAIN_WINDOW_SHOWN, - DARK_MODE_CHANGE, } from 'common/communication'; import Config from 'common/config'; import urlUtils, {equalUrlsIgnoringSubpath} from 'common/utils/url'; @@ -37,21 +35,16 @@ import {localizeMessage} from 'main/i18nManager'; import {ServerInfo} from 'main/server/serverInfo'; import MainWindow from 'main/windows/mainWindow'; -import {getLocalURLString, getLocalPreload, getWindowBoundaries} from '../utils'; +import {getLocalURLString, getLocalPreload} from '../utils'; import {MattermostView} from './MattermostView'; import modalManager from './modalManager'; import WebContentsEventManager from './webContentEvents'; +import LoadingScreen from './loadingScreen'; const URL_VIEW_DURATION = 10 * SECOND; const URL_VIEW_HEIGHT = 20; -export enum LoadingScreenState { - VISIBLE = 1, - FADING = 2, - HIDDEN = 3, -} - export class ViewManager { lastActiveServer?: number; viewOptions: BrowserViewConstructorOptions; @@ -60,15 +53,12 @@ export class ViewManager { currentView?: string; urlView?: BrowserView; urlViewCancel?: () => void; - loadingScreen?: BrowserView; - loadingScreenState: LoadingScreenState; constructor() { this.lastActiveServer = Config.lastActiveTeam; this.viewOptions = {webPreferences: {spellcheck: Config.useSpellChecker}}; this.views = new Map(); // keep in mind that this doesn't need to hold server order, only tabs on the renderer need that. this.closedViews = new Map(); - this.loadingScreenState = LoadingScreenState.HIDDEN; } getServers = () => { @@ -97,9 +87,6 @@ export class ViewManager { if (this.closedViews.has(view.name)) { this.closedViews.delete(view.name); } - if (!this.loadingScreen) { - this.createLoadingScreen(); - } } loadView = (srv: MattermostServer, serverInfo: ServerInfo, tab: Tab, url?: string) => { @@ -242,7 +229,7 @@ export class ViewManager { if (!newView.isErrored()) { newView.show(); if (newView.needsLoadingScreen()) { - this.showLoadingScreen(); + LoadingScreen.show(); } } MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, newView.tab.server.name, newView.tab.type); @@ -290,7 +277,7 @@ export class ViewManager { const view = this.views.get(server); if (view && this.getCurrentView() === view) { this.showByName(this.currentView!); - this.fadeLoadingScreen(); + LoadingScreen.fade(); } } @@ -314,7 +301,7 @@ export class ViewManager { failLoading = (tabName: string) => { log.debug('viewManager.failLoading', tabName); - this.fadeLoadingScreen(); + LoadingScreen.fade(); if (this.currentView === tabName) { this.getCurrentView()?.hide(); } @@ -417,87 +404,16 @@ export class ViewManager { } } - setLoadingScreenBounds = () => { - const mainWindow = MainWindow.get(); - if (!mainWindow) { - return; - } - this.loadingScreen?.setBounds(getWindowBoundaries(mainWindow)); - } - - createLoadingScreen = () => { - const preload = getLocalPreload('desktopAPI.js'); - this.loadingScreen = new BrowserView({webPreferences: { - preload, - - // Workaround for this issue: https://github.com/electron/electron/issues/30993 - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - transparent: true, - }}); - const localURL = getLocalURLString('loadingScreen.html'); - this.loadingScreen.webContents.loadURL(localURL); - } - - showLoadingScreen = () => { - const mainWindow = MainWindow.get(); - if (!mainWindow) { - return; - } - - if (!this.loadingScreen) { - this.createLoadingScreen(); - } - - this.loadingScreenState = LoadingScreenState.VISIBLE; - - if (this.loadingScreen?.webContents.isLoading()) { - this.loadingScreen.webContents.once('did-finish-load', () => { - this.loadingScreen!.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, true); - }); - } else { - this.loadingScreen!.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, true); - } - - if (mainWindow.getBrowserViews().includes(this.loadingScreen!)) { - mainWindow.setTopBrowserView(this.loadingScreen!); - } else { - mainWindow.addBrowserView(this.loadingScreen!); - } - - this.setLoadingScreenBounds(); - } - - fadeLoadingScreen = () => { - if (this.loadingScreen && this.loadingScreenState === LoadingScreenState.VISIBLE) { - this.loadingScreenState = LoadingScreenState.FADING; - this.loadingScreen.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, false); - } - } - - hideLoadingScreen = () => { - if (this.loadingScreen && this.loadingScreenState !== LoadingScreenState.HIDDEN) { - this.loadingScreenState = LoadingScreenState.HIDDEN; - MainWindow.get()?.removeBrowserView(this.loadingScreen); - } - } - setServerInitialized = (server: string) => { const view = this.views.get(server); if (view) { view.setInitialized(); if (this.getCurrentView() === view) { - this.fadeLoadingScreen(); + LoadingScreen.fade(); } } } - updateLoadingScreenDarkMode = (darkMode: boolean) => { - if (this.loadingScreen) { - this.loadingScreen.webContents.send(DARK_MODE_CHANGE, darkMode); - } - } - deeplinkSuccess = (viewName: string) => { log.debug('viewManager.deeplinkSuccess', viewName); diff --git a/src/main/windows/callsWidgetWindow.test.js b/src/main/windows/callsWidgetWindow.test.js index ab960709..7eb495ba 100644 --- a/src/main/windows/callsWidgetWindow.test.js +++ b/src/main/windows/callsWidgetWindow.test.js @@ -10,6 +10,7 @@ import { MINIMUM_CALLS_WIDGET_HEIGHT, CALLS_PLUGIN_ID, } from 'common/utils/constants'; + import WebContentsEventManager from '../views/webContentEvents'; import CallsWidgetWindow from './callsWidgetWindow'; diff --git a/src/main/windows/mainWindow.test.js b/src/main/windows/mainWindow.test.js index d6af271c..1583b55c 100644 --- a/src/main/windows/mainWindow.test.js +++ b/src/main/windows/mainWindow.test.js @@ -14,6 +14,7 @@ import * as Validator from 'common/Validator'; import ContextMenu from '../contextMenu'; import {isInsideRectangle} from '../utils'; + import {MainWindow} from './mainWindow'; jest.mock('path', () => ({ diff --git a/src/main/windows/windowManager.test.js b/src/main/windows/windowManager.test.js index b244cd6b..f4a415cf 100644 --- a/src/main/windows/windowManager.test.js +++ b/src/main/windows/windowManager.test.js @@ -15,6 +15,7 @@ import { resetScreensharePermissionsMacOS, openScreensharePermissionsSettingsMacOS, } from 'main/utils'; +import LoadingScreen from '../views/loadingScreen'; import {WindowManager} from './windowManager'; import MainWindow from './mainWindow'; @@ -68,11 +69,12 @@ jest.mock('../utils', () => ({ })); jest.mock('../views/viewManager', () => ({ ViewManager: jest.fn(), - LoadingScreenState: { - HIDDEN: 3, - }, })); jest.mock('../CriticalErrorHandler', () => jest.fn()); +jest.mock('../views/loadingScreen', () => ({ + isHidden: jest.fn(), + setBounds: jest.fn(), +})); jest.mock('../views/teamDropdownView', () => jest.fn()); jest.mock('../views/downloadsDropdownView', () => jest.fn()); jest.mock('../views/downloadsDropdownMenuView', () => jest.fn()); @@ -193,7 +195,7 @@ describe('main/windows/windowManager', () => { it('should update loading screen and team dropdown bounds', () => { windowManager.handleResizeMainWindow(); - expect(windowManager.viewManager.setLoadingScreenBounds).toHaveBeenCalled(); + expect(LoadingScreen.setBounds).toHaveBeenCalled(); expect(windowManager.teamDropdown.updateWindowBounds).toHaveBeenCalled(); }); @@ -254,12 +256,13 @@ describe('main/windows/windowManager', () => { it('should update loading screen and team dropdown bounds', () => { const event = {preventDefault: jest.fn()}; windowManager.handleWillResizeMainWindow(event, {width: 800, height: 600}); - expect(windowManager.viewManager.setLoadingScreenBounds).toHaveBeenCalled(); + expect(LoadingScreen.setBounds).toHaveBeenCalled(); expect(windowManager.teamDropdown.updateWindowBounds).toHaveBeenCalled(); }); it('should not resize if the app is already resizing', () => { windowManager.isResizing = true; + LoadingScreen.isHidden.mockReturnValue(true); const event = {preventDefault: jest.fn()}; windowManager.handleWillResizeMainWindow(event, {width: 800, height: 600}); expect(view.setBounds).not.toHaveBeenCalled(); diff --git a/src/main/windows/windowManager.ts b/src/main/windows/windowManager.ts index 515826dd..3bcf6f08 100644 --- a/src/main/windows/windowManager.ts +++ b/src/main/windows/windowManager.ts @@ -18,7 +18,6 @@ import { MAXIMIZE_CHANGE, HISTORY, REACT_APP_INITIALIZED, - LOADING_SCREEN_ANIMATION_FINISHED, FOCUS_THREE_DOT_MENU, GET_DARK_MODE, UPDATE_SHORTCUT_MENU, @@ -45,6 +44,7 @@ import {SECOND} from 'common/utils/constants'; import Config from 'common/config'; import {getTabViewName, TAB_MESSAGING} from 'common/tabs/TabView'; +import downloadsManager from 'main/downloadsManager'; import {MattermostView} from 'main/views/MattermostView'; import { @@ -54,14 +54,12 @@ import { openScreensharePermissionsSettingsMacOS, } from '../utils'; -import {ViewManager, LoadingScreenState} from '../views/viewManager'; - +import {ViewManager} from '../views/viewManager'; +import LoadingScreen from '../views/loadingScreen'; import TeamDropdownView from '../views/teamDropdownView'; import DownloadsDropdownView from '../views/downloadsDropdownView'; import DownloadsDropdownMenuView from '../views/downloadsDropdownMenuView'; -import downloadsManager from 'main/downloadsManager'; - import MainWindow from './mainWindow'; import CallsWidgetWindow from './callsWidgetWindow'; @@ -86,7 +84,6 @@ export class WindowManager { ipcMain.on(HISTORY, this.handleHistory); ipcMain.handle(GET_DARK_MODE, this.handleGetDarkMode); ipcMain.on(REACT_APP_INITIALIZED, this.handleReactAppInitialized); - ipcMain.on(LOADING_SCREEN_ANIMATION_FINISHED, this.handleLoadingScreenAnimationFinished); ipcMain.on(BROWSER_HISTORY_PUSH, this.handleBrowserHistoryPush); ipcMain.on(BROWSER_HISTORY_BUTTON, this.handleBrowserHistoryButton); ipcMain.on(APP_LOGGED_IN, this.handleAppLoggedIn); @@ -270,14 +267,14 @@ export class WindowManager { return; } - if (this.isResizing && this.viewManager.loadingScreenState === LoadingScreenState.HIDDEN && this.viewManager.getCurrentView()) { + if (this.isResizing && LoadingScreen.isHidden() && this.viewManager.getCurrentView()) { log.debug('prevented resize'); event.preventDefault(); return; } this.throttledWillResize(newBounds); - this.viewManager?.setLoadingScreenBounds(); + LoadingScreen.setBounds(); this.teamDropdown?.updateWindowBounds(); this.downloadsDropdown?.updateWindowBounds(); this.downloadsDropdownMenu?.updateWindowBounds(); @@ -324,7 +321,8 @@ export class WindowManager { // Another workaround since the window doesn't update properly under Linux for some reason // See above comment setTimeout(this.setCurrentViewBounds, 10, bounds); - this.viewManager.setLoadingScreenBounds(); + + LoadingScreen.setBounds(); this.teamDropdown?.updateWindowBounds(); this.downloadsDropdown?.updateWindowBounds(); this.downloadsDropdownMenu?.updateWindowBounds(); @@ -542,24 +540,6 @@ export class WindowManager { } } - handleLoadingScreenAnimationFinished = () => { - log.debug('WindowManager.handleLoadingScreenAnimationFinished'); - - if (this.viewManager) { - this.viewManager.hideLoadingScreen(); - } - - if (process.env.NODE_ENV === 'test') { - app.emit('e2e-app-loaded'); - } - } - - updateLoadingScreenDarkMode = (darkMode: boolean) => { - if (this.viewManager) { - this.viewManager.updateLoadingScreenDarkMode(darkMode); - } - } - getViewNameByWebContentsId = (webContentsId: number) => { const view = this.viewManager?.findViewByWebContent(webContentsId); return view?.name; @@ -599,7 +579,7 @@ export class WindowManager { reload = () => { const currentView = this.viewManager?.getCurrentView(); if (currentView) { - this.viewManager?.showLoadingScreen(); + LoadingScreen.show(); currentView.reload(); } }