diff --git a/android/app/src/main/ic_launcher_new_splash-playstore.png b/android/app/src/main/ic_launcher_new_splash-playstore.png new file mode 100644 index 0000000000..6155261e93 Binary files /dev/null and b/android/app/src/main/ic_launcher_new_splash-playstore.png differ diff --git a/android/app/src/main/res/drawable/ic_launcher_new_splash_background.xml b/android/app/src/main/res/drawable/ic_launcher_new_splash_background.xml new file mode 100644 index 0000000000..ca3826a46c --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_new_splash_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_launcher_new_splash_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_new_splash_foreground.xml index b6ff074f5f..187227c55a 100644 --- a/android/app/src/main/res/drawable/ic_launcher_new_splash_foreground.xml +++ b/android/app/src/main/res/drawable/ic_launcher_new_splash_foreground.xml @@ -1,34 +1,36 @@ - - - - - - - - + android:viewportWidth="768" + android:viewportHeight="768"> + + + + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_new_splash.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_new_splash.xml index 3f18e9ffc9..56c2c7af0c 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_new_splash.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_new_splash.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_new_splash_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_new_splash_round.xml index 3f18e9ffc9..56c2c7af0c 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_new_splash_round.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_new_splash_round.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_new_splash.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_new_splash.webp index ad2642ff46..67e8d871a1 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_new_splash.webp and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_new_splash.webp differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_new_splash_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_new_splash_round.webp new file mode 100644 index 0000000000..8c423e15c2 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_new_splash_round.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_new_splash.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_new_splash.webp index 7213949a7e..227159afd1 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_new_splash.webp and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_new_splash.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_new_splash_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_new_splash_round.webp index 2f10fcc36d..e7d3141d81 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_new_splash_round.webp and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_new_splash_round.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_new_splash.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_new_splash.webp new file mode 100644 index 0000000000..023a246326 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_new_splash.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_new_splash_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_new_splash_round.webp new file mode 100644 index 0000000000..d708ec82d8 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_new_splash_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_new_splash.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_new_splash.webp index dff53babfc..d6b1efd409 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_new_splash.webp and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_new_splash.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_new_splash_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_new_splash_round.webp index 94dd502d5d..d5da2a651d 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_new_splash_round.webp and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_new_splash_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_new_splash.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_new_splash.webp index b2d893d881..01b1fbc69b 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_new_splash.webp and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_new_splash.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_new_splash_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_new_splash_round.webp index fee5841ff0..6e4518593c 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_new_splash_round.webp and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_new_splash_round.webp differ diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7dc9aefdec..57f9ca78b3 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -105,7 +105,7 @@ export class AppComponent implements OnInit { style: Style.Default, }); - setTimeout(async () => await SplashScreen.hide(), 1000); + setTimeout(async () => await SplashScreen.hide(), 500); /* * Use the app's font size irrespective of the user's device font size. diff --git a/src/app/auth/sign-in/sign-in.page.html b/src/app/auth/sign-in/sign-in.page.html index 0fbb779586..c34f951475 100644 --- a/src/app/auth/sign-in/sign-in.page.html +++ b/src/app/auth/sign-in/sign-in.page.html @@ -7,6 +7,7 @@ height="auto" onloadedmetadata="this.muted = true" oncanplay="this.play()" + poster="../../../assets/images/video-default-background.png" loop > diff --git a/src/app/auth/sign-in/sign-in.page.scss b/src/app/auth/sign-in/sign-in.page.scss index 957ae2928b..39cfb9ceb8 100644 --- a/src/app/auth/sign-in/sign-in.page.scss +++ b/src/app/auth/sign-in/sign-in.page.scss @@ -41,7 +41,7 @@ &__select-sign-in { display: flex; flex-direction: column; - height: 96vh; + height: 100vh; padding: 20px 20px 0 20px; } @@ -49,6 +49,7 @@ margin-top: 24px; position: relative; height: 144px; + margin-bottom: 24px; } &__divider-container { @@ -62,7 +63,7 @@ &__divider { height: 1px; - background-color: $grey; + background-color: $border-tertiary; width: 100%; } @@ -70,7 +71,7 @@ height: 96vh; margin-top: 18px; padding: 20px; - padding: 28px 20px 44px 20px; + padding: 24px 20px 44px 20px; display: flex; flex-direction: column; justify-content: space-between; @@ -134,8 +135,9 @@ &__disabled-email { font-size: 14px; font-weight: 500; - line-height: 1.28; + line-height: 1.3; color: $blue-black; + padding-top: 4px; margin-bottom: 24px; display: flex; align-items: center; @@ -269,3 +271,16 @@ } } } + +.fade-in { + animation: fadeIn 0.5s ease-in-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/src/app/auth/sign-in/sign-in.page.spec.ts b/src/app/auth/sign-in/sign-in.page.spec.ts index 130c3cf8a0..4ca8ea1a06 100644 --- a/src/app/auth/sign-in/sign-in.page.spec.ts +++ b/src/app/auth/sign-in/sign-in.page.spec.ts @@ -15,7 +15,7 @@ import { Router, ActivatedRoute, RouterModule } from '@angular/router'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { of, throwError } from 'rxjs'; +import { of, Subscription, throwError } from 'rxjs'; import { extendedDeviceInfoMockData } from 'src/app/core/mock-data/extended-device-info.data'; import { ErrorComponent } from './error/error.component'; import { authResData1, authResData2, samlResData1, samlResData2 } from 'src/app/core/mock-data/auth-reponse.data'; @@ -28,6 +28,9 @@ import { click, getElementBySelector, getTextContent } from 'src/app/core/dom-he import { By } from '@angular/platform-browser'; import { HttpErrorResponse } from '@angular/common/http'; import { SignInPageState } from './sign-in-page-state.enum'; +import { PlatformHandlerService } from 'src/app/core/services/platform-handler.service'; +import { BackButtonService } from 'src/app/core/services/back-button.service'; +import { BackButtonActionPriority } from 'src/app/core/models/back-button-action-priority.enum'; describe('SignInPage', () => { let component: SignInPage; @@ -45,6 +48,8 @@ describe('SignInPage', () => { let deviceService: jasmine.SpyObj; let loginInfoService: jasmine.SpyObj; let inAppBrowserService: jasmine.SpyObj; + let platformHandlerService: jasmine.SpyObj; + let backButtonService: jasmine.SpyObj; beforeEach(waitForAsync(() => { const routerAuthServiceSpy = jasmine.createSpyObj('RouterAuthService', [ @@ -64,7 +69,8 @@ describe('SignInPage', () => { const deviceServiceSpy = jasmine.createSpyObj('DeviceService', ['getDeviceInfo']); const loginInfoServiceSpy = jasmine.createSpyObj('LoginInfoService', ['addLoginInfo']); const inAppBrowserServiceSpy = jasmine.createSpyObj('InAppBrowserService', ['create']); - + const platformHandlerServiceSpy = jasmine.createSpyObj('PlatformHandlerService', ['registerBackButtonAction']); + const backButtonServiceSpy = jasmine.createSpyObj('BackButtonService', ['showAppCloseAlert']); TestBed.configureTestingModule({ declarations: [SignInPage, MatButton], imports: [ @@ -128,6 +134,14 @@ describe('SignInPage', () => { provide: InAppBrowserService, useValue: inAppBrowserServiceSpy, }, + { + provide: PlatformHandlerService, + useValue: platformHandlerServiceSpy, + }, + { + provide: BackButtonService, + useValue: backButtonServiceSpy, + }, ], schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA], }).compileComponents(); @@ -147,6 +161,8 @@ describe('SignInPage', () => { deviceService = TestBed.inject(DeviceService) as jasmine.SpyObj; loginInfoService = TestBed.inject(LoginInfoService) as jasmine.SpyObj; inAppBrowserService = TestBed.inject(InAppBrowserService) as jasmine.SpyObj; + platformHandlerService = TestBed.inject(PlatformHandlerService) as jasmine.SpyObj; + backButtonService = TestBed.inject(BackButtonService) as jasmine.SpyObj; loaderService.showLoader.and.returnValue(new Promise(() => {})); router.navigate.and.stub(); @@ -206,6 +222,73 @@ describe('SignInPage', () => { }); }); + it('ionViewWillEnter(): should set up back button actions', () => { + const mockSubscription = new Subscription(); + platformHandlerService.registerBackButtonAction.and.returnValue(mockSubscription); + spyOn(component, 'goBack'); + component.ionViewWillEnter(); + expect(component.hardwareBackButtonAction).toEqual(mockSubscription); + expect(platformHandlerService.registerBackButtonAction).toHaveBeenCalledOnceWith( + BackButtonActionPriority.MEDIUM, + component.goBack + ); + }); + + describe('goBack(): ', () => { + it('should navigate to ENTER_EMAIL when current step is ENTER_PASSWORD', () => { + spyOn(component, 'changeState'); + + component.currentStep = SignInPageState.ENTER_PASSWORD; + component.goBack(); + + expect(component.changeState).toHaveBeenCalledWith(SignInPageState.ENTER_EMAIL); + expect(backButtonService.showAppCloseAlert).not.toHaveBeenCalled(); + }); + + it('should show app close alert for other states', () => { + spyOn(component, 'changeState'); + + component.currentStep = SignInPageState.SELECT_SIGN_IN_METHOD; + component.goBack(); + + expect(backButtonService.showAppCloseAlert).toHaveBeenCalledTimes(1); + expect(component.changeState).not.toHaveBeenCalled(); + }); + + it('should show app close alert for other states', () => { + spyOn(component, 'changeState'); + + component.currentStep = SignInPageState.ENTER_EMAIL; + component.goBack(); + + expect(component.changeState).toHaveBeenCalledWith(SignInPageState.SELECT_SIGN_IN_METHOD); + }); + }); + + it('ionViewWillLeave(): should unsubscribe from hardwareBackButtonAction', () => { + component.hardwareBackButtonAction = new Subscription(); + spyOn(component.hardwareBackButtonAction, 'unsubscribe'); + component.ionViewWillLeave(); + + expect(component.hardwareBackButtonAction.unsubscribe).toHaveBeenCalled(); + }); + + it('changeState(): should update the current step', () => { + component.changeState(SignInPageState.ENTER_EMAIL); + + expect(component.currentStep).toBe(SignInPageState.ENTER_EMAIL); + + component.changeState(SignInPageState.ENTER_PASSWORD); + + expect(component.currentStep).toBe(SignInPageState.ENTER_PASSWORD); + }); + + it('goToForgotPasswordPage(): should navigate to the reset password page with the email parameter', () => { + component.goToForgotPasswordPage(); + + expect(router.navigate).toHaveBeenCalledWith(['/', 'auth', 'reset_password', { email: 'ajain@fyle.in' }]); + }); + describe('checkIfEmailExists(): ', () => { it('should check if value email exists', (done) => { component.fg.controls.email.setValue('email@gmail.com'); @@ -419,15 +502,6 @@ describe('SignInPage', () => { expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'auth', 'switch_org', { choose: true }]); expect(component.trackLoginInfo).toHaveBeenCalledTimes(1); }); - - it("should throw an error if google reponse doesn't contain access token", async () => { - googleAuthService.login.and.resolveTo(authResData1); - spyOn(component, 'handleError'); - - await component.googleSignIn(); - expect(googleAuthService.login).toHaveBeenCalledTimes(1); - expect(component.handleError).toHaveBeenCalledOnceWith(undefined); - }); }); it('trackLoginInfo(): should track login', async () => { diff --git a/src/app/auth/sign-in/sign-in.page.ts b/src/app/auth/sign-in/sign-in.page.ts index f0eecd33f8..98a12a01b3 100644 --- a/src/app/auth/sign-in/sign-in.page.ts +++ b/src/app/auth/sign-in/sign-in.page.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { RouterAuthService } from 'src/app/core/services/router-auth.service'; -import { from, throwError, Observable, of, noop } from 'rxjs'; +import { from, throwError, Observable, of, noop, Subscription } from 'rxjs'; import { PopoverController } from '@ionic/angular'; import { ErrorComponent } from './error/error.component'; import { shareReplay, filter, finalize, switchMap, map, tap, take } from 'rxjs/operators'; @@ -17,6 +17,9 @@ import { HttpErrorResponse } from '@angular/common/http'; import { EmailExistsResponse } from 'src/app/core/models/email-exists-response.model'; import { SamlResponse } from 'src/app/core/models/saml-response.model'; import { SignInPageState } from './sign-in-page-state.enum'; +import { BackButtonActionPriority } from 'src/app/core/models/back-button-action-priority.enum'; +import { PlatformHandlerService } from 'src/app/core/services/platform-handler.service'; +import { BackButtonService } from 'src/app/core/services/back-button.service'; @Component({ selector: 'app-sign-in', @@ -40,6 +43,8 @@ export class SignInPage implements OnInit { signInPageState: typeof SignInPageState = SignInPageState; + hardwareBackButtonAction: Subscription; + constructor( private formBuilder: FormBuilder, private routerAuthService: RouterAuthService, @@ -52,7 +57,9 @@ export class SignInPage implements OnInit { private trackingService: TrackingService, private deviceService: DeviceService, private loginInfoService: LoginInfoService, - private inAppBrowserService: InAppBrowserService + private inAppBrowserService: InAppBrowserService, + private platformHandlerService: PlatformHandlerService, + private backButtonService: BackButtonService ) {} async checkSAMLResponseAndSignInUser(data: SamlResponse): Promise { @@ -250,7 +257,6 @@ export class SignInPage implements OnInit { this.fg.reset(); this.router.navigate(['/', 'auth', 'switch_org', { choose: true }]); }, - error: (err: HttpErrorResponse) => this.handleError(err), }); } @@ -260,14 +266,24 @@ export class SignInPage implements OnInit { await this.loginInfoService.addLoginInfo(deviceInfo.appVersion, new Date()); } - ionViewWillEnter(): void { - if (this.activatedRoute.snapshot.params.email) { - this.currentStep = SignInPageState.ENTER_PASSWORD; - } else { - this.currentStep = SignInPageState.SELECT_SIGN_IN_METHOD; + goBack(): void { + switch (this.currentStep) { + case SignInPageState.ENTER_EMAIL: + this.changeState(SignInPageState.SELECT_SIGN_IN_METHOD); + break; + case SignInPageState.ENTER_PASSWORD: + this.changeState(SignInPageState.ENTER_EMAIL); + break; + default: + this.backButtonService.showAppCloseAlert(); } } + ionViewWillEnter(): void { + const priority = BackButtonActionPriority.MEDIUM; + this.hardwareBackButtonAction = this.platformHandlerService.registerBackButtonAction(priority, this.goBack); + } + changeState(state: SignInPageState): void { this.currentStep = state; } @@ -290,4 +306,8 @@ export class SignInPage implements OnInit { } }); } + + ionViewWillLeave(): void { + this.hardwareBackButtonAction.unsubscribe(); + } } diff --git a/src/assets/images/video-default-background.png b/src/assets/images/video-default-background.png new file mode 100644 index 0000000000..ede5b2084f Binary files /dev/null and b/src/assets/images/video-default-background.png differ