mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 21:56:18 -08:00
refactor: general code improvements
This commit is contained in:
1 parent
e2f3098b7e
commit
8233328b9a
10 files changed
+522
-151
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { app, BrowserWindow, Menu, MenuItem, shell } from 'electron';
|
||||
import * as path from 'path';
|
||||
import { SHOW_WHATS_NEW } from './shared/ipc-commands';
|
||||
import { SHOW_WHATS_NEW, VIEW_ADD_PLAYLIST, VIEW_SETTINGS } from './shared/ipc-commands';
|
||||
const openAboutWindow = require('about-window').default;
|
||||
|
||||
export class AppMenu {
|
||||
@@ -48,14 +48,14 @@ export class AppMenu {
|
||||
{
|
||||
label: 'Add playlist',
|
||||
click: () =>
|
||||
this.window.webContents.send('add-playlist-view'),
|
||||
this.window.webContents.send(VIEW_ADD_PLAYLIST),
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
click: () => this.window.webContents.send('settings-view'),
|
||||
click: () => this.window.webContents.send(VIEW_SETTINGS),
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
|
||||
+239
-10
@@ -1,12 +1,30 @@
|
||||
import { NgxWhatsNewModule } from 'ngx-whats-new';
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import {
|
||||
ComponentFixture,
|
||||
inject,
|
||||
TestBed,
|
||||
waitForAsync,
|
||||
} from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
import { TranslateModule, TranslatePipe } from '@ngx-translate/core';
|
||||
import {
|
||||
TranslateModule,
|
||||
TranslatePipe,
|
||||
TranslateService,
|
||||
} from '@ngx-translate/core';
|
||||
import { ElectronService } from './services/electron.service';
|
||||
import { ElectronServiceStub } from './services/electron.service.stub';
|
||||
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MockModule, MockPipe } from 'ng-mocks';
|
||||
import { of } from 'rxjs';
|
||||
import { WhatsNewService } from './services/whats-new.service';
|
||||
import { Theme } from './settings/theme.enum';
|
||||
import { SettingsService } from './services/settings.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { ChannelStore } from './state';
|
||||
import { STORE_KEY } from './shared/enums/store-keys.enum';
|
||||
import { WhatsNewServiceStub } from './services/whats-new.service.stub';
|
||||
import { IpcCommand } from '../../shared/ipc-command.class';
|
||||
|
||||
jest.mock('custom-electron-titlebar', () => {
|
||||
return {
|
||||
@@ -24,6 +42,14 @@ class MatSnackBarStub {
|
||||
}
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let component: AppComponent;
|
||||
let electronService: ElectronService;
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
let settingsService: SettingsService;
|
||||
let translateService: TranslateService;
|
||||
let whatsNewService: WhatsNewService;
|
||||
const defaultLanguage = 'en';
|
||||
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -31,6 +57,9 @@ describe('AppComponent', () => {
|
||||
providers: [
|
||||
{ provide: ElectronService, useClass: ElectronServiceStub },
|
||||
{ provide: MatSnackBar, useClass: MatSnackBarStub },
|
||||
{ provide: WhatsNewService, useClass: WhatsNewServiceStub },
|
||||
TranslateService,
|
||||
SettingsService, // TODO: stub
|
||||
],
|
||||
imports: [
|
||||
MockModule(MatSnackBarModule),
|
||||
@@ -42,12 +71,212 @@ describe('AppComponent', () => {
|
||||
})
|
||||
);
|
||||
|
||||
it(
|
||||
'should create the app',
|
||||
waitForAsync(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
})
|
||||
);
|
||||
beforeEach(() => {
|
||||
electronService = TestBed.inject(ElectronService);
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
settingsService = TestBed.inject(SettingsService);
|
||||
translateService = TestBed.inject(TranslateService);
|
||||
whatsNewService = TestBed.inject(WhatsNewService);
|
||||
component = fixture.componentInstance;
|
||||
component.modals = [];
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create the component and set default language', () => {
|
||||
spyOn(translateService, 'setDefaultLang');
|
||||
spyOn(component, 'setRendererListeners');
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
expect(component.commandsList.length).toEqual(5);
|
||||
expect(IpcCommand).toHaveBeenCalledTimes(5);
|
||||
});
|
||||
|
||||
it('should init component', () => {
|
||||
spyOn(translateService, 'setDefaultLang');
|
||||
spyOn(component, 'setRendererListeners');
|
||||
spyOn(component, 'initSettings');
|
||||
spyOn(component, 'handleWhatsNewDialog');
|
||||
component.ngOnInit();
|
||||
expect(translateService.setDefaultLang).toHaveBeenCalledWith(
|
||||
defaultLanguage
|
||||
);
|
||||
expect(component.setRendererListeners).toHaveBeenCalledTimes(1);
|
||||
expect(component.initSettings).toHaveBeenCalledTimes(1);
|
||||
expect(component.handleWhatsNewDialog).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe('Test ipc listeners and commands', () => {
|
||||
it('should set IPC listeners', () => {
|
||||
spyOn(electronService.ipcRenderer, 'on');
|
||||
component.setRendererListeners();
|
||||
expect(electronService.ipcRenderer.on).toHaveBeenCalledTimes(5);
|
||||
});
|
||||
|
||||
it('should remove all ipc listeners on destroy', () => {
|
||||
spyOn(electronService.ipcRenderer, 'removeAllListeners');
|
||||
component.ngOnDestroy();
|
||||
expect(
|
||||
electronService.ipcRenderer.removeAllListeners
|
||||
).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it('should navigate to the provided route', inject(
|
||||
[Router],
|
||||
(router: Router) => {
|
||||
const route = '/add-playlists';
|
||||
spyOn(router, 'navigateByUrl');
|
||||
component.navigateToRoute(route);
|
||||
expect(router.navigateByUrl).toHaveBeenCalledTimes(1);
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith(
|
||||
route,
|
||||
expect.anything()
|
||||
);
|
||||
}
|
||||
));
|
||||
|
||||
it('should show a notification on epg error', inject(
|
||||
[MatSnackBar],
|
||||
(snackbar: MatSnackBar) => {
|
||||
spyOn(snackbar, 'open');
|
||||
component.onEpgError();
|
||||
expect(snackbar.open).toHaveBeenCalledTimes(1);
|
||||
}
|
||||
));
|
||||
|
||||
it('should handle epg download success', inject(
|
||||
[MatSnackBar, ChannelStore],
|
||||
(snackbar: MatSnackBar, channelStore: ChannelStore) => {
|
||||
spyOn(snackbar, 'open');
|
||||
spyOn(channelStore, 'setEpgAvailableFlag');
|
||||
component.onEpgFetchDone();
|
||||
expect(snackbar.open).toHaveBeenCalledTimes(1);
|
||||
expect(channelStore.setEpgAvailableFlag).toHaveBeenCalledWith(
|
||||
true
|
||||
);
|
||||
}
|
||||
));
|
||||
|
||||
it('show show whats new dialog', () => {
|
||||
spyOn(whatsNewService, 'getModalsByVersion');
|
||||
spyOn(component, 'setDialogVisibility');
|
||||
component.showWhatsNewDialog();
|
||||
expect(whatsNewService.getModalsByVersion).toHaveBeenCalledTimes(1);
|
||||
expect(component.setDialogVisibility).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test version handling', () => {
|
||||
it('should get actual app version which is outdated and show updates dialog', () => {
|
||||
const currentAppVersion = '0.0.1';
|
||||
const spyOnSettingsGet = spyOn(
|
||||
settingsService,
|
||||
'getValueFromLocalStorage'
|
||||
).and.returnValue(of(currentAppVersion));
|
||||
spyOn(whatsNewService, 'getModalsByVersion').and.returnValue([{}]);
|
||||
spyOn(whatsNewService, 'changeDialogVisibleState');
|
||||
|
||||
component.handleWhatsNewDialog();
|
||||
expect(spyOnSettingsGet).toHaveBeenCalled();
|
||||
expect(whatsNewService.getModalsByVersion).toHaveBeenCalled();
|
||||
expect(
|
||||
whatsNewService.changeDialogVisibleState
|
||||
).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('should get actual app version which is not outdated and do not shop updates dialog', () => {
|
||||
const currentAppVersion = '1.0.0';
|
||||
const spyOnSettingsGet = spyOn(
|
||||
settingsService,
|
||||
'getValueFromLocalStorage'
|
||||
).and.returnValue(of(currentAppVersion));
|
||||
spyOn(whatsNewService, 'getModalsByVersion').and.returnValue([{}]);
|
||||
spyOn(whatsNewService, 'changeDialogVisibleState');
|
||||
|
||||
component.handleWhatsNewDialog();
|
||||
expect(spyOnSettingsGet).toHaveBeenCalled();
|
||||
expect(whatsNewService.getModalsByVersion).toHaveBeenCalledTimes(0);
|
||||
expect(
|
||||
whatsNewService.changeDialogVisibleState
|
||||
).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should change the visibility of the whats new dialog', () => {
|
||||
spyOn(whatsNewService, 'changeDialogVisibleState');
|
||||
component.modals = [{}, {}];
|
||||
const visibilityFlag = true;
|
||||
component.setDialogVisibility(true);
|
||||
|
||||
expect(
|
||||
whatsNewService.changeDialogVisibleState
|
||||
).toHaveBeenCalledWith(visibilityFlag);
|
||||
expect(
|
||||
whatsNewService.changeDialogVisibleState
|
||||
).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not change the visibility of the whats new dialog', () => {
|
||||
spyOn(whatsNewService, 'changeDialogVisibleState');
|
||||
component.modals = [];
|
||||
component.setDialogVisibility(true);
|
||||
expect(
|
||||
whatsNewService.changeDialogVisibleState
|
||||
).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Set initial settings', () => {
|
||||
const theme = Theme.DarkTheme;
|
||||
const language = 'es';
|
||||
const epgUrl = 'http://localhost/epg.xml';
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(electronService.ipcRenderer, 'send');
|
||||
spyOn(settingsService, 'changeTheme');
|
||||
spyOn(component, 'handleWhatsNewDialog');
|
||||
spyOn(translateService, 'use');
|
||||
});
|
||||
|
||||
it('should get and init settings (all settings are defined)', () => {
|
||||
const spyOnSettingsGet = spyOn(
|
||||
settingsService,
|
||||
'getValueFromLocalStorage'
|
||||
).and.returnValue(of({ theme, epgUrl, language }));
|
||||
|
||||
component.initSettings();
|
||||
|
||||
expect(spyOnSettingsGet).toHaveBeenCalledWith(STORE_KEY.Settings);
|
||||
expect(settingsService.changeTheme).toHaveBeenCalledWith(theme);
|
||||
expect(electronService.ipcRenderer.send).toHaveBeenCalledTimes(1);
|
||||
expect(translateService.use).toHaveBeenCalledWith(language);
|
||||
});
|
||||
|
||||
it('should get and init settings (nothing is defined)', () => {
|
||||
const spyOnSettingsGet = spyOn(
|
||||
settingsService,
|
||||
'getValueFromLocalStorage'
|
||||
).and.returnValue(of());
|
||||
|
||||
component.initSettings();
|
||||
|
||||
expect(spyOnSettingsGet).toHaveBeenCalledWith(STORE_KEY.Settings);
|
||||
expect(settingsService.changeTheme).toHaveBeenCalledTimes(0);
|
||||
expect(electronService.ipcRenderer.send).toHaveBeenCalledTimes(0);
|
||||
expect(translateService.use).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should get and init settings (only theme is defined)', () => {
|
||||
const spyOnSettingsGet = spyOn(
|
||||
settingsService,
|
||||
'getValueFromLocalStorage'
|
||||
).and.returnValue(of({ theme }));
|
||||
|
||||
component.initSettings();
|
||||
|
||||
expect(spyOnSettingsGet).toHaveBeenCalledWith(STORE_KEY.Settings);
|
||||
expect(settingsService.changeTheme).toHaveBeenCalledWith(theme);
|
||||
expect(electronService.ipcRenderer.send).toHaveBeenCalledTimes(0);
|
||||
expect(translateService.use).toHaveBeenCalledWith(defaultLanguage);
|
||||
});
|
||||
});
|
||||
});
|
||||
+130
-89
@@ -3,7 +3,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { Titlebar, Color } from 'custom-electron-titlebar';
|
||||
import { ElectronService } from './services/electron.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar';
|
||||
import { ChannelStore } from './state';
|
||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import { Settings } from './settings/settings.interface';
|
||||
@@ -12,11 +12,15 @@ import {
|
||||
EPG_FETCH,
|
||||
EPG_FETCH_DONE,
|
||||
SHOW_WHATS_NEW,
|
||||
VIEW_ADD_PLAYLIST,
|
||||
VIEW_SETTINGS,
|
||||
} from '../../shared/ipc-commands';
|
||||
import { IpcCommand } from '../../shared/ipc-command.class';
|
||||
import { SettingsService } from './services/settings.service';
|
||||
import { WhatsNewService } from './services/whats-new.service';
|
||||
import * as semver from 'semver';
|
||||
import { ModalWindow } from 'ngx-whats-new/lib/modal-window.interface';
|
||||
import { STORE_KEY } from './shared/enums/store-keys.enum';
|
||||
|
||||
// create custom title bar
|
||||
new Titlebar({
|
||||
@@ -25,6 +29,9 @@ new Titlebar({
|
||||
enableMnemonics: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* AppComponent
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
@@ -32,10 +39,30 @@ new Titlebar({
|
||||
export class AppComponent {
|
||||
/** Visibility flag of the "what is new" modal dialog */
|
||||
isDialogVisible$ = this.whatsNewService.dialogState$;
|
||||
|
||||
/** Dialog options */
|
||||
options = this.whatsNewService.options;
|
||||
|
||||
/** Modals to show for the updated version of the application */
|
||||
modals: ModalWindow[];
|
||||
modals: ModalWindow[] = [];
|
||||
|
||||
/** Default options for epg snackbar notifications */
|
||||
epgSnackBarOptions: MatSnackBarConfig = {
|
||||
verticalPosition: 'bottom',
|
||||
horizontalPosition: 'right',
|
||||
};
|
||||
|
||||
/** List of ipc commands with function mapping */
|
||||
commandsList = [
|
||||
new IpcCommand(VIEW_ADD_PLAYLIST, () => this.navigateToRoute('/')),
|
||||
new IpcCommand(VIEW_SETTINGS, () => this.navigateToRoute('/settings')),
|
||||
new IpcCommand(EPG_FETCH_DONE, () => this.onEpgFetchDone),
|
||||
new IpcCommand(EPG_ERROR, () => this.onEpgError),
|
||||
new IpcCommand(SHOW_WHATS_NEW, () => this.showWhatsNewDialog),
|
||||
];
|
||||
|
||||
/** Default language as fallback */
|
||||
DEFAULT_LANG = 'en';
|
||||
|
||||
/**
|
||||
* Creates an instance of AppComponent
|
||||
@@ -51,10 +78,6 @@ export class AppComponent {
|
||||
private storage: StorageMap,
|
||||
private whatsNewService: WhatsNewService
|
||||
) {
|
||||
this.translate.setDefaultLang('en');
|
||||
|
||||
this.setRendererListeners();
|
||||
|
||||
if (
|
||||
(this.electronService.remote.process.platform === 'linux' ||
|
||||
this.electronService.remote.process.platform === 'win32') &&
|
||||
@@ -76,76 +99,53 @@ export class AppComponent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all necessary listeners for the events from the renderer process
|
||||
* Starts all the functions to initialize the component
|
||||
*/
|
||||
setRendererListeners(): void {
|
||||
this.electronService.ipcRenderer.on('add-playlist-view', () => {
|
||||
this.ngZone.run(() => {
|
||||
this.router.navigateByUrl('/', { skipLocationChange: true });
|
||||
});
|
||||
});
|
||||
ngOnInit(): void {
|
||||
this.translate.setDefaultLang(this.DEFAULT_LANG);
|
||||
|
||||
this.electronService.ipcRenderer.on('settings-view', () => {
|
||||
this.ngZone.run(() => {
|
||||
this.router.navigateByUrl('/settings', {
|
||||
skipLocationChange: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.electronService.ipcRenderer.on(EPG_FETCH_DONE, () => {
|
||||
this.ngZone.run(() => {
|
||||
this.channelStore.setEpgAvailableFlag(true);
|
||||
this.snackBar.open('EPG was successfully downloaded!', null, {
|
||||
duration: 2000,
|
||||
verticalPosition: 'bottom',
|
||||
horizontalPosition: 'right',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.electronService.ipcRenderer.on(EPG_ERROR, () => {
|
||||
this.snackBar.open('EPG Error: something went wrong...', null, {
|
||||
duration: 2000,
|
||||
verticalPosition: 'bottom',
|
||||
horizontalPosition: 'right',
|
||||
});
|
||||
});
|
||||
|
||||
this.electronService.ipcRenderer.on(SHOW_WHATS_NEW, () => {
|
||||
this.ngZone.run(() => {
|
||||
this.modals = this.whatsNewService.getModalsByVersion(
|
||||
this.electronService.getAppVersion()
|
||||
);
|
||||
this.setDialogVisibility(true);
|
||||
});
|
||||
});
|
||||
this.setRendererListeners();
|
||||
this.initSettings();
|
||||
this.handleWhatsNewDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes for app settings on component init
|
||||
* Initializes all necessary listeners for the events from the renderer process
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.storage.get('settings').subscribe((settings: Settings) => {
|
||||
if (settings && Object.keys(settings).length > 0) {
|
||||
this.translate.use(settings.language ?? 'en');
|
||||
if (settings.epgUrl) {
|
||||
this.electronService.ipcRenderer.send(EPG_FETCH, {
|
||||
url: settings.epgUrl,
|
||||
});
|
||||
this.snackBar.open('Fetch EPG data...', 'Close', {
|
||||
verticalPosition: 'bottom',
|
||||
horizontalPosition: 'right',
|
||||
});
|
||||
}
|
||||
setRendererListeners(): void {
|
||||
this.commandsList.forEach((command) =>
|
||||
this.electronService.ipcRenderer.on(command.id, () =>
|
||||
this.ngZone.run(() => command.callback())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (settings.theme) {
|
||||
this.settingsService.changeTheme(settings.theme);
|
||||
}
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Reads the settings object from local storage and initializes the
|
||||
* application based on them
|
||||
*/
|
||||
initSettings(): void {
|
||||
this.settingsService
|
||||
.getValueFromLocalStorage(STORE_KEY.Settings)
|
||||
.subscribe((settings: Settings) => {
|
||||
if (settings && Object.keys(settings).length > 0) {
|
||||
this.translate.use(settings.language ?? this.DEFAULT_LANG);
|
||||
if (settings.epgUrl) {
|
||||
this.electronService.ipcRenderer.send(EPG_FETCH, {
|
||||
url: settings.epgUrl,
|
||||
});
|
||||
this.snackBar.open(
|
||||
this.translate.instant('FETCH_EPG'),
|
||||
this.translate.instant('CLOSE'),
|
||||
this.epgSnackBarOptions
|
||||
);
|
||||
}
|
||||
|
||||
this.handleWhatsNewDialog();
|
||||
if (settings.theme) {
|
||||
this.settingsService.changeTheme(settings.theme);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,23 +153,23 @@ export class AppComponent {
|
||||
*/
|
||||
handleWhatsNewDialog(): void {
|
||||
const actualVersion = this.electronService.getAppVersion();
|
||||
this.storage.get('version').subscribe((version) => {
|
||||
const isNewVersion = semver.gt(actualVersion, version || '0.0.0');
|
||||
if (!version || isNewVersion) {
|
||||
this.modals =
|
||||
this.whatsNewService.getModalsByVersion(actualVersion);
|
||||
this.setDialogVisibility(true);
|
||||
}
|
||||
this.setVersion(actualVersion);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the version of the application in localstorage
|
||||
* @param version actual version of the application
|
||||
*/
|
||||
setVersion(version: string): void {
|
||||
this.storage.set('version', version).subscribe(() => {});
|
||||
this.settingsService
|
||||
.getValueFromLocalStorage(STORE_KEY.Version)
|
||||
.subscribe((version) => {
|
||||
const isNewVersion = semver.gt(
|
||||
actualVersion,
|
||||
version || '0.0.0'
|
||||
);
|
||||
if (!version || isNewVersion) {
|
||||
this.modals =
|
||||
this.whatsNewService.getModalsByVersion(actualVersion);
|
||||
this.setDialogVisibility(true);
|
||||
}
|
||||
this.settingsService.setValueToLocalStorage(
|
||||
STORE_KEY.Version,
|
||||
actualVersion
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,15 +182,56 @@ export class AppComponent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the specified route
|
||||
* @param route route to navigate to
|
||||
*/
|
||||
navigateToRoute(route: string) {
|
||||
this.router.navigateByUrl(route, { skipLocationChange: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when the EPG fetching is done
|
||||
*/
|
||||
onEpgFetchDone(): void {
|
||||
this.channelStore.setEpgAvailableFlag(true);
|
||||
this.snackBar.open(
|
||||
this.translate.instant('EPG.DOWNLOAD_SUCCESS'),
|
||||
null,
|
||||
{
|
||||
...this.epgSnackBarOptions,
|
||||
duration: 2000,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles epg error
|
||||
*/
|
||||
onEpgError(): void {
|
||||
this.snackBar.open(this.translate.instant('EPG.ERROR'), null, {
|
||||
...this.epgSnackBarOptions,
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the "what is new" dialog
|
||||
*/
|
||||
showWhatsNewDialog(): void {
|
||||
this.modals = this.whatsNewService.getModalsByVersion(
|
||||
this.electronService.getAppVersion()
|
||||
);
|
||||
this.setDialogVisibility(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all ipc command listeners on component destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.electronService.ipcRenderer.removeAllListeners(EPG_FETCH_DONE);
|
||||
this.electronService.ipcRenderer.removeAllListeners(EPG_ERROR);
|
||||
this.electronService.ipcRenderer.removeAllListeners(
|
||||
'add-playlist-view'
|
||||
);
|
||||
this.electronService.ipcRenderer.removeAllListeners('settings-view');
|
||||
this.electronService.ipcRenderer.removeAllListeners(VIEW_ADD_PLAYLIST);
|
||||
this.electronService.ipcRenderer.removeAllListeners(VIEW_SETTINGS);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,9 @@ import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { MatSidenav } from '@angular/material/sidenav';
|
||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import { Settings, SETTINGS_STORE_KEY, VideoPlayer } from '../../../settings/settings.interface';
|
||||
import { Settings, VideoPlayer } from '../../../settings/settings.interface';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { STORE_KEY } from '../../../shared/enums/store-keys.enum';
|
||||
|
||||
@Component({
|
||||
selector: 'app-video-player',
|
||||
@@ -74,7 +75,7 @@ export class VideoPlayerComponent implements OnInit {
|
||||
* Reads the app configuration from the browsers storage and applies the settings in the current component
|
||||
*/
|
||||
applySettings(): void {
|
||||
this.storage.get(SETTINGS_STORE_KEY).subscribe((settings: Settings) => {
|
||||
this.storage.get(STORE_KEY.Settings).subscribe((settings: Settings) => {
|
||||
if (settings && Object.keys(settings).length > 0) {
|
||||
this.playerSettings = {
|
||||
player: settings.player,
|
||||
|
||||
@@ -10,12 +10,12 @@ export class ElectronServiceStub {
|
||||
argv: [0, 1],
|
||||
},
|
||||
app: {
|
||||
getVersion: jest.fn(),
|
||||
getVersion: jest.fn(() => '1.0.0'),
|
||||
}
|
||||
};
|
||||
|
||||
getAppVersion() {
|
||||
this.remote.app.getVersion();
|
||||
return this.remote.app.getVersion();
|
||||
}
|
||||
|
||||
sendIpcEvent() {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import { of } from 'rxjs';
|
||||
import { Theme } from '../settings/theme.enum';
|
||||
import { STORE_KEY } from '../shared/enums/store-keys.enum';
|
||||
import { SettingsService } from './settings.service';
|
||||
|
||||
describe('Service: Settings', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [SettingsService],
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a service instance', inject(
|
||||
[SettingsService],
|
||||
(service: SettingsService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}
|
||||
));
|
||||
|
||||
it('should set key/value pair', inject(
|
||||
[SettingsService, StorageMap],
|
||||
(service: SettingsService, storage: StorageMap) => {
|
||||
const version = '2.1.0';
|
||||
spyOn(storage, 'set').and.returnValue(of([]));
|
||||
service.setValueToLocalStorage(STORE_KEY.Version, version);
|
||||
expect(storage.set).toHaveBeenCalledWith(
|
||||
STORE_KEY.Version,
|
||||
version
|
||||
);
|
||||
}
|
||||
));
|
||||
|
||||
it('should get value from the local storage', inject(
|
||||
[SettingsService, StorageMap],
|
||||
(service: SettingsService, storage: StorageMap) => {
|
||||
const version = '2.1.0';
|
||||
service.setValueToLocalStorage(STORE_KEY.Version, version);
|
||||
service
|
||||
.getValueFromLocalStorage(STORE_KEY.Version)
|
||||
.subscribe((value) => {
|
||||
expect(value).toBe(version);
|
||||
});
|
||||
}
|
||||
));
|
||||
|
||||
describe('Test theme switch', () => {
|
||||
let spyOnAdd, spyOnRemove;
|
||||
beforeEach(() => {
|
||||
spyOnAdd = spyOn(document.body.classList, 'add');
|
||||
spyOnRemove = spyOn(document.body.classList, 'remove');
|
||||
});
|
||||
|
||||
it('should switch to the dark theme', inject(
|
||||
[SettingsService],
|
||||
(service: SettingsService) => {
|
||||
service.changeTheme(Theme.DarkTheme);
|
||||
expect(spyOnRemove).toHaveBeenCalledTimes(0);
|
||||
expect(spyOnAdd).toHaveBeenCalledTimes(1);
|
||||
}
|
||||
));
|
||||
|
||||
it('should switch to the light theme', inject(
|
||||
[SettingsService],
|
||||
(service: SettingsService) => {
|
||||
service.changeTheme(Theme.LightTheme);
|
||||
expect(spyOnRemove).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnAdd).toHaveBeenCalledTimes(0);
|
||||
}
|
||||
));
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,15 @@
|
||||
import { Theme } from './../settings/theme.enum';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import { STORE_KEY } from '../shared/enums/store-keys.enum';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class SettingsService {
|
||||
/** Creates an instance of SettingsService */
|
||||
constructor(private storage: StorageMap) {}
|
||||
|
||||
/**
|
||||
* Changes the visual theme of the application
|
||||
* @param selectedTheme theme to set
|
||||
@@ -16,4 +21,22 @@ export class SettingsService {
|
||||
document.body.classList.add('dark-theme');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the given key from the local storage
|
||||
* @param key key to get
|
||||
* @returns returns the value of the given key
|
||||
*/
|
||||
getValueFromLocalStorage(key: STORE_KEY) {
|
||||
return this.storage.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given key/value pair in the local storage
|
||||
* @param key key to set
|
||||
* @param value value to set
|
||||
*/
|
||||
setValueToLocalStorage(key: STORE_KEY, value: string) {
|
||||
this.storage.set(key, value).subscribe(() => {});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export class WhatsNewServiceStub {
|
||||
changeDialogVisibleState(): void {}
|
||||
getModalsByVersion(): any {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import { Router } from '@angular/router';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Settings, SETTINGS_STORE_KEY, VideoPlayer } from './settings.interface';
|
||||
import { STORE_KEY } from '../shared/enums/store-keys.enum';
|
||||
import { Settings, VideoPlayer } from './settings.interface';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import * as semver from 'semver';
|
||||
import { ElectronService } from '../services/electron.service';
|
||||
@@ -59,7 +60,7 @@ export class SettingsComponent implements OnInit {
|
||||
themeEnum = Theme;
|
||||
|
||||
/**
|
||||
* Creates an instance of SettingsComponent and injects
|
||||
* Creates an instance of SettingsComponent and injects
|
||||
* required dependencies into the component
|
||||
* @param channelQuery
|
||||
* @param electronService
|
||||
@@ -94,50 +95,54 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the config object from the browsers
|
||||
* Reads the config object from the browsers
|
||||
* storage (indexed db)
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.storage.get(SETTINGS_STORE_KEY).subscribe((settings: Settings) => {
|
||||
if (settings) {
|
||||
this.settingsForm.setValue({
|
||||
player: settings.player
|
||||
? settings.player
|
||||
: VideoPlayer.VideoJs,
|
||||
epgUrl: settings.epgUrl ? settings.epgUrl : '',
|
||||
language: settings.language
|
||||
? settings.language
|
||||
: Language.ENGLISH,
|
||||
showCaptions: settings.showCaptions
|
||||
? settings.showCaptions
|
||||
: false,
|
||||
theme: settings.theme
|
||||
? settings.theme
|
||||
: Theme.LightTheme,
|
||||
});
|
||||
}
|
||||
});
|
||||
this.settingsService
|
||||
.getValueFromLocalStorage(STORE_KEY.Settings)
|
||||
.subscribe((settings: Settings) => {
|
||||
if (settings) {
|
||||
this.settingsForm.setValue({
|
||||
player: settings.player
|
||||
? settings.player
|
||||
: VideoPlayer.VideoJs,
|
||||
epgUrl: settings.epgUrl ? settings.epgUrl : '',
|
||||
language: settings.language
|
||||
? settings.language
|
||||
: Language.ENGLISH,
|
||||
showCaptions: settings.showCaptions
|
||||
? settings.showCaptions
|
||||
: false,
|
||||
theme: settings.theme
|
||||
? settings.theme
|
||||
: Theme.LightTheme,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the latest version of the application
|
||||
* Checks whether the latest version of the application
|
||||
* is used and updates the version message in the
|
||||
* settings UI
|
||||
*/
|
||||
checkAppVersion(): void {
|
||||
this.http
|
||||
.get(PACKAGE_JSON_URL)
|
||||
.pipe(catchError(err => {
|
||||
console.error(err);
|
||||
throw new Error(err);
|
||||
}))
|
||||
.subscribe((response: { version: string }) => {
|
||||
this.showVersionInformation(response.version);
|
||||
});
|
||||
.get(PACKAGE_JSON_URL)
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
console.error(err);
|
||||
throw new Error(err);
|
||||
})
|
||||
)
|
||||
.subscribe((response: { version: string }) => {
|
||||
this.showVersionInformation(response.version);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the message in settings UI about the used
|
||||
* Updates the message in settings UI about the used
|
||||
* version of the app
|
||||
* @param currentVersion current version of the application
|
||||
*/
|
||||
@@ -158,17 +163,14 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares actual with latest version of the
|
||||
* application
|
||||
* Compares actual with latest version of the
|
||||
* application
|
||||
* @param latestVersion latest version
|
||||
* @returns returns true if an update is available
|
||||
*/
|
||||
isCurrentVersionOutdated(latestVersion: string): boolean {
|
||||
this.version = this.electronService.getAppVersion();
|
||||
return semver.lt(
|
||||
this.version,
|
||||
latestVersion
|
||||
);
|
||||
return semver.lt(this.version, latestVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,7 +179,7 @@ export class SettingsComponent implements OnInit {
|
||||
*/
|
||||
onSubmit(): void {
|
||||
this.storage
|
||||
.set(SETTINGS_STORE_KEY, this.settingsForm.value)
|
||||
.set(STORE_KEY.Settings, this.settingsForm.value)
|
||||
.subscribe(() => {
|
||||
this.settingsForm.markAsPristine();
|
||||
// check whether the epg url was changed or not
|
||||
@@ -185,9 +187,7 @@ export class SettingsComponent implements OnInit {
|
||||
this.fetchEpg();
|
||||
}
|
||||
this.translate.use(this.settingsForm.value.language);
|
||||
this.settingsService.changeTheme(
|
||||
this.settingsForm.value.theme
|
||||
);
|
||||
this.settingsService.changeTheme(this.settingsForm.value.theme);
|
||||
this.snackBar.open(
|
||||
this.translate.instant('SETTINGS.SETTINGS_SAVED'),
|
||||
null,
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Language } from './language.enum';
|
||||
import { Theme } from './theme.enum';
|
||||
|
||||
/** Settings key in storage */
|
||||
export const SETTINGS_STORE_KEY = 'settings';
|
||||
|
||||
/**
|
||||
* Contains all types of supported video players
|
||||
* TODO: extract to separate file
|
||||
*/
|
||||
export enum VideoPlayer {
|
||||
VideoJs = 'videojs',
|
||||
|
||||
Reference in new issue
Block a user