mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-21 17:12:43 +00:00
refactor: display id-cards in their own modal
This commit is contained in:
committed by
Thea Schöbl
parent
a6b88d3534
commit
341b209092
@@ -1,10 +1,14 @@
|
||||
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, ElementRef, Input} from '@angular/core';
|
||||
import {SCIdCard} from '@openstapps/core';
|
||||
import {FullScreenImageDirective} from '../../util/full-screen-image.directive';
|
||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {AsyncPipe, TitleCasePipe} from '@angular/common';
|
||||
import {InRangeNowPipe, ToDateRangePipe} from '../../util/in-range.pipe';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {AnimationController, ModalController} from '@ionic/angular';
|
||||
import {ScreenBrightness} from '@capacitor-community/screen-brightness';
|
||||
import {ScreenOrientation} from '@capacitor/screen-orientation';
|
||||
import {Capacitor} from '@capacitor/core';
|
||||
import {iosDuration, iosEasing, mdDuration, mdEasing} from 'src/app/animation/easings';
|
||||
|
||||
@Component({
|
||||
selector: 'stapps-id-card',
|
||||
@@ -12,17 +16,130 @@ import {TranslateModule} from '@ngx-translate/core';
|
||||
styleUrls: ['id-card.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
hostDirectives: [FullScreenImageDirective],
|
||||
imports: [
|
||||
FullScreenImageDirective,
|
||||
ThingTranslateModule,
|
||||
InRangeNowPipe,
|
||||
ToDateRangePipe,
|
||||
AsyncPipe,
|
||||
TranslateModule,
|
||||
TitleCasePipe,
|
||||
],
|
||||
imports: [ThingTranslateModule, InRangeNowPipe, ToDateRangePipe, AsyncPipe, TranslateModule, TitleCasePipe],
|
||||
})
|
||||
export class IdCardComponent {
|
||||
@Input({required: true}) item: SCIdCard;
|
||||
|
||||
constructor(
|
||||
private modalController: ModalController,
|
||||
private animationController: AnimationController,
|
||||
private elementRef: ElementRef,
|
||||
) {}
|
||||
|
||||
async presentFullscreen() {
|
||||
const top = await this.modalController.getTop();
|
||||
const mode = document.querySelector('ion-app')!.getAttribute('mode');
|
||||
if (top) return;
|
||||
|
||||
if (window.innerWidth >= 768) return;
|
||||
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
const orientation = await ScreenOrientation.orientation();
|
||||
ScreenOrientation.lock({orientation: 'portrait'});
|
||||
if (orientation.type.startsWith('landscape')) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
}
|
||||
|
||||
const img: HTMLImageElement = this.elementRef.nativeElement.querySelector('img')!;
|
||||
|
||||
const safeArea = (location: string) =>
|
||||
Number(img.computedStyleMap().get(`--ion-safe-area-${location}`)!.toString().replace(/px$/, ''));
|
||||
const safeAreaTop = safeArea('top');
|
||||
const safeAreaBottom = safeArea('bottom');
|
||||
const safeAreaLeft = safeArea('left');
|
||||
const safeAreaRight = safeArea('right');
|
||||
|
||||
const windowWidth = window.innerWidth - safeAreaLeft - safeAreaRight;
|
||||
const windowHeight = window.innerHeight - safeAreaTop - safeAreaBottom;
|
||||
|
||||
const isLandscape = windowWidth > windowHeight;
|
||||
const nativeBounds = img.getBoundingClientRect();
|
||||
const imageAspect = nativeBounds.width / nativeBounds.height;
|
||||
const imgWidth = Math.min(windowHeight, windowWidth * imageAspect);
|
||||
const imgHeight = imgWidth / imageAspect;
|
||||
const fullscreenWidth = isLandscape ? imgHeight : imgWidth;
|
||||
const fullscreenHeight = isLandscape ? imgWidth : imgHeight;
|
||||
|
||||
const scale = fullscreenWidth / windowWidth;
|
||||
const animation = (modal: HTMLElement, leave = false) => {
|
||||
const root = modal.shadowRoot!;
|
||||
|
||||
const sourceAnimation = this.animationController
|
||||
.create()
|
||||
.addElement(this.elementRef.nativeElement)
|
||||
.fromTo('opacity', 0, 0);
|
||||
const backdrop =
|
||||
'linear-gradient(to bottom,' +
|
||||
'transparent 16px,' +
|
||||
'rgba(0, 0, 0, 0.3) 20%,' +
|
||||
'rgba(0, 0, 0, 0.3) 80%,' +
|
||||
'transparent 100%)';
|
||||
const wrapperAnimation = this.animationController
|
||||
.create()
|
||||
.beforeStyles({'--background': 'transparent', 'margin': '0'})
|
||||
.addElement(root.querySelector('.modal-wrapper')!)
|
||||
.fromTo('transform', 'scale(1)', 'scale(1)')
|
||||
.fromTo('opacity', '1', '1');
|
||||
const backdropAnimation = this.animationController
|
||||
.create()
|
||||
.beforeStyles({background: backdrop, filter: 'blur(16px)'})
|
||||
.addElement(root.querySelector('ion-backdrop')!)
|
||||
.fromTo('opacity', leave ? 1 : 0, leave ? 0 : 1);
|
||||
|
||||
const small =
|
||||
`translate(${nativeBounds.left - safeAreaLeft}px, ${nativeBounds.top - safeAreaTop}px) ` +
|
||||
`scale(${nativeBounds.width / windowWidth}) ` +
|
||||
`rotate(0) `;
|
||||
const large =
|
||||
`translate(${windowWidth - (windowWidth - fullscreenHeight) / 2}px, ${
|
||||
(windowHeight - fullscreenWidth) / 2
|
||||
}px)` +
|
||||
`scale(${scale}) ` +
|
||||
`rotate(${isLandscape ? 0 : 90}deg) `;
|
||||
const cardAnimation = this.animationController
|
||||
.create()
|
||||
.addElement(modal.querySelector('stapps-id-card')!)
|
||||
.beforeStyles({
|
||||
'transform-origin': 'top left',
|
||||
'filter': 'drop-shadow(0 0 16px rgba(0, 0, 0, 0.1))',
|
||||
})
|
||||
.fromTo('transform', leave ? large : small, leave ? small : large);
|
||||
|
||||
return this.animationController
|
||||
.create()
|
||||
.addElement(modal)
|
||||
.easing(mode === 'ios' ? iosEasing : mdEasing)
|
||||
.duration(2 * (mode === 'ios' ? iosDuration : mdDuration))
|
||||
.addAnimation([wrapperAnimation, backdropAnimation, cardAnimation, sourceAnimation]);
|
||||
};
|
||||
|
||||
const modal = await this.modalController.create({
|
||||
component: IdCardComponent,
|
||||
backdropDismiss: true,
|
||||
mode: 'md',
|
||||
componentProps: {
|
||||
item: this.item,
|
||||
},
|
||||
presentingElement: this.elementRef.nativeElement,
|
||||
enterAnimation: base => animation(base),
|
||||
leaveAnimation: base => animation(base, true),
|
||||
});
|
||||
|
||||
const dismiss = () => modal.dismiss();
|
||||
window.addEventListener('click', dismiss);
|
||||
modal.addEventListener('didDismiss', () => window.removeEventListener('click', dismiss));
|
||||
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
const brightness = (await ScreenBrightness.getBrightness()).brightness;
|
||||
ScreenBrightness.setBrightness({brightness: 1});
|
||||
modal.addEventListener('didDismiss', () => {
|
||||
ScreenOrientation.unlock();
|
||||
ScreenBrightness.setBrightness({brightness: brightness === 1 ? 0.5 : brightness});
|
||||
});
|
||||
}
|
||||
|
||||
await modal.present();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<img [src]="item.image" [alt]="'name' | thingTranslate: item" draggable="false" />
|
||||
<img
|
||||
[src]="item.image"
|
||||
[alt]="'name' | thingTranslate: item"
|
||||
draggable="false"
|
||||
(click)="presentFullscreen()"
|
||||
/>
|
||||
@if (item.validity && (item.validity | toDateRange | isInRangeNow | async) === false) {
|
||||
<div class="expired">{{ 'profile.userInfo.expired' | translate | titlecase }}</div>
|
||||
}
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:host:fullscreen {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: 3mm;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import {IonicModule} from '@ionic/angular';
|
||||
import {AsyncPipe, TitleCasePipe} from '@angular/common';
|
||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
import {FullScreenImageDirective} from '../../util/full-screen-image.directive';
|
||||
import {IdCardComponent} from './id-card.component';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {Observable} from 'rxjs';
|
||||
@@ -36,7 +35,6 @@ import {Observable} from 'rxjs';
|
||||
AsyncPipe,
|
||||
ThingTranslateModule,
|
||||
UtilModule,
|
||||
FullScreenImageDirective,
|
||||
IdCardComponent,
|
||||
TranslateModule,
|
||||
TitleCasePipe,
|
||||
|
||||
Reference in New Issue
Block a user