Version 1.5.0: Update check and dialog popups implemented

This commit is contained in:
Mal 2021-03-21 23:56:54 +01:00
parent 6717077e80
commit 00a9a8c67f
26 changed files with 396 additions and 200 deletions

View File

@ -1,6 +1,8 @@
install:
ionic cordova plugin add cordova-plugin-local-notification
npm install --save @ionic-native/local-notifications
npm install --save @ionic-native/background-mode
npm install --save @ionic-native/app-minimize
npm install --save @ionic-native/core
npm install --save @capacitor/core

View File

@ -9,9 +9,9 @@
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg9367"
version="1.1"
viewBox="0 0 75.361905 75.361907"
height="75.361908mm"
width="75.361908mm"
viewBox="0 0 74.938352 74.938354"
height="74.938354mm"
width="74.938354mm"
sodipodi:docname="ic_launcher.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
<sodipodi:namedview
@ -49,20 +49,19 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<rect
style="fill:#2d122d;fill-opacity:1;stroke-width:1.19397;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.858824"
id="rect868"
width="75.361908"
height="75.361908"
x="0"
y="0" />
<circle
style="fill:#512051;fill-opacity:1;stroke-width:0.999478;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.858824"
id="path830"
r="37.469177"
cy="37.469177"
cx="37.469177" />
<path
id="rect882"
style="fill:#ffffff;fill-opacity:1;stroke-width:1.064;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.858824"
d="m 22.941029,12.684805 -10.256227,10.256222 5.696296,5.69681 10.256741,-10.256222 z m 29.479854,0 -5.69681,5.69681 10.256224,10.256222 5.69681,-5.69681 z M 31.90534,18.248807 v 2.764689 l 2.888197,2.981213 h 2.886644 2.888197 l 2.888197,-2.981213 V 18.248807 H 37.680181 Z M 18.249839,31.904304 v 5.776908 5.775875 h 2.764175 l 2.981215,-2.888194 v -2.887681 -2.888195 l -2.981215,-2.888713 z m 36.09806,0 -2.98173,2.888713 v 2.888195 2.887681 l 2.98173,2.888194 h 2.764171 v -5.775875 -5.776908 z m -35.966801,14.819768 -5.696296,5.69681 10.256227,10.256222 5.69681,-5.696294 z m 38.599199,0 -10.256224,10.256738 5.69681,5.696294 10.256224,-10.256222 z m -22.18676,4.643644 -2.888197,2.981214 v 2.763655 h 5.774841 5.776394 V 54.34893 l -2.888197,-2.981214 h -2.888197 z"
d="M 22.729251,12.473028 12.473024,22.72925 18.16932,28.42606 28.426061,18.169838 Z m 29.479854,0 -5.69681,5.69681 10.256224,10.256222 5.69681,-5.69681 z M 31.693562,18.03703 v 2.764689 l 2.888197,2.981213 h 2.886644 2.888197 l 2.888197,-2.981213 V 18.03703 H 37.468403 Z M 18.038061,31.692527 v 5.776908 5.775875 h 2.764175 l 2.981215,-2.888194 V 37.469435 34.58124 l -2.981215,-2.888713 z m 36.09806,0 -2.98173,2.888713 v 2.888195 2.887681 l 2.98173,2.888194 h 2.764171 v -5.775875 -5.776908 z m -35.966801,14.819768 -5.696296,5.69681 10.256227,10.256222 5.69681,-5.696294 z m 38.599199,0 -10.256224,10.256738 5.69681,5.696294 10.256224,-10.256222 z m -22.18676,4.643644 -2.888197,2.981214 v 2.763655 h 5.774841 5.776394 V 54.137153 L 40.3566,51.155939 h -2.888197 z"
sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccccccccccccc" />
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,67 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg9367"
version="1.1"
viewBox="0 0 74.938352 74.938354"
height="74.938354mm"
width="74.938354mm"
sodipodi:docname="ic_launcher_round.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1829"
inkscape:window-height="1016"
id="namedview1452"
showgrid="false"
inkscape:zoom="1.0134608"
inkscape:cx="298.30195"
inkscape:cy="201.14212"
inkscape:window-x="91"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg9367"
inkscape:document-rotation="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<defs
id="defs9361" />
<metadata
id="metadata9364">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<circle
style="fill:#2d122d;fill-opacity:1;stroke-width:0.999478;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.858824"
id="path830"
r="37.469177"
cy="37.469177"
cx="37.469177" />
<path
id="rect882"
style="fill:#ffffff;fill-opacity:1;stroke-width:1.064;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.858824"
d="M 22.729251,12.473028 12.473024,22.72925 18.16932,28.42606 28.426061,18.169838 Z m 29.479854,0 -5.69681,5.69681 10.256224,10.256222 5.69681,-5.69681 z M 31.693562,18.03703 v 2.764689 l 2.888197,2.981213 h 2.886644 2.888197 l 2.888197,-2.981213 V 18.03703 H 37.468403 Z M 18.038061,31.692527 v 5.776908 5.775875 h 2.764175 l 2.981215,-2.888194 V 37.469435 34.58124 l -2.981215,-2.888713 z m 36.09806,0 -2.98173,2.888713 v 2.888195 2.887681 l 2.98173,2.888194 h 2.764171 v -5.775875 -5.776908 z m -35.966801,14.819768 -5.696296,5.69681 10.256227,10.256222 5.69681,-5.696294 z m 38.599199,0 -10.256224,10.256738 5.69681,5.696294 10.256224,-10.256222 z m -22.18676,4.643644 -2.888197,2.981214 v 2.763655 h 5.774841 5.776394 V 54.137153 L 40.3566,51.155939 h -2.888197 z"
sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccccccccccccc" />
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<widget id="io.ionic.starter" version="1.4.3" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<widget id="io.ionic.starter" version="1.5.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>METAsocket</name>
<description>WowApp's awesome instant messenger for the whole Greifentanzgeschwader</description>
<author email="webmaster@sabolli.de" href="https://sabolli.de/metasocket/">sabolli</author>

93
package-lock.json generated
View File

@ -19,8 +19,6 @@
"@ionic-native/core": "^5.31.1",
"@ionic-native/foreground-service": "^5.31.1",
"@ionic-native/local-notifications": "^5.31.1",
"@ionic-native/network": "^5.31.1",
"@ionic-native/power-management": "^5.31.1",
"@ionic/angular": "^5.5.2",
"rxjs": "~6.6.0",
"tslib": "^2.0.0",
@ -36,6 +34,7 @@
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"co.mylonas.cordova.applicationstate": "github:leomylonas/cordova-plugin-applicationstate",
"codelyzer": "^6.0.0",
"cordova-android": "^9.0.0",
"cordova-plugin-app-exit": "0.0.2",
@ -47,8 +46,6 @@
"cordova-plugin-ionic-keyboard": "^2.2.0",
"cordova-plugin-ionic-webview": "^4.2.1",
"cordova-plugin-local-notification": "^0.9.0-beta.2",
"cordova-plugin-network-information": "^2.0.2",
"cordova-plugin-powermanagement-orig": "^1.1.2",
"cordova-plugin-splashscreen": "^5.0.2",
"cordova-plugin-statusbar": "^2.4.2",
"cordova-plugin-whitelist": "^1.3.3",
@ -1777,30 +1774,6 @@
"@types/cordova": "^0.0.34"
}
},
"node_modules/@ionic-native/network": {
"version": "5.31.1",
"resolved": "https://registry.npmjs.org/@ionic-native/network/-/network-5.31.1.tgz",
"integrity": "sha512-dSN7jaiXXgm8kVcvxcD39wWPFg2NujWTrEnj9Fq7Fx2aGKkxPkocNsxoy02aFVyBp1LQZvp+uiDNmVIVRjrA7A==",
"dependencies": {
"@types/cordova": "latest"
},
"peerDependencies": {
"@ionic-native/core": "^5.1.0",
"rxjs": "^5.5.0 || ^6.5.0"
}
},
"node_modules/@ionic-native/power-management": {
"version": "5.31.1",
"resolved": "https://registry.npmjs.org/@ionic-native/power-management/-/power-management-5.31.1.tgz",
"integrity": "sha512-SJiTOMMkEEJ7B/MNztCjnwCKEF416hRDLquSeK6zJbRhPzMsWLr7Cb+BzjvwV5pfo05ilfzV08yB95IBdRteUg==",
"dependencies": {
"@types/cordova": "latest"
},
"peerDependencies": {
"@ionic-native/core": "^5.1.0",
"rxjs": "^5.5.0 || ^6.5.0"
}
},
"node_modules/@ionic/angular": {
"version": "5.5.5",
"resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-5.5.5.tgz",
@ -3962,6 +3935,18 @@
"node": ">=6"
}
},
"node_modules/co.mylonas.cordova.applicationstate": {
"version": "0.0.2",
"resolved": "git+ssh://git@github.com/leomylonas/cordova-plugin-applicationstate.git#1ead7d5045eed494bd1bb8dabde461208634780b",
"dev": true,
"engines": [
{
"name": "cordova",
"version": ">=3.0.0"
}
],
"license": "CC-BY-4.0"
},
"node_modules/coa": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz",
@ -4891,25 +4876,6 @@
}
]
},
"node_modules/cordova-plugin-network-information": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/cordova-plugin-network-information/-/cordova-plugin-network-information-2.0.2.tgz",
"integrity": "sha512-NwO3qDBNL/vJxUxBTPNOA1HvkDf9eTeGH8JSZiwy1jq2W2mJKQEDBwqWkaEQS19Yd/MQTiw0cykxg5D7u4J6cQ==",
"dev": true,
"engines": {
"cordovaDependencies": {
"3.0.0": {
"cordova": ">100"
}
}
}
},
"node_modules/cordova-plugin-powermanagement-orig": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cordova-plugin-powermanagement-orig/-/cordova-plugin-powermanagement-orig-1.1.2.tgz",
"integrity": "sha1-hVuljKvnndWxECXkWG/ywe0YnOA=",
"dev": true
},
"node_modules/cordova-plugin-splashscreen": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/cordova-plugin-splashscreen/-/cordova-plugin-splashscreen-5.0.2.tgz",
@ -20432,22 +20398,6 @@
"@types/cordova": "^0.0.34"
}
},
"@ionic-native/network": {
"version": "5.31.1",
"resolved": "https://registry.npmjs.org/@ionic-native/network/-/network-5.31.1.tgz",
"integrity": "sha512-dSN7jaiXXgm8kVcvxcD39wWPFg2NujWTrEnj9Fq7Fx2aGKkxPkocNsxoy02aFVyBp1LQZvp+uiDNmVIVRjrA7A==",
"requires": {
"@types/cordova": "latest"
}
},
"@ionic-native/power-management": {
"version": "5.31.1",
"resolved": "https://registry.npmjs.org/@ionic-native/power-management/-/power-management-5.31.1.tgz",
"integrity": "sha512-SJiTOMMkEEJ7B/MNztCjnwCKEF416hRDLquSeK6zJbRhPzMsWLr7Cb+BzjvwV5pfo05ilfzV08yB95IBdRteUg==",
"requires": {
"@types/cordova": "latest"
}
},
"@ionic/angular": {
"version": "5.5.5",
"resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-5.5.5.tgz",
@ -22330,6 +22280,11 @@
"shallow-clone": "^3.0.0"
}
},
"co.mylonas.cordova.applicationstate": {
"version": "git+ssh://git@github.com/leomylonas/cordova-plugin-applicationstate.git#1ead7d5045eed494bd1bb8dabde461208634780b",
"dev": true,
"from": "co.mylonas.cordova.applicationstate@github:leomylonas/cordova-plugin-applicationstate"
},
"coa": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz",
@ -23070,18 +23025,6 @@
"integrity": "sha512-63n77K1pt8dnbWnNR8QWETi9Glezi1bvNHvHWmGNIOv0xCb0phZnm+Ku49BQ+omwe8Z5voMvrA4I03SYPpv38w==",
"dev": true
},
"cordova-plugin-network-information": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/cordova-plugin-network-information/-/cordova-plugin-network-information-2.0.2.tgz",
"integrity": "sha512-NwO3qDBNL/vJxUxBTPNOA1HvkDf9eTeGH8JSZiwy1jq2W2mJKQEDBwqWkaEQS19Yd/MQTiw0cykxg5D7u4J6cQ==",
"dev": true
},
"cordova-plugin-powermanagement-orig": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cordova-plugin-powermanagement-orig/-/cordova-plugin-powermanagement-orig-1.1.2.tgz",
"integrity": "sha1-hVuljKvnndWxECXkWG/ywe0YnOA=",
"dev": true
},
"cordova-plugin-splashscreen": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/cordova-plugin-splashscreen/-/cordova-plugin-splashscreen-5.0.2.tgz",

View File

@ -40,6 +40,7 @@
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"co.mylonas.cordova.applicationstate": "github:leomylonas/cordova-plugin-applicationstate",
"codelyzer": "^6.0.0",
"cordova-android": "^9.0.0",
"cordova-plugin-app-exit": "0.0.2",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -5,6 +5,7 @@ import {Token} from './token';
import {Setting} from './setting';
import {ChatMessage} from './chat.message';
import {ChatTokenResponse} from './chat.token';
import {VersionInfo} from './version.info';
@Injectable({
providedIn: 'root'
@ -15,15 +16,6 @@ export class ApiService {
constructor(private client: HttpClient) {
}
storeData(key: string, value: string): void
{
sessionStorage.setItem(key, value);
}
getFromStorage(key: string): string {
return sessionStorage.getItem(key);
}
getAuthToken(username: string, password: string): Observable<Token> {
return this.client.post<Token>(Setting.URL + '/token', {username, password});
}
@ -56,4 +48,9 @@ export class ApiService {
{headers: {Authorization: 'Bearer ' + token}}
);
}
getCurrentVersion(): Observable<VersionInfo>
{
return this.client.get<VersionInfo>(Setting.URL + '/metasocket/info');
}
}

View File

@ -1,5 +1,6 @@
import { Component } from '@angular/core';
import { ApiService } from './api.service';
import AppStorage from './app.storage';
@Component({
selector: 'app-root',
@ -13,7 +14,7 @@ export class AppComponent {
public constructor(private apiService: ApiService)
{
AppComponent.token = this.apiService.getFromStorage('token');
AppComponent.token = AppStorage.getToken();
}
public getToken(): string

4
src/app/app.config.ts Normal file
View File

@ -0,0 +1,4 @@
export default class AppConfig
{
public static readonly VERSION: string = '1.5.0';
}

View File

@ -1,14 +1,11 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {RouteReuseStrategy} from '@angular/router';
import {IonicModule, IonicRouteStrategy} from '@ionic/angular';
import {FormsModule} from '@angular/forms';
import {HttpClientModule} from '@angular/common/http';
import {AppComponent} from './app.component';
import {AppRoutingModule} from './app-routing.module';
import {ChatComponent} from './chat/chat.component';
import {LoginComponent} from './login/login.component';
import {TopbarComponent} from './topbar/topbar.component';

77
src/app/app.storage.ts Normal file
View File

@ -0,0 +1,77 @@
import {ApiService} from './api.service';
export default class AppStorage
{
private static readonly LAST_VERSION_CHECK: string = 'lastVersionCheck';
private static readonly IS_UPDATE_NOTIFICATION_DESIRED: string = 'isUpdateNotificationDesired';
private static readonly TOKEN: string = 'token';
private static readonly USER_ID: string = 'userId';
private static readonly CHAT_TOKEN: string = 'chatToken';
private static readonly LAST_LOGIN_NAME: string = 'lastLoginName';
public static getChatToken(): string
{
return sessionStorage.getItem(this.CHAT_TOKEN);
}
public static getLastLoginName(): string
{
return localStorage.getItem(this.LAST_LOGIN_NAME);
}
public static getLastVersionCheck(): number
{
const lastCheck = localStorage.getItem(this.LAST_VERSION_CHECK);
return lastCheck === null ? null : Number(lastCheck);
}
public static getToken(): string
{
return sessionStorage.getItem(this.TOKEN);
}
public static getUserId(): number
{
const userId = sessionStorage.getItem(this.USER_ID);
return userId === null ? null : Number(userId);
}
public static isUpdateNotificationDesired(): boolean
{
const isDesired = localStorage.getItem(this.IS_UPDATE_NOTIFICATION_DESIRED);
return isDesired === null ? true : isDesired === 'true';
}
public static setChatToken(chatToken: string): void
{
sessionStorage.setItem(this.CHAT_TOKEN, chatToken);
}
public static setIsUpdateNotificationDesired(isDesired: boolean): void
{
localStorage.setItem(this.IS_UPDATE_NOTIFICATION_DESIRED, isDesired.toString());
}
public static setLastLoginName(loginName: string): void
{
localStorage.setItem(this.LAST_LOGIN_NAME, loginName);
}
public static setLastVersionCheck(versionCheck: number): void
{
localStorage.setItem(this.LAST_VERSION_CHECK, versionCheck.toString());
}
public static setToken(token: string): void
{
sessionStorage.setItem(this.TOKEN, token);
}
public static setUserId(userId: number): void
{
sessionStorage.setItem(this.USER_ID, userId.toString());
}
}

View File

@ -9,6 +9,12 @@ import {LocalNotifications} from '@ionic-native/local-notifications/ngx';
import {BackgroundMode} from '@ionic-native/background-mode/ngx';
import {AppComponent} from '../app.component';
import {Platform} from '@ionic/angular';
import {VersionInfo} from '../version.info';
import FullscreenError from '../fullscreen.error';
import FullscreenNotification from '../fullscreen.notification';
import AppConfig from '../app.config';
import FullscreenDialog from '../fullscreen.dialog';
import AppStorage from '../app.storage';
const {App} = Plugins;
@ -44,11 +50,12 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W
private backgroundMode: BackgroundMode,
private platform: Platform
) {
this.userToken = this.apiService.getFromStorage('token');
this.userId = Number(this.apiService.getFromStorage('userId'));
this.userToken = AppStorage.getToken();
this.userId = AppStorage.getUserId();
this.url = Setting.URL;
this.websocketService.setListener(this);
this.websocketService.initializeSocket(this.apiService.getFromStorage('chatToken'));
this.websocketService.initializeSocket(AppStorage.getChatToken());
this.backgroundMode.enable();
this.backgroundMode.disableBatteryOptimizations();
this.backgroundMode.disableWebViewOptimizations();
}
@ -94,7 +101,7 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W
}
).catch(
(error) => {
window.alert('Fehler ' + error.status + ': Verbindung zur Web-API gescheitert!');
const notification = new FullscreenError('Verbindung zur Web-API gescheitert!\n\nStatus-Code: ' + error.status);
}
);
@ -104,9 +111,9 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W
this.platform.backButton.subscribe(
() => {
if (window.confirm('Möchtest du wirklich ausloggen?')) {
this.onLogout();
}
const question = new FullscreenDialog('App beenden', 'Möchtest du wirklich ausloggen?');
question.addButton('Ja', () => {this.onLogout(); });
question.addButton('Nein');
}
);
@ -115,6 +122,13 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W
this.websocketService.sendKeepAliveMessage();
}, 1000 * 60 // every minute
);
setTimeout(
() => {
this.checkVersion();
},
5000
);
}
onScroll(): void {
@ -161,7 +175,7 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W
}
onLogout() {
this.apiService.storeData('token', null);
AppStorage.setToken(null);
this.websocketService.destroy();
AppComponent.token = null;
@ -173,7 +187,7 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W
this.messages.push(message);
this.messageOffset++;
if (this.hasFocus || message.userId === this.userId) {
if (!this.backgroundMode.isActive() || message.userId === this.userId) {
return;
}
@ -263,6 +277,46 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W
);
}
private checkVersion(): void
{
const lastChecked = AppStorage.getLastVersionCheck();
const today = new Date();
if (lastChecked !== null) {
const dateLastChecked = new Date(lastChecked);
if (dateLastChecked.toDateString() === today.toDateString()) {
return;
}
}
AppStorage.setLastVersionCheck(today.getTime());
this.apiService.getCurrentVersion().toPromise()
.then(
(versionInfo: VersionInfo) => {
const isUpdateNotificationDesired = AppStorage.isUpdateNotificationDesired();
if (isUpdateNotificationDesired && AppConfig.VERSION < versionInfo.currentVersionAndroid) {
const notification = new FullscreenNotification('Neue Version', 'Eine neue Version von METAsocket ist verfügbar.');
const label = document.createElement('label');
const checkbox = document.createElement('input');
const text = document.createElement('span');
text.innerText = ' Nerv mich nie wieder damit';
checkbox.type = 'checkbox';
label.appendChild(checkbox);
label.appendChild(text);
label.onclick = () => {
AppStorage.setIsUpdateNotificationDesired(!checkbox.checked);
};
notification.addExtendedInputElement(label);
notification.addUrlButton('Runterladen', 'https://sabolli.de/wow/metasocket/download/android');
}
}
);
}
private hasChatMessage(message: ChatMessage): boolean
{
for (const messageStored of this.messages) {

View File

@ -0,0 +1,73 @@
export default class FullscreenDialog
{
protected rootElement: HTMLDivElement;
protected window: HTMLDivElement;
protected title: HTMLHeadElement;
protected message: HTMLParagraphElement;
protected extendedInputArea: HTMLDivElement;
protected buttonArea: HTMLDivElement;
public constructor(title: string, message: string) {
this.rootElement = document.createElement('div');
this.rootElement.classList.add('notification');
this.window = document.createElement('div');
this.window.classList.add('notification-window');
this.rootElement.appendChild(this.window);
this.title = document.createElement('h1');
this.title.classList.add('notification-headline');
this.title.innerText = title;
this.window.appendChild(this.title);
this.message = document.createElement('p');
this.message.innerText = message;
this.window.appendChild(this.message);
this.extendedInputArea = document.createElement('div');
this.extendedInputArea.classList.add('notification-extended-input-area');
this.window.appendChild(this.extendedInputArea);
this.buttonArea = document.createElement('div');
this.buttonArea.classList.add('notification-button-area');
this.window.appendChild(this.buttonArea);
document.body.appendChild(this.rootElement);
}
public addButton(title: string, callback: () => void = () => {}): void
{
const button = document.createElement('button');
button.classList.add('notification-button');
button.innerText = title;
button.onclick = () => {
callback();
this.close();
};
this.buttonArea.appendChild(button);
}
public addUrlButton(title: string, url: string): void
{
const button = document.createElement('a');
button.classList.add('notification-button');
button.innerText = title;
button.href = url;
button.onclick = () => {this.close(); };
this.buttonArea.appendChild(button);
}
public addExtendedInputElement(element: HTMLElement): void
{
this.extendedInputArea.appendChild(element);
}
public close(): void
{
this.rootElement.remove();
}
}

View File

@ -0,0 +1,9 @@
import FullscreenNotification from './fullscreen.notification';
export default class FullscreenError extends FullscreenNotification
{
public constructor(message: string) {
super('Fehler', message);
this.window.classList.add('notification-window-error');
}
}

View File

@ -0,0 +1,10 @@
import FullscreenDialog from './fullscreen.dialog';
export default class FullscreenNotification extends FullscreenDialog
{
public constructor(title: string, message: string) {
super(title, message);
this.addButton('OK');
}
}

View File

@ -6,15 +6,17 @@
<form>
<label>
Username
<input name="username" [(ngModel)] = "username" required>
<input #loginName name="username" [(ngModel)] = "username" required autofocus>
</label>
<label>
Password
<input type="password" name="password" [(ngModel)] = "password" required>
<input #loginPassword type="password" name="password" [(ngModel)] = "password" required>
</label>
<input type="submit" (click)="login($event)" value="Anmelden">
</form>
<p class="app-version">{{version}}</p>
</div>
</div>

View File

@ -1,7 +1,9 @@
import { Component, OnInit } from '@angular/core';
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import { ApiService } from '../api.service';
import {Platform} from '@ionic/angular';
import {AppMinimize} from '@ionic-native/app-minimize/ngx';
import AppStorage from '../app.storage';
import AppConfig from '../app.config';
@Component({
selector: 'app-login',
@ -12,8 +14,14 @@ export class LoginComponent implements OnInit {
username = '';
password = '';
error: string = null;
version: string;
constructor(private apiService: ApiService, private platform: Platform, private appMinimize: AppMinimize) { }
@ViewChild('loginName') usernameElement: ElementRef;
@ViewChild('loginPassword') passwordElement: ElementRef;
constructor(private apiService: ApiService, private platform: Platform, private appMinimize: AppMinimize) {
this.version = AppConfig.VERSION;
}
ngOnInit(): void {
this.platform.backButton.subscribe(
@ -21,6 +29,12 @@ export class LoginComponent implements OnInit {
this.appMinimize.minimize();
}
);
const lastUsername = AppStorage.getLastLoginName() ?? '';
if (lastUsername.length > 0) {
this.username = lastUsername;
}
}
login(event): void
@ -31,19 +45,20 @@ export class LoginComponent implements OnInit {
this.apiService.getAuthToken(this.username, this.password).toPromise()
.then(
(response) => {
this.apiService.storeData('token', response.token);
AppStorage.setToken(response.token);
this.apiService.getChatToken(response.token).toPromise()
.then(
(chatTokenResponse) => {
this.apiService.storeData('chatToken', chatTokenResponse.token);
this.apiService.storeData('userId', chatTokenResponse.userId.toString());
AppStorage.setChatToken(chatTokenResponse.token);
AppStorage.setUserId(chatTokenResponse.userId);
AppStorage.setLastLoginName(this.username);
window.location.reload();
}
).catch(
(error) => {
this.apiService.storeData('token', null);
AppStorage.setToken(null);
this.error = error.name + ': ' + error.message;
event.target.disabled = false;
@ -53,7 +68,7 @@ export class LoginComponent implements OnInit {
}
).catch (
(error) => {
this.apiService.storeData('token', null);
AppStorage.setToken(null);
switch (error.status) {
case 401:
@ -76,7 +91,7 @@ export class LoginComponent implements OnInit {
this.error = 'Fehler: Etwas unfassbar grauenhaftes ist passiert!';
}
console.log(error);
this.password = '';
event.target.disabled = false;
event.target.style.visibility = 'visible';

View File

@ -1,7 +1,7 @@
import {Component, Input, OnInit} from '@angular/core';
import {ApiService} from '../api.service';
import {AppComponent} from '../app.component';
import {ForegroundService} from '@ionic-native/foreground-service/ngx';
import AppStorage from '../app.storage';
@Component({
selector: 'app-topbar',
@ -11,21 +11,20 @@ import {ForegroundService} from '@ionic-native/foreground-service/ngx';
export class TopbarComponent implements OnInit {
@Input() token: string;
constructor(private apiService: ApiService, private foregroundService: ForegroundService) {
constructor(private apiService: ApiService) {
}
ngOnInit(): void {
}
logout(): void {
this.apiService.deleteAuthToken(this.apiService.getFromStorage('token')).toPromise()
this.apiService.deleteAuthToken(AppStorage.getToken()).toPromise()
.then()
.catch(
(error) => {console.log(error); }
);
this.apiService.storeData('token', null);
this.foregroundService.stop();
AppStorage.setToken(null);
AppComponent.token = null;
}
}

5
src/app/version.info.ts Normal file
View File

@ -0,0 +1,5 @@
export interface VersionInfo
{
success: boolean;
currentVersionAndroid: string;
}

View File

@ -3,6 +3,15 @@
to { top: 0}
}
@keyframes fullscreen-notification-entrance {
from { background-color: rgba(0, 0, 0, 0) }
to { background-color: rgba(0, 0, 0, 0.5) }
}
@keyframes fullscreen-notification-window-entrance {
from { transform: scale(0)}
}
* {
box-sizing: border-box;
font-family: sans-serif;
@ -158,6 +167,14 @@ html, body {
#login-form input[type=submit] {
margin-top: 50px;
background-color: white;
color: #2a2a2a;
font-weight: bold;
}
.app-version {
color: white;
margin-top: 50px;
}
.error-message {
@ -247,6 +264,64 @@ html, body {
background-color: #550000;
}
.notification {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
color: white;
animation-name: fullscreen-notification-entrance;
animation-duration: 0.5s;
}
.notification-window {
background-color: #251a25;
padding: 20px;
margin: 20px;
box-shadow: 0 0 20px black;
animation-name: fullscreen-notification-window-entrance;
animation-duration: 0.5s;
}
.notification-window-error {
background-color: #bb0000;
}
.notification-button-area {
display: flex;
justify-content: space-around;
margin-top: 30px;
}
.notification-button {
background-color: white;
color: #251a25;
font-weight: bold;
padding: 5px 20px;
border: none;
border-radius: 0;
cursor: pointer;
}
input[type="checkbox"] {
border: solid 1px white !important;
border-radius: 0 !important;
}
a.notification-button {
text-decoration: none;
}
.notification-headline {
margin-top: 5px;
}
@media only screen and (max-height: 500px) {
#login {
position: static;