From 341b209092462d0d7c60aedd3e9beb6a19c6c5c7 Mon Sep 17 00:00:00 2001 From: Rainer Killinger Date: Wed, 5 Jun 2024 11:07:30 +0000 Subject: [PATCH] refactor: display id-cards in their own modal --- frontend/app/README.md | 69 ++++++--- .../app/android/app/capacitor.build.gradle | 2 + .../src/main/assets/capacitor.plugins.json | 8 + .../app/android/capacitor.settings.gradle | 40 ++--- frontend/app/ios/App/Podfile | 40 ++--- frontend/app/package.json | 2 + frontend/app/scripts/get-ip.mjs | 8 + .../app/modules/profile/id-card.component.ts | 141 ++++++++++++++++-- .../app/src/app/modules/profile/id-card.html | 7 +- .../app/src/app/modules/profile/id-card.scss | 5 - .../app/modules/profile/id-cards.component.ts | 2 - pnpm-lock.yaml | 23 +++ 12 files changed, 273 insertions(+), 74 deletions(-) create mode 100644 frontend/app/scripts/get-ip.mjs diff --git a/frontend/app/README.md b/frontend/app/README.md index 32113c46..3095d098 100644 --- a/frontend/app/README.md +++ b/frontend/app/README.md @@ -13,25 +13,25 @@ The StApps 1.x.x (legacy app, but current app in stores) is written using Ionic There are (`npm`) scripts defined to get the app running as quickly as possible. Those scripts (shortcuts for docker commands) are called using the syntax `npm run + `. So we have the following commands available: -``` +```sh npm run docker:pull ``` which pulls the up-to-date image ([Dockerfile](Dockerfile)) which contains all the tools needed for building, serving and deploying the app. -``` +```sh npm run docker:enter ``` which enters the container on docker builder image, where we can run `npm install` (to install the required npm packages) and `npm build` (to build the app: convert into executable files), but also any other arbitrary commands with the tools available in the docker image. -``` +```sh npm run docker:build ``` which runs `npm install` (to install the required npm packages) and `npm build` (to build the app: convert into executable files) in the docker container which runs on the docker builder image. -``` +```sh npm run docker:serve ``` @@ -39,7 +39,7 @@ which serves the app for running it in the browser. It basically runs `ionic ser ## How to build and start the app using the default backend? -``` +```sh npm run build npm run start ``` @@ -86,52 +86,85 @@ addToIonicDB( You'll need to run _Chromium_ using -```shell +```sh pnpm chromium:no-cors ``` ### Help, I can't log in! Login services will often block hosts not coming from the production -server. You can circumvent this locally by using the `:virtual-host` -scripts: +server. -```shell +#### Web + +On the web you can circumvent this locally by using the `:virtual-host` scripts: + +```sh # Start the dev server on mobile.app.uni-frankfurt.de pnpm start:virtual-host # Run chromium with flags that redirect mobile.app.uni-frankfurt.de to localhost:8100 pnpm chromium:virtual-host ``` +#### Android + +On Android you will need to change the `custom_url_scheme` values +to `de.unifrankfurt.app` in the following files: + +- `android/app/src/main/res/values/strings.xml` +- `src/environment/environment.ts` + +Then start the app normally as you would + +```sh +pnpm run:android +``` + +#### iOS + +On Android you will need to change the `custom_url_scheme` value in +`src/environment/environment.ts` as well as the `CFBundleURLTypes` +in `ios/App/App/Info.plist`. + +- make sure to remove any `Info.plist.orig` as capacitor might override + the modified `Info.plist` with that. +- make sure you have a valid device in XCode (Simulator or real device). + +After that, run + +```sh +pnpm run:ios +``` + ### Running the app Install the npm packages needed for running the app (as for any other node project which uses npm): -``` +```sh npm install ``` Check the code for linter issues: -``` +```sh npm run lint ``` Automatically fix linter issues (those where autofix is possible): -``` +```sh npm run lint:fix ``` Build the app (transpile etc.): -``` +```sh npm run build ``` Open the app in the browser: -``` +```sh ionic serve ``` @@ -139,7 +172,7 @@ ionic serve Run the app for testing on an android device (with live reload in the webview / device, when files are changed): -``` +```sh npm run build # if needed npm run resources:android # generate needed resources (icons and splashscreens) npm run docker:run:android # runs "ionic capacitor run android --livereload --external" on a selected device @@ -159,7 +192,7 @@ Besides that, it is possible to monitor processes (and so the processes related Build the (debug) app for testing on an android device (creates an APK file in the android's build outputs path): -``` +```sh npm run docker:build:android ``` @@ -169,13 +202,13 @@ The mentioned `docker:*:android` npm commands are executed in a docker container Execute unit tests: -``` +```sh npm test ``` Execute e2e tests: -``` +```sh npm run e2e ``` diff --git a/frontend/app/android/app/capacitor.build.gradle b/frontend/app/android/app/capacitor.build.gradle index 2baa8ea1..01dd5e0d 100644 --- a/frontend/app/android/app/capacitor.build.gradle +++ b/frontend/app/android/app/capacitor.build.gradle @@ -9,6 +9,7 @@ android { apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { + implementation project(':capacitor-community-screen-brightness') implementation project(':capacitor-app') implementation project(':capacitor-browser') implementation project(':capacitor-clipboard') @@ -21,6 +22,7 @@ dependencies { implementation project(':capacitor-local-notifications') implementation project(':capacitor-network') implementation project(':capacitor-preferences') + implementation project(':capacitor-screen-orientation') implementation project(':capacitor-share') implementation project(':capacitor-splash-screen') implementation project(':transistorsoft-capacitor-background-fetch') diff --git a/frontend/app/android/app/src/main/assets/capacitor.plugins.json b/frontend/app/android/app/src/main/assets/capacitor.plugins.json index 3c08c5ed..524cf3ad 100644 --- a/frontend/app/android/app/src/main/assets/capacitor.plugins.json +++ b/frontend/app/android/app/src/main/assets/capacitor.plugins.json @@ -1,4 +1,8 @@ [ + { + "pkg": "@capacitor-community/screen-brightness", + "classpath": "com.elylucas.capscreenbrightness.ScreenBrightnessPlugin" + }, { "pkg": "@capacitor/app", "classpath": "com.capacitorjs.plugins.app.AppPlugin" @@ -47,6 +51,10 @@ "pkg": "@capacitor/preferences", "classpath": "com.capacitorjs.plugins.preferences.PreferencesPlugin" }, + { + "pkg": "@capacitor/screen-orientation", + "classpath": "com.capacitorjs.plugins.screenorientation.ScreenOrientationPlugin" + }, { "pkg": "@capacitor/share", "classpath": "com.capacitorjs.plugins.share.SharePlugin" diff --git a/frontend/app/android/capacitor.settings.gradle b/frontend/app/android/capacitor.settings.gradle index f6430451..0536f767 100644 --- a/frontend/app/android/capacitor.settings.gradle +++ b/frontend/app/android/capacitor.settings.gradle @@ -1,51 +1,57 @@ // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN include ':capacitor-android' -project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/android/capacitor') +project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@5.7.3_@capacitor+core@5.7.3/node_modules/@capacitor/android/capacitor') + +include ':capacitor-community-screen-brightness' +project(':capacitor-community-screen-brightness').projectDir = new File('../../../node_modules/.pnpm/@capacitor-community+screen-brightness@6.0.0_@capacitor+core@5.7.3/node_modules/@capacitor-community/screen-brightness/android') include ':capacitor-app' -project(':capacitor-app').projectDir = new File('../../../node_modules/.pnpm/@capacitor+app@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/app/android') +project(':capacitor-app').projectDir = new File('../../../node_modules/.pnpm/@capacitor+app@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/app/android') include ':capacitor-browser' -project(':capacitor-browser').projectDir = new File('../../../node_modules/.pnpm/@capacitor+browser@5.1.0_@capacitor+core@5.5.0/node_modules/@capacitor/browser/android') +project(':capacitor-browser').projectDir = new File('../../../node_modules/.pnpm/@capacitor+browser@5.2.0_@capacitor+core@5.7.3/node_modules/@capacitor/browser/android') include ':capacitor-clipboard' -project(':capacitor-clipboard').projectDir = new File('../../../node_modules/.pnpm/@capacitor+clipboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/clipboard/android') +project(':capacitor-clipboard').projectDir = new File('../../../node_modules/.pnpm/@capacitor+clipboard@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/clipboard/android') include ':capacitor-device' -project(':capacitor-device').projectDir = new File('../../../node_modules/.pnpm/@capacitor+device@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/device/android') +project(':capacitor-device').projectDir = new File('../../../node_modules/.pnpm/@capacitor+device@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/device/android') include ':capacitor-dialog' -project(':capacitor-dialog').projectDir = new File('../../../node_modules/.pnpm/@capacitor+dialog@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/dialog/android') +project(':capacitor-dialog').projectDir = new File('../../../node_modules/.pnpm/@capacitor+dialog@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/dialog/android') include ':capacitor-filesystem' -project(':capacitor-filesystem').projectDir = new File('../../../node_modules/.pnpm/@capacitor+filesystem@5.1.4_@capacitor+core@5.5.0/node_modules/@capacitor/filesystem/android') +project(':capacitor-filesystem').projectDir = new File('../../../node_modules/.pnpm/@capacitor+filesystem@5.2.1_@capacitor+core@5.7.3/node_modules/@capacitor/filesystem/android') include ':capacitor-geolocation' -project(':capacitor-geolocation').projectDir = new File('../../../node_modules/.pnpm/@capacitor+geolocation@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/geolocation/android') +project(':capacitor-geolocation').projectDir = new File('../../../node_modules/.pnpm/@capacitor+geolocation@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/geolocation/android') include ':capacitor-haptics' -project(':capacitor-haptics').projectDir = new File('../../../node_modules/.pnpm/@capacitor+haptics@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/haptics/android') +project(':capacitor-haptics').projectDir = new File('../../../node_modules/.pnpm/@capacitor+haptics@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/haptics/android') include ':capacitor-keyboard' -project(':capacitor-keyboard').projectDir = new File('../../../node_modules/.pnpm/@capacitor+keyboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/keyboard/android') +project(':capacitor-keyboard').projectDir = new File('../../../node_modules/.pnpm/@capacitor+keyboard@5.0.8_@capacitor+core@5.7.3/node_modules/@capacitor/keyboard/android') include ':capacitor-local-notifications' -project(':capacitor-local-notifications').projectDir = new File('../../../node_modules/.pnpm/@capacitor+local-notifications@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/local-notifications/android') +project(':capacitor-local-notifications').projectDir = new File('../../../node_modules/.pnpm/@capacitor+local-notifications@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/local-notifications/android') include ':capacitor-network' -project(':capacitor-network').projectDir = new File('../../../node_modules/.pnpm/@capacitor+network@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/network/android') +project(':capacitor-network').projectDir = new File('../../../node_modules/.pnpm/@capacitor+network@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/network/android') include ':capacitor-preferences' -project(':capacitor-preferences').projectDir = new File('../../../node_modules/.pnpm/@capacitor+preferences@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/preferences/android') +project(':capacitor-preferences').projectDir = new File('../../../node_modules/.pnpm/@capacitor+preferences@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/preferences/android') + +include ':capacitor-screen-orientation' +project(':capacitor-screen-orientation').projectDir = new File('../../../node_modules/.pnpm/@capacitor+screen-orientation@6.0.0_@capacitor+core@5.7.3/node_modules/@capacitor/screen-orientation/android') include ':capacitor-share' -project(':capacitor-share').projectDir = new File('../../../node_modules/.pnpm/@capacitor+share@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/share/android') +project(':capacitor-share').projectDir = new File('../../../node_modules/.pnpm/@capacitor+share@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/share/android') include ':capacitor-splash-screen' -project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/.pnpm/@capacitor+splash-screen@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/splash-screen/android') +project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/.pnpm/@capacitor+splash-screen@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/splash-screen/android') include ':transistorsoft-capacitor-background-fetch' -project(':transistorsoft-capacitor-background-fetch').projectDir = new File('../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.1.1_@capacitor+core@5.5.0/node_modules/@transistorsoft/capacitor-background-fetch/android') +project(':transistorsoft-capacitor-background-fetch').projectDir = new File('../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.2.0_@capacitor+core@5.7.3/node_modules/@transistorsoft/capacitor-background-fetch/android') include ':capacitor-secure-storage-plugin' -project(':capacitor-secure-storage-plugin').projectDir = new File('../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.5.0/node_modules/capacitor-secure-storage-plugin/android') +project(':capacitor-secure-storage-plugin').projectDir = new File('../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.7.3/node_modules/capacitor-secure-storage-plugin/android') diff --git a/frontend/app/ios/App/Podfile b/frontend/app/ios/App/Podfile index b7093ca7..72e9d679 100644 --- a/frontend/app/ios/App/Podfile +++ b/frontend/app/ios/App/Podfile @@ -1,4 +1,4 @@ -require_relative '../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios/scripts/pods_helpers' +require_relative '../../../../node_modules/.pnpm/@capacitor+ios@5.7.3_@capacitor+core@5.7.3/node_modules/@capacitor/ios/scripts/pods_helpers' platform :ios, '13.0' use_frameworks! @@ -9,24 +9,26 @@ use_frameworks! install! 'cocoapods', :disable_input_output_paths => true def capacitor_pods - pod 'Capacitor', :path => '../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios' - pod 'CapacitorCordova', :path => '../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios' - pod 'CapacitorApp', :path => '../../../../node_modules/.pnpm/@capacitor+app@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/app' - pod 'CapacitorBrowser', :path => '../../../../node_modules/.pnpm/@capacitor+browser@5.1.0_@capacitor+core@5.5.0/node_modules/@capacitor/browser' - pod 'CapacitorClipboard', :path => '../../../../node_modules/.pnpm/@capacitor+clipboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/clipboard' - pod 'CapacitorDevice', :path => '../../../../node_modules/.pnpm/@capacitor+device@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/device' - pod 'CapacitorDialog', :path => '../../../../node_modules/.pnpm/@capacitor+dialog@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/dialog' - pod 'CapacitorFilesystem', :path => '../../../../node_modules/.pnpm/@capacitor+filesystem@5.1.4_@capacitor+core@5.5.0/node_modules/@capacitor/filesystem' - pod 'CapacitorGeolocation', :path => '../../../../node_modules/.pnpm/@capacitor+geolocation@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/geolocation' - pod 'CapacitorHaptics', :path => '../../../../node_modules/.pnpm/@capacitor+haptics@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/haptics' - pod 'CapacitorKeyboard', :path => '../../../../node_modules/.pnpm/@capacitor+keyboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/keyboard' - pod 'CapacitorLocalNotifications', :path => '../../../../node_modules/.pnpm/@capacitor+local-notifications@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/local-notifications' - pod 'CapacitorNetwork', :path => '../../../../node_modules/.pnpm/@capacitor+network@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/network' - pod 'CapacitorPreferences', :path => '../../../../node_modules/.pnpm/@capacitor+preferences@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/preferences' - pod 'CapacitorShare', :path => '../../../../node_modules/.pnpm/@capacitor+share@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/share' - pod 'CapacitorSplashScreen', :path => '../../../../node_modules/.pnpm/@capacitor+splash-screen@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/splash-screen' - pod 'TransistorsoftCapacitorBackgroundFetch', :path => '../../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.1.1_@capacitor+core@5.5.0/node_modules/@transistorsoft/capacitor-background-fetch' - pod 'CapacitorSecureStoragePlugin', :path => '../../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.5.0/node_modules/capacitor-secure-storage-plugin' + pod 'Capacitor', :path => '../../../../node_modules/.pnpm/@capacitor+ios@5.7.3_@capacitor+core@5.7.3/node_modules/@capacitor/ios' + pod 'CapacitorCordova', :path => '../../../../node_modules/.pnpm/@capacitor+ios@5.7.3_@capacitor+core@5.7.3/node_modules/@capacitor/ios' + pod 'CapacitorCommunityScreenBrightness', :path => '../../../../node_modules/.pnpm/@capacitor-community+screen-brightness@6.0.0_@capacitor+core@5.7.3/node_modules/@capacitor-community/screen-brightness' + pod 'CapacitorApp', :path => '../../../../node_modules/.pnpm/@capacitor+app@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/app' + pod 'CapacitorBrowser', :path => '../../../../node_modules/.pnpm/@capacitor+browser@5.2.0_@capacitor+core@5.7.3/node_modules/@capacitor/browser' + pod 'CapacitorClipboard', :path => '../../../../node_modules/.pnpm/@capacitor+clipboard@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/clipboard' + pod 'CapacitorDevice', :path => '../../../../node_modules/.pnpm/@capacitor+device@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/device' + pod 'CapacitorDialog', :path => '../../../../node_modules/.pnpm/@capacitor+dialog@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/dialog' + pod 'CapacitorFilesystem', :path => '../../../../node_modules/.pnpm/@capacitor+filesystem@5.2.1_@capacitor+core@5.7.3/node_modules/@capacitor/filesystem' + pod 'CapacitorGeolocation', :path => '../../../../node_modules/.pnpm/@capacitor+geolocation@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/geolocation' + pod 'CapacitorHaptics', :path => '../../../../node_modules/.pnpm/@capacitor+haptics@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/haptics' + pod 'CapacitorKeyboard', :path => '../../../../node_modules/.pnpm/@capacitor+keyboard@5.0.8_@capacitor+core@5.7.3/node_modules/@capacitor/keyboard' + pod 'CapacitorLocalNotifications', :path => '../../../../node_modules/.pnpm/@capacitor+local-notifications@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/local-notifications' + pod 'CapacitorNetwork', :path => '../../../../node_modules/.pnpm/@capacitor+network@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/network' + pod 'CapacitorPreferences', :path => '../../../../node_modules/.pnpm/@capacitor+preferences@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/preferences' + pod 'CapacitorScreenOrientation', :path => '../../../../node_modules/.pnpm/@capacitor+screen-orientation@6.0.0_@capacitor+core@5.7.3/node_modules/@capacitor/screen-orientation' + pod 'CapacitorShare', :path => '../../../../node_modules/.pnpm/@capacitor+share@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/share' + pod 'CapacitorSplashScreen', :path => '../../../../node_modules/.pnpm/@capacitor+splash-screen@5.0.7_@capacitor+core@5.7.3/node_modules/@capacitor/splash-screen' + pod 'TransistorsoftCapacitorBackgroundFetch', :path => '../../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.2.0_@capacitor+core@5.7.3/node_modules/@transistorsoft/capacitor-background-fetch' + pod 'CapacitorSecureStoragePlugin', :path => '../../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.7.3/node_modules/capacitor-secure-storage-plugin' pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins' end diff --git a/frontend/app/package.json b/frontend/app/package.json index c32257ef..59278144 100644 --- a/frontend/app/package.json +++ b/frontend/app/package.json @@ -61,6 +61,7 @@ "@angular/router": "17.3.0", "@awesome-cordova-plugins/calendar": "6.6.0", "@awesome-cordova-plugins/core": "6.6.0", + "@capacitor-community/screen-brightness": "6.0.0", "@capacitor/app": "5.0.7", "@capacitor/browser": "5.2.0", "@capacitor/clipboard": "5.0.7", @@ -74,6 +75,7 @@ "@capacitor/local-notifications": "5.0.7", "@capacitor/network": "5.0.7", "@capacitor/preferences": "5.0.7", + "@capacitor/screen-orientation": "6.0.0", "@capacitor/share": "5.0.7", "@capacitor/splash-screen": "5.0.7", "@ionic-native/core": "5.36.0", diff --git a/frontend/app/scripts/get-ip.mjs b/frontend/app/scripts/get-ip.mjs new file mode 100644 index 00000000..2228343a --- /dev/null +++ b/frontend/app/scripts/get-ip.mjs @@ -0,0 +1,8 @@ +import {networkInterfaces} from 'os'; + +console.log( + Object.entries(networkInterfaces()) + .map(([, info]) => info) + .flat() + .find(info => info && !info.internal && info.family === 'IPv4')?.address, +); diff --git a/frontend/app/src/app/modules/profile/id-card.component.ts b/frontend/app/src/app/modules/profile/id-card.component.ts index 0f73a481..72835a4f 100644 --- a/frontend/app/src/app/modules/profile/id-card.component.ts +++ b/frontend/app/src/app/modules/profile/id-card.component.ts @@ -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(); + } } diff --git a/frontend/app/src/app/modules/profile/id-card.html b/frontend/app/src/app/modules/profile/id-card.html index 8df27539..7625cc76 100644 --- a/frontend/app/src/app/modules/profile/id-card.html +++ b/frontend/app/src/app/modules/profile/id-card.html @@ -1,4 +1,9 @@ - + @if (item.validity && (item.validity | toDateRange | isInRangeNow | async) === false) {
{{ 'profile.userInfo.expired' | translate | titlecase }}
} diff --git a/frontend/app/src/app/modules/profile/id-card.scss b/frontend/app/src/app/modules/profile/id-card.scss index 5f7e9a1f..72c88b21 100644 --- a/frontend/app/src/app/modules/profile/id-card.scss +++ b/frontend/app/src/app/modules/profile/id-card.scss @@ -3,11 +3,6 @@ overflow: hidden; } -:host:fullscreen { - margin: 0; - padding: 0; -} - img { border-radius: 3mm; } diff --git a/frontend/app/src/app/modules/profile/id-cards.component.ts b/frontend/app/src/app/modules/profile/id-cards.component.ts index aba980c5..695c505f 100644 --- a/frontend/app/src/app/modules/profile/id-cards.component.ts +++ b/frontend/app/src/app/modules/profile/id-cards.component.ts @@ -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, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c284639e..812a73f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -725,6 +725,9 @@ importers: '@awesome-cordova-plugins/core': specifier: 6.6.0 version: 6.6.0(rxjs@7.8.1) + '@capacitor-community/screen-brightness': + specifier: 6.0.0 + version: 6.0.0(@capacitor/core@5.7.3) '@capacitor/app': specifier: 5.0.7 version: 5.0.7(@capacitor/core@5.7.3) @@ -764,6 +767,9 @@ importers: '@capacitor/preferences': specifier: 5.0.7 version: 5.0.7(@capacitor/core@5.7.3) + '@capacitor/screen-orientation': + specifier: 6.0.0 + version: 6.0.0(@capacitor/core@5.7.3) '@capacitor/share': specifier: 5.0.7 version: 5.0.7(@capacitor/core@5.7.3) @@ -4031,6 +4037,14 @@ packages: dev: false optional: true + /@capacitor-community/screen-brightness@6.0.0(@capacitor/core@5.7.3): + resolution: {integrity: sha512-8yU2Epwym7IKJ3Ae8LDlo6RDbZuo4x2B2M1oKT04kaVjWRxHzx6wETpzLJqrwix1NyqbXIx5TPPBpk0Kxmv45w==} + peerDependencies: + '@capacitor/core': ^6.0.0 + dependencies: + '@capacitor/core': 5.7.3 + dev: false + /@capacitor/android@5.7.3(@capacitor/core@5.7.3): resolution: {integrity: sha512-l4FoagqyoId+D/597fG8pPfmVrNevzWzbtkJkbGtQZS5rqRR4HCNoJn0LAMs812o9bfuxGdE1T3MjpehGOe0Rw==} peerDependencies: @@ -4228,6 +4242,14 @@ packages: '@capacitor/core': 5.7.3 dev: false + /@capacitor/screen-orientation@6.0.0(@capacitor/core@5.7.3): + resolution: {integrity: sha512-E2hoKNcvn2DwDyFDyJr9t725oN5VEETPQ57wHm4DPnz3AhTqLXqMdI6pvmAiWenfPIW2N0XMDqWLhLKpftEp4A==} + peerDependencies: + '@capacitor/core': ^6.0.0 + dependencies: + '@capacitor/core': 5.7.3 + dev: false + /@capacitor/share@5.0.7(@capacitor/core@5.7.3): resolution: {integrity: sha512-4GraggRRxwhstxIdF9JyOEBq4QTufqFOekdB4P9GeiQYWJoA5VraSR1mwy4Trke1VFfaBjz/nGi4WQOJdHIAgg==} peerDependencies: @@ -16253,6 +16275,7 @@ packages: /pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + requiresBuild: true dev: true /pirates@4.0.6: