Skip to content

Commit

Permalink
[MOBILEAPPS-1654] Open in App pop-up implementation in android and ip…
Browse files Browse the repository at this point in the history
…hone (#2889)

* Open in App pop up implementation

* review comments addressed

* unit test cases added for open-in-app component and aca-mobile-app-switcher-service and review comments addressed

* cspell changes

* test case build failing issue resolved

* review comments addressed of using specific type and void in functions that do not return anything

* remaining review comments fixed for type cases

* added check for ipad and ipod

* checkForIOSDevice function removed and checked for ios device in same line

* spacing issues addressed

* missing unit tests added and some review comments addressed

* app.config.json file updated

* removed configuration variables from default Readme file

* used only boolean instead of string conversion in app service file

* session storage name change

* review comments addressed
  • Loading branch information
jatin2008 committed Feb 10, 2023
1 parent 6b72ef5 commit c10a137
Show file tree
Hide file tree
Showing 14 changed files with 360 additions and 6 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ env:
- APP_CONFIG_OAUTH2_CLIENTID=alfresco
- APP_CONFIG_PLUGIN_AOS=true
- APP_CONFIG_PLUGIN_CONTENT_SERVICE=true
- APP_CONFIG_ENABLE_MOBILE_APP_SWITCH=true
- APP_CONFIG_SESSION_TIME_FOR_OPEN_APP_DIALOG_DISPLAY_IN_HOURS="12"
- APP_CONFIG_OAUTH2_IMPLICIT_FLOW=true
- APP_CONFIG_OAUTH2_SILENT_LOGIN=true
- APP_CONFIG_OAUTH2_REDIRECT_LOGOUT=/
Expand Down
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ ENV APP_CONFIG_OAUTH2_REDIRECT_LOGOUT="/"
ENV APP_CONFIG_PLUGIN_AOS=true
ENV APP_CONFIG_PLUGIN_FOLDER_RULES=true
ENV APP_CONFIG_PLUGIN_CONTENT_SERVICE=true
ENV APP_CONFIG_ENABLE_MOBILE_APP_SWITCH=true
ENV APP_CONFIG_SESSION_TIME_FOR_OPEN_APP_DIALOG_DISPLAY_IN_HOURS="12"

COPY docker/default.conf.template /etc/nginx/templates/
COPY docker/docker-entrypoint.d/* /docker-entrypoint.d/
Expand Down
7 changes: 7 additions & 0 deletions app/src/app.config.json.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"providers": "${APP_CONFIG_PROVIDER}",
"authType": "${APP_CONFIG_AUTH_TYPE}",
"loginRoute": "login",
"mobileAppSwitch": {
"enabled" : ${APP_CONFIG_ENABLE_MOBILE_APP_SWITCH},
"iphoneUrl": "iosamw://",
"androidUrlPart1": "intent:///",
"androidUrlPart2": "#Intent;scheme=androidamw;package=com.alfresco.content.app;end",
"sessionTimeForOpenAppDialogDisplay": "${APP_CONFIG_SESSION_TIME_FOR_OPEN_APP_DIALOG_DISPLAY_IN_HOURS}"
},
"plugins": {
"aosPlugin": ${APP_CONFIG_PLUGIN_AOS},
"contentService": ${APP_CONFIG_PLUGIN_CONTENT_SERVICE},
Expand Down
2 changes: 2 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
"ngstack",
"sidenav",
"injectable",
"iosamw",
"truthy",
"cryptodoc",
"mysites",
"afts",
"androidamw",
"classlist",
"folderlink",
"filelink",
Expand Down
3 changes: 3 additions & 0 deletions projects/aca-content/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,9 @@
"MESSAGE": "Leaving will remove your access.",
"YES_LABEL": "OK",
"NO_LABEL": "Cancel"
},
"MOBILE_APP": {
"MOBILE_APP_BUTTON_LABEL": "Open in App"
}
},
"DOCUMENT_LIST": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div class="container">
<button mat-button (click)="openInApp()" data-automation-id="open-in-app-button">
<span>{{ 'APP.DIALOGS.MOBILE_APP.MOBILE_APP_BUTTON_LABEL' }}</span>
</button>
<button mat-button mat-dialog-close>
<mat-icon>close</mat-icon>
</button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.container{
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}

.mat-dialog-container{
padding: 12px;
border-radius: 36px;
background-color: var(--theme-blue-button-color);
color: var(--theme-about-panel-background-color);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { By } from '@angular/platform-browser';
import { OpenInAppComponent } from './open-in-app.component';

describe('OpenInAppComponent', () => {
let fixture: ComponentFixture<OpenInAppComponent>;
let component: OpenInAppComponent;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [OpenInAppComponent],
providers: [{ provide: MAT_DIALOG_DATA, useValue: { redirectUrl: 'mockRedirectUrl' } }]
}).compileComponents();

fixture = TestBed.createComponent(OpenInAppComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should redirect to app when click on `Open in App` button` ', async () => {
let currentLocation: string | string[];
const windowStub: Window & typeof globalThis = {
location: {
set href(value: string | string[]) {
currentLocation = value;
}
}
} as Window & typeof globalThis;
component.window = windowStub;
const saveButton = fixture.debugElement.query(By.css('[data-automation-id="open-in-app-button"]')).nativeElement;
saveButton.dispatchEvent(new Event('click'));
fixture.detectChanges();
await fixture.whenStable();

expect(currentLocation).toBe('mockRedirectUrl');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2020 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/

import { Component, Inject, ViewEncapsulation } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';

export interface OpenInAppDialogOptions {
redirectUrl: string;
}
@Component({
selector: 'aca-open-in-app',
templateUrl: './open-in-app.component.html',
styleUrls: ['./open-in-app.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class OpenInAppComponent {
private redirectUrl: string;
public window: Window & typeof globalThis = window;

constructor(
@Inject(MAT_DIALOG_DATA)
public data: OpenInAppDialogOptions
) {
if (data) {
this.redirectUrl = data.redirectUrl;
}
}

openInApp(): void {
this.window.location.href = this.redirectUrl;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2020 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/

import { TestBed } from '@angular/core/testing';
import { AppConfigService } from '@alfresco/adf-core';
import { LibTestingModule, initialState } from '../testing/lib-testing-module';
import { provideMockStore } from '@ngrx/store/testing';
import { AcaMobileAppSwitcherService } from './aca-mobile-app-switcher.service';
import { MatDialog } from '@angular/material/dialog';

describe('AcaMobileAppSwitcherService', () => {
let appConfig: AppConfigService;
let service: AcaMobileAppSwitcherService;

const mockDialogRef = {
close: jasmine.createSpy('close'),
open: jasmine.createSpy('open')
};

beforeEach(() => {
TestBed.configureTestingModule({
imports: [LibTestingModule],
providers: [provideMockStore({ initialState }), { provide: MatDialog, useValue: mockDialogRef }]
});
appConfig = TestBed.inject(AppConfigService);
appConfig.config.mobileAppSwitch = {
enabled: true,
iphoneUrl: 'iosamw://',
androidUrlPart1: 'intent:///',
androidUrlPart2: '#Intent;scheme=androidamw;package=com.alfresco.content.app;end',
sessionTimeForOpenAppDialogDisplay: 12
};
service = TestBed.inject(AcaMobileAppSwitcherService);
sessionStorage.clear();
});

it('should set the redirectUrl to `iphoneUrl`', () => {
spyOnProperty(window.navigator, 'userAgent').and.returnValue('iphone');
const url: string = window.location.href;
const iphoneUrl: string = appConfig.config.mobileAppSwitch.iphoneUrl + url;
service.showAppNotification();
expect(service.redirectUrl).toEqual(iphoneUrl);
});

it('should set the redirectUrl to `androidUrl`', () => {
spyOnProperty(window.navigator, 'userAgent').and.returnValue('android');
const url: string = window.location.href;
const androidUrl: string = appConfig.config.mobileAppSwitch.androidUrlPart1 + url + appConfig.config.mobileAppSwitch.androidUrlPart2;
service.showAppNotification();
expect(service.redirectUrl).toEqual(androidUrl);
});

it('should check if `showAppNotification` function is called', () => {
const showAppNotificationSpy: jasmine.Spy<() => void> = spyOn(service, 'showAppNotification');
service.checkForMobileApp();
expect(showAppNotificationSpy).toHaveBeenCalled();
});

it('should not display `openInApp` dialog box when timeDifference is less than the session time', () => {
service.checkForMobileApp();
const showAppNotificationSpy: jasmine.Spy<() => void> = spyOn(service, 'showAppNotification');
service.checkForMobileApp();
expect(showAppNotificationSpy).not.toHaveBeenCalled();
});

it('should check if `openInApp` dialog box is getting opened with `iphone` url', () => {
const openInAppSpy: jasmine.Spy<(redirectUrl: string) => void> = spyOn(service, 'openInApp');
const url: string = window.location.href;
service.redirectUrl = appConfig.config.mobileAppSwitch.iphoneUrl + url;
service.showAppNotification();
expect(openInAppSpy).toHaveBeenCalled();
expect(mockDialogRef.open).toHaveBeenCalled();
});

it('should check if `openInApp` dialog box is getting opened with `android` url', () => {
const openInAppSpy: jasmine.Spy<(redirectUrl: string) => void> = spyOn(service, 'openInApp');
const url: string = window.location.href;
service.redirectUrl = appConfig.config.mobileAppSwitch.androidUrlPart1 + url + appConfig.config.mobileAppSwitch.androidUrlPart2;
service.showAppNotification();
expect(openInAppSpy).toHaveBeenCalled();
expect(mockDialogRef.open).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2020 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/

import { AppConfigService } from '@alfresco/adf-core';
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { OpenInAppComponent } from '../components/open-in-app/open-in-app.component';

export interface MobileAppSwitchConfigurationOptions {
enabled: string;
iphoneUrl: string;
androidUrlPart1: string;
androidUrlPart2: string;
sessionTimeForOpenAppDialogDisplay: string;
}
@Injectable({
providedIn: 'root'
})
export class AcaMobileAppSwitcherService {
private mobileAppSwitchConfig: MobileAppSwitchConfigurationOptions;
public redirectUrl: string;

constructor(private config: AppConfigService, private dialog: MatDialog) {
this.mobileAppSwitchConfig = this.config.get<MobileAppSwitchConfigurationOptions>('mobileAppSwitch');
}

checkForMobileApp(): void {
const currentTime: number = new Date().getTime();
const sessionTime: string = sessionStorage.getItem('mobile_notification_expires_in');

if (sessionTime !== null) {
const sessionConvertedTime: number = parseFloat(sessionTime);
const timeDifference: number = (currentTime - sessionConvertedTime) / (1000 * 60 * 60);
const sessionTimeForOpenAppDialogDisplay: number = parseFloat(this.mobileAppSwitchConfig.sessionTimeForOpenAppDialogDisplay);

if (timeDifference > sessionTimeForOpenAppDialogDisplay) {
this.showAppNotification();
}
} else {
this.showAppNotification();
}
}

showAppNotification(): void {
const ua: string = navigator.userAgent.toLowerCase();
const isAndroid: boolean = ua.indexOf('android') > -1;
const isIOS: boolean = ua.indexOf('iphone') > -1 || ua.indexOf('ipad') > -1 || ua.indexOf('ipod') > -1;
const currentUrl: string = window.location.href;
const time: number = new Date().getTime();

sessionStorage.setItem('mobile_notification_expires_in', time.toString());

if (isIOS === true) {
this.redirectUrl = this.mobileAppSwitchConfig.iphoneUrl + currentUrl;
} else if (isAndroid === true) {
this.redirectUrl = this.mobileAppSwitchConfig.androidUrlPart1 + currentUrl + this.mobileAppSwitchConfig.androidUrlPart2;
}

if (this.redirectUrl !== undefined && this.redirectUrl !== null) {
this.openInApp(this.redirectUrl);
}
}

openInApp(redirectUrl: string): void {
this.dialog.open(OpenInAppComponent, {
data: {
redirectUrl
},
width: '75%',
role: 'dialog',
position: { bottom: '50px' }
});
}

reset(): void {
sessionStorage.removeItem('mobile_notification_expires_in');
}
}

0 comments on commit c10a137

Please sign in to comment.