From 00a9a8c67f477d5654ee8b18cd21bb3c17662667 Mon Sep 17 00:00:00 2001 From: Mal <=> Date: Sun, 21 Mar 2021 23:56:54 +0100 Subject: [PATCH] Version 1.5.0: Update check and dialog popups implemented --- Makefile | 2 + build/android/ic_launcher.svg | 23 +++-- build/android/ic_launcher_round.svg | 67 ------------- config.xml | 2 +- package-lock.json | 93 ++++-------------- package.json | 1 + resources/android/icon/drawable-hdpi-icon.png | Bin 1694 -> 3057 bytes resources/android/icon/drawable-ldpi-icon.png | Bin 1060 -> 1669 bytes resources/android/icon/drawable-mdpi-icon.png | Bin 1319 -> 2191 bytes .../android/icon/drawable-xhdpi-icon.png | Bin 2028 -> 3819 bytes .../android/icon/drawable-xxhdpi-icon.png | Bin 2785 -> 5516 bytes .../android/icon/drawable-xxxhdpi-icon.png | Bin 3594 -> 7425 bytes src/app/api.service.ts | 15 ++- src/app/app.component.ts | 3 +- src/app/app.config.ts | 4 + src/app/app.module.ts | 19 ++-- src/app/app.storage.ts | 77 +++++++++++++++ src/app/chat/chat.component.ts | 72 ++++++++++++-- src/app/fullscreen.dialog.ts | 73 ++++++++++++++ src/app/fullscreen.error.ts | 9 ++ src/app/fullscreen.notification.ts | 10 ++ src/app/login/login.component.html | 6 +- src/app/login/login.component.ts | 31 ++++-- src/app/topbar/topbar.component.ts | 9 +- src/app/version.info.ts | 5 + src/global.scss | 75 ++++++++++++++ 26 files changed, 396 insertions(+), 200 deletions(-) delete mode 100644 build/android/ic_launcher_round.svg create mode 100644 src/app/app.config.ts create mode 100644 src/app/app.storage.ts create mode 100644 src/app/fullscreen.dialog.ts create mode 100644 src/app/fullscreen.error.ts create mode 100644 src/app/fullscreen.notification.ts create mode 100644 src/app/version.info.ts diff --git a/Makefile b/Makefile index c6a53ae..dd27065 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/build/android/ic_launcher.svg b/build/android/ic_launcher.svg index d077488..b462269 100644 --- a/build/android/ic_launcher.svg +++ b/build/android/ic_launcher.svg @@ -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)"> image/svg+xml - + - + diff --git a/build/android/ic_launcher_round.svg b/build/android/ic_launcher_round.svg deleted file mode 100644 index d6ce0e6..0000000 --- a/build/android/ic_launcher_round.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - diff --git a/config.xml b/config.xml index cfbd671..7824d23 100644 --- a/config.xml +++ b/config.xml @@ -1,5 +1,5 @@ - + METAsocket WowApp's awesome instant messenger for the whole Greifentanzgeschwader sabolli diff --git a/package-lock.json b/package-lock.json index c602cfe..b722a1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 0811f9f..dd25295 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/resources/android/icon/drawable-hdpi-icon.png b/resources/android/icon/drawable-hdpi-icon.png index 091328638e895a70a5c8e1db00ab161929af9adf..e97cc48ff75f5133310d7cb631e35450f667ea0a 100644 GIT binary patch delta 3008 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 b*gt#e*@L?#RmL$eFfe$!`njxgN@xNAQGfxY diff --git a/resources/android/icon/drawable-ldpi-icon.png b/resources/android/icon/drawable-ldpi-icon.png index 9aa79825eca379f4193d9397499b8fbc18525871..2b5a9f2f91b708390f0476434f91798bf6628255 100644 GIT binary patch delta 1609 zcmZ3&(aJkPhwA|Y1LFfm)>rdACz`3(vk7^+IEF;D?wygF6MS9rczyr-(v=x=j)dN9 zXX4z}?4jiDCl*w?#xQW^C6myAHC(wD%vBaFITf+A?!@%0BaXM1uy*?D25)v-P})50 zlvBi7ugcfaT)MeZw>mXS*(wW#R%vZf%D=Sj*teVQN5tcg-!L@!?H&Hf_IdrepLdUc zzgPd7l2;p}@@)|_QiY5g^I#eJ6cru01S6%YTl zJ-wECZTgfT>vsZ2bX=FqU7gS0!}@P;hQj>Q--Q?LsptOl#l5xDSo3S^)vUGrF}yM7 zIZXI3v#yEen!)n*^{%+NJgJ^GnLG9;{;WyyF;F^mZrfa&ZGShe3153R%=@zZEP>Na z+41Jq*40J7XT2>~H+$8o{`q^)%X}fhvLEFE|4VGWG#T#wylvgxcJuGFJqkXtFDXptMrD5 zXL(06R8A@WSKKH0tL^6hb44GoxSm>ldgJ;H(#1!VuYPv)e{|*h(ND?o?P^YYtr|2k z{rVo?I3VGrsr=-m0mI$*rI&xXXxFcfS>0n;JO8`n-^=F9KH9%LUVo&2@fF5~pstOL z8=pLN_+eV$W47B&JTvg^DJS+Td23U@zb`lb^YsJMjGPkxceX$Je-!STD|4^>M;L!= z*NP7Bn$~@3EzK)s81A|0$Jc#%ntS+2>gL-kZbUeSZ+JaT&T-a}?Z>`bZTQ+3_Ism> zO#M~{)t5H&+HKy{XWNI)UX@opca?7_h%>LI?tXE6>x{&8Wp#JRm!$JP zuDjst?asZ!cWQ@$-L+jal7S%Q$U?U}2QA733fk{qaixW;#F>(+Yn#(yr(>DlV)|LZQ9 zoCtii;BAKV&TlacHRpf&=-c{U|6u!^`I*t5d)L3sN?)NYpMezW?L*3Y5&u{q2p+XshF5}(7ugx)+LDN%TzE&@B7&{f8LDlxTlAwTHN&u zzQ3}5&Z5m*FRwV8-_~yXu|9TfVdcWk3kjZaC%sxuxx8P>ub;U2cEsz*?x3#2zxj&a z-LPah=6+mquThrOG56z*JA=v}{bHHQAFiY{$F^)*c>V8JOcIW9mV$PPQ|y<;w#!|e zKmTf)wo6LwDvwy{J=!;t?p|F!}9oxFsyW)6D^hG-r=D*za&bQ5zGsVI6OYedB=?u@N z#uQ}-y?y=sm+_0j=a2Vy98J<^K00%c3d}zL=WqY&+#4WMu#SO&fx*+&&t;ucLK6TWQ74)J delta 995 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 delta 2734 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 JWt~$(69Dw!9;pBT diff --git a/resources/android/icon/drawable-xxxhdpi-icon.png b/resources/android/icon/drawable-xxxhdpi-icon.png index 53d8b39dbefde928dd4a789fc4ace2eb601a5bae..46f534be26c3bd2a10a8a72bb9a4563659ca49c4 100644 GIT binary patch delta 7411 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
|It=?+44CJ9GkM@4-LSRiMs{Ir;nt_U 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 I&MBb@0D+Cgs{jB1 diff --git a/src/app/api.service.ts b/src/app/api.service.ts index 4dd5562..6886087 100644 --- a/src/app/api.service.ts +++ b/src/app/api.service.ts @@ -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 { return this.client.post(Setting.URL + '/token', {username, password}); } @@ -56,4 +48,9 @@ export class ApiService { {headers: {Authorization: 'Bearer ' + token}} ); } + + getCurrentVersion(): Observable + { + return this.client.get(Setting.URL + '/metasocket/info'); + } } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 01abf81..e1d163a 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -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 diff --git a/src/app/app.config.ts b/src/app/app.config.ts new file mode 100644 index 0000000..d30c8f7 --- /dev/null +++ b/src/app/app.config.ts @@ -0,0 +1,4 @@ +export default class AppConfig +{ + public static readonly VERSION: string = '1.5.0'; +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index be80cab..72e6fde 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -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'; diff --git a/src/app/app.storage.ts b/src/app/app.storage.ts new file mode 100644 index 0000000..d26d50d --- /dev/null +++ b/src/app/app.storage.ts @@ -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()); + } +} diff --git a/src/app/chat/chat.component.ts b/src/app/chat/chat.component.ts index 4519cf7..24a58a2 100644 --- a/src/app/chat/chat.component.ts +++ b/src/app/chat/chat.component.ts @@ -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) { diff --git a/src/app/fullscreen.dialog.ts b/src/app/fullscreen.dialog.ts new file mode 100644 index 0000000..aab93fb --- /dev/null +++ b/src/app/fullscreen.dialog.ts @@ -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(); + } +} diff --git a/src/app/fullscreen.error.ts b/src/app/fullscreen.error.ts new file mode 100644 index 0000000..4831150 --- /dev/null +++ b/src/app/fullscreen.error.ts @@ -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'); + } +} diff --git a/src/app/fullscreen.notification.ts b/src/app/fullscreen.notification.ts new file mode 100644 index 0000000..52c94c9 --- /dev/null +++ b/src/app/fullscreen.notification.ts @@ -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'); + } +} diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html index d5bd6aa..f7b222f 100644 --- a/src/app/login/login.component.html +++ b/src/app/login/login.component.html @@ -6,15 +6,17 @@
+ +

{{version}}

diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts index 1957a47..c64021f 100644 --- a/src/app/login/login.component.ts +++ b/src/app/login/login.component.ts @@ -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'; diff --git a/src/app/topbar/topbar.component.ts b/src/app/topbar/topbar.component.ts index 35173b5..989ef9a 100644 --- a/src/app/topbar/topbar.component.ts +++ b/src/app/topbar/topbar.component.ts @@ -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; } } diff --git a/src/app/version.info.ts b/src/app/version.info.ts new file mode 100644 index 0000000..b2d1b61 --- /dev/null +++ b/src/app/version.info.ts @@ -0,0 +1,5 @@ +export interface VersionInfo +{ + success: boolean; + currentVersionAndroid: string; +} diff --git a/src/global.scss b/src/global.scss index eaf53b4..c6c65cc 100644 --- a/src/global.scss +++ b/src/global.scss @@ -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;