Browse Source

Improve mobile app support

pull/4687/head
Igor Kulikov 5 years ago
parent
commit
bbf78721a7
  1. 1
      ui-ngx/src/app/core/api/widget-api.models.ts
  2. 34
      ui-ngx/src/app/core/auth/auth.service.ts
  3. 6
      ui-ngx/src/app/core/guards/auth.guard.ts
  4. 103
      ui-ngx/src/app/core/services/mobile.service.ts
  5. 4
      ui-ngx/src/app/core/utils.ts
  6. 2
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  7. 14
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  8. 4
      ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts
  9. 4
      ui-ngx/src/app/modules/home/components/dashboard-page/states/entity-state-controller.component.ts
  10. 2
      ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts
  11. 14
      ui-ngx/src/app/shared/models/window-message.model.ts
  12. 47
      ui-ngx/src/theme.scss

1
ui-ngx/src/app/core/api/widget-api.models.ts

@ -152,6 +152,7 @@ export interface IStateController {
getStateIndex(): number;
getStateIdAtIndex(index: number): string;
getEntityId(entityParamName: string): EntityId;
getCurrentStateName(): string;
}
export interface SubscriptionInfo {

34
ui-ngx/src/app/core/auth/auth.service.ts

@ -45,6 +45,7 @@ import { ActionNotificationShow } from '@core/notification/notification.actions'
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component';
import { OAuth2ClientInfo } from '@shared/models/oauth2.models';
import { isMobileApp } from '@core/utils';
@Injectable({
providedIn: 'root'
@ -194,11 +195,13 @@ export class AuthService {
}
public gotoDefaultPlace(isAuthenticated: boolean) {
const authState = getCurrentAuthState(this.store);
const url = this.defaultUrl(isAuthenticated, authState);
this.zone.run(() => {
this.router.navigateByUrl(url);
});
if (!isMobileApp()) {
const authState = getCurrentAuthState(this.store);
const url = this.defaultUrl(isAuthenticated, authState);
this.zone.run(() => {
this.router.navigateByUrl(url);
});
}
}
public loadOAuth2Clients(): Observable<Array<OAuth2ClientInfo>> {
@ -516,12 +519,15 @@ export class AuthService {
return this.refreshTokenSubject !== null;
}
public setUserFromJwtToken(jwtToken, refreshToken, notify) {
public setUserFromJwtToken(jwtToken, refreshToken, notify): Observable<boolean> {
const authenticatedSubject = new ReplaySubject<boolean>();
if (!jwtToken) {
AuthService.clearTokenData();
if (notify) {
this.notifyUnauthenticated();
}
authenticatedSubject.next(false);
authenticatedSubject.complete();
} else {
this.updateAndValidateTokens(jwtToken, refreshToken, true);
if (notify) {
@ -530,16 +536,30 @@ export class AuthService {
(authPayload) => {
this.notifyUserLoaded(true);
this.notifyAuthenticated(authPayload);
authenticatedSubject.next(true);
authenticatedSubject.complete();
},
() => {
this.notifyUserLoaded(true);
this.notifyUnauthenticated();
authenticatedSubject.next(false);
authenticatedSubject.complete();
}
);
} else {
this.loadUser(false).subscribe();
this.loadUser(false).subscribe(
() => {
authenticatedSubject.next(true);
authenticatedSubject.complete();
},
() => {
authenticatedSubject.next(false);
authenticatedSubject.complete();
}
);
}
}
return authenticatedSubject;
}
private updateAndValidateTokens(jwtToken, refreshToken, notify: boolean) {

6
ui-ngx/src/app/core/guards/auth.guard.ts

@ -29,6 +29,7 @@ import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core';
import { UtilsService } from '@core/services/utils.service';
import { isObject } from '@core/utils';
import { MobileService } from '@core/services/mobile.service';
@Injectable({
providedIn: 'root'
@ -41,6 +42,7 @@ export class AuthGuard implements CanActivate, CanActivateChild {
private dialogService: DialogService,
private utils: UtilsService,
private translate: TranslateService,
private mobileService: MobileService,
private zone: NgZone) {}
getAuthState(): Observable<AuthState> {
@ -108,6 +110,10 @@ export class AuthGuard implements CanActivate, CanActivateChild {
return of(false);
}
}
if (this.mobileService.isMobileApp() && !path.startsWith('dashboard.')) {
this.mobileService.handleMobileNavigation(path, params);
return of(false);
}
const defaultUrl = this.authService.defaultUrl(true, authState, path, params);
if (defaultUrl) {
// this.authService.gotoDefaultPlace(true);

103
ui-ngx/src/app/core/services/mobile.service.ts

@ -20,9 +20,14 @@ import { isDefined } from '@core/utils';
import { MobileActionResult, WidgetMobileActionResult, WidgetMobileActionType } from '@shared/models/widget.models';
import { from, of } from 'rxjs';
import { Observable } from 'rxjs/internal/Observable';
import { catchError } from 'rxjs/operators';
import { catchError, tap } from 'rxjs/operators';
import { OpenDashboardMessage, ReloadUserMessage, WindowMessage } from '@shared/models/window-message.model';
import { Params, Router } from '@angular/router';
import { AuthService } from '@core/auth/auth.service';
const dashboardStateNameHandler = 'tbMobileDashboardStateNameHandler';
const dashboardLoadedHandler = 'tbMobileDashboardLoadedHandler';
const navigationHandler = 'tbMobileNavigationHandler';
const mobileHandler = 'tbMobileHandler';
// @dynamic
@ -34,10 +39,20 @@ export class MobileService {
private readonly mobileApp;
private readonly mobileChannel;
constructor(@Inject(WINDOW) private window: Window) {
private readonly onWindowMessageListener = this.onWindowMessage.bind(this);
private reloadUserObservable: Observable<boolean>;
private lastDashboardId: string;
constructor(@Inject(WINDOW) private window: Window,
private router: Router,
private authService: AuthService) {
const w = (this.window as any);
this.mobileChannel = w.flutter_inappwebview;
this.mobileApp = isDefined(this.mobileChannel);
if (this.mobileApp) {
window.addEventListener('message', this.onWindowMessageListener);
}
}
public isMobileApp(): boolean {
@ -50,6 +65,12 @@ export class MobileService {
}
}
public onDashboardLoaded() {
if (this.mobileApp) {
this.mobileChannel.callHandler(dashboardLoadedHandler);
}
}
public handleWidgetMobileAction<T extends MobileActionResult>(type: WidgetMobileActionType, ...args: any[]):
Observable<WidgetMobileActionResult<T>> {
if (this.mobileApp) {
@ -67,4 +88,82 @@ export class MobileService {
}
}
public handleMobileNavigation(path?: string, params?: Params) {
if (this.mobileApp) {
this.mobileChannel.callHandler(navigationHandler, path, params);
}
}
private onWindowMessage(event: MessageEvent) {
if (event.data) {
let message: WindowMessage;
try {
message = JSON.parse(event.data);
} catch (e) {}
if (message && message.type) {
switch (message.type) {
case 'openDashboardMessage':
const openDashboardMessage: OpenDashboardMessage = message.data;
this.openDashboard(openDashboardMessage);
break;
case 'reloadUserMessage':
const reloadUserMessage: ReloadUserMessage = message.data;
this.reloadUser(reloadUserMessage);
break;
}
}
}
}
private openDashboard(openDashboardMessage: OpenDashboardMessage) {
if (openDashboardMessage && openDashboardMessage.dashboardId) {
if (this.reloadUserObservable) {
this.reloadUserObservable.subscribe(
(authenticated) => {
if (authenticated) {
this.doDashboardNavigation(openDashboardMessage);
}
}
);
} else {
this.doDashboardNavigation(openDashboardMessage);
}
}
}
private doDashboardNavigation(openDashboardMessage: OpenDashboardMessage) {
let url = `/dashboard/${openDashboardMessage.dashboardId}`;
const params = [];
if (openDashboardMessage.state) {
params.push(`state=${openDashboardMessage.state}`);
}
if (openDashboardMessage.embedded) {
params.push(`embedded=true`);
}
if (openDashboardMessage.hideToolbar) {
params.push(`hideToolbar=true`);
}
if (this.lastDashboardId === openDashboardMessage.dashboardId) {
params.push(`reload=${new Date().getTime()}`);
}
if (params.length) {
url += `?${params.join('&')}`;
}
this.lastDashboardId = openDashboardMessage.dashboardId;
this.router.navigateByUrl(url, {replaceUrl: true});
}
private reloadUser(reloadUserMessage: ReloadUserMessage) {
if (reloadUserMessage && reloadUserMessage.accessToken && reloadUserMessage.refreshToken) {
this.reloadUserObservable = this.authService.setUserFromJwtToken(reloadUserMessage.accessToken,
reloadUserMessage.refreshToken, true).pipe(
tap(
() => {
this.reloadUserObservable = null;
}
)
);
}
}
}

4
ui-ngx/src/app/core/utils.ts

@ -441,3 +441,7 @@ export function generateSecret(length?: number): string {
export function validateEntityId(entityId: EntityId | null): boolean {
return isDefinedAndNotNull(entityId?.id) && entityId.id !== NULL_UUID && isDefinedAndNotNull(entityId?.entityType);
}
export function isMobileApp(): boolean {
return isDefined((window as any).flutter_inappwebview);
}

2
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -87,7 +87,7 @@
(click)="updateDashboardImage($event)">
<mat-icon>wallpaper</mat-icon>
</button>
<button [fxShow]="currentDashboardId && (isEdit || displayExport())" mat-icon-button
<button [fxShow]="currentDashboardId && !isMobileApp && (isEdit || displayExport())" mat-icon-button
matTooltip="{{'dashboard.export' | translate}}"
matTooltipPosition="below"
(click)="exportDashboard($event)">

14
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -323,6 +323,17 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.runChangeDetection();
}
));
this.rxSubscriptions.push(this.route.queryParamMap.subscribe(
(paramMap) => {
if (paramMap.has('reload')) {
this.dashboardCtx.aliasController.updateAliases();
setTimeout(() => {
this.mobileService.handleDashboardStateName(this.dashboardCtx.stateController.getCurrentStateName());
this.mobileService.onDashboardLoaded();
});
}
}
));
this.rxSubscriptions.push(this.breakpointObserver
.observe(MediaBreakpoints['gt-sm'])
.subscribe((state: BreakpointState) => {
@ -770,6 +781,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.isRightLayoutOpened = openRightLayout ? true : false;
this.updateLayouts(layoutsData);
}
setTimeout(() => {
this.mobileService.onDashboardLoaded();
});
}
private updateLayouts(layoutsData?: DashboardLayoutsInfo) {

4
ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts

@ -185,6 +185,10 @@ export class DefaultStateControllerComponent extends StateControllerComponent im
return this.utils.customTranslation(state.name, id);
}
public getCurrentStateName(): string {
return this.getStateName(this.stateObject[0].id, this.statesValue[this.stateObject[0].id]);
}
public displayStateSelection(): boolean {
return this.states && Object.keys(this.states).length > 1;
}

4
ui-ngx/src/app/modules/home/components/dashboard-page/states/entity-state-controller.component.ts

@ -235,6 +235,10 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
return result;
}
public getCurrentStateName(): string {
return this.getStateName(this.stateObject.length - 1);
}
public selectedStateIndexChanged() {
this.navigatePrevState(this.selectedStateIndex);
}

2
ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts

@ -198,4 +198,6 @@ export abstract class StateControllerComponent implements IStateControllerCompon
public abstract updateState(id?: string, params?: StateParams, openRightLayout?: boolean): void;
public abstract getCurrentStateName(): string;
}

14
ui-ngx/src/app/shared/models/window-message.model.ts

@ -14,9 +14,21 @@
/// limitations under the License.
///
export type WindowMessageType = 'widgetException' | 'widgetEditModeInited' | 'widgetEditUpdated';
export type WindowMessageType = 'widgetException' | 'widgetEditModeInited' | 'widgetEditUpdated' | 'openDashboardMessage' | 'reloadUserMessage';
export interface WindowMessage {
type: WindowMessageType;
data?: any;
}
export interface OpenDashboardMessage {
dashboardId: string;
state?: string;
hideToolbar?: boolean;
embedded?: boolean;
}
export interface ReloadUserMessage {
accessToken: string;
refreshToken: string;
}

47
ui-ngx/src/theme.scss

@ -174,6 +174,48 @@ $tb-dark-theme: get-tb-dark-theme(
}
}
@mixin _mat-toolbar-inverse-color($palette) {
background: mat-color($palette, default-contrast);
color: $dark-primary-text;
}
@mixin mat-fab-toolbar-inverse-theme($theme) {
$primary: map-get($theme, primary);
$accent: map-get($theme, accent);
$warn: map-get($theme, warn);
$background: map-get($theme, foreground);
$foreground: map-get($theme, background);
mat-fab-toolbar {
.mat-fab-toolbar-background {
background: mat-color($background, app-bar);
color: mat-color($foreground, text);
}
&.mat-primary {
.mat-fab-toolbar-background {
@include _mat-toolbar-inverse-color($primary);
}
}
mat-toolbar {
&.mat-primary {
@include _mat-toolbar-inverse-color($primary);
button.mat-icon-button {
mat-icon {
color: mat-color($primary);
}
}
}
}
.mat-fab {
&.mat-primary {
background: mat-color($primary, default-contrast);
color: mat-color($primary);
}
}
}
}
@mixin tb-components-theme($theme) {
$primary: map-get($theme, primary);
@ -184,6 +226,10 @@ $tb-dark-theme: get-tb-dark-theme(
}
@include mat-fab-toolbar-theme($tb-theme);
div.tb-dashboard-page.mobile-app {
@include mat-fab-toolbar-inverse-theme($tb-theme);
}
}
.tb-default {
@ -195,3 +241,4 @@ $tb-dark-theme: get-tb-dark-theme(
.tb-dark {
@include angular-material-theme($tb-dark-theme);
}

Loading…
Cancel
Save