Compare commits

...

54 Commits

Author SHA1 Message Date
Rainer Killinger
aafc59f6bc refactor: remove deprecated "jetifier" 2025-06-12 18:21:44 +02:00
Rainer Killinger
67e483dc82 ci: let pnpm handle build dependencies 2025-06-12 18:19:16 +02:00
Rainer Killinger
09ce46ed7e fix: prettier package type 2025-06-12 14:48:32 +02:00
Rainer Killinger
69ca673701 ci: update android commandline tools 2025-06-12 14:34:01 +02:00
Rainer Killinger
a00ba67eb3 fix: prettier depencencies 2025-06-12 14:28:35 +02:00
Rainer Killinger
cbd4225ab5 refactor: rebase 2025-06-12 14:00:43 +02:00
a962a35a8b feat: update to node 22 2025-06-12 10:59:54 +02:00
25d18e681b feat: temporarily use external node image in ci 2025-06-12 10:57:04 +02:00
e34807f086 refactor: update node to 22 2025-06-12 10:57:00 +02:00
516168f8e6 fix: test 2025-06-12 10:56:43 +02:00
f4d357e382 fix: build 2025-06-12 10:56:42 +02:00
685ed84c12 refactor: update to ionic v8
refactor: remove deprecated @ionic-native/core (replaced by
@awesome-cordova-plugins/core)
2025-06-12 10:55:23 +02:00
6ef5641aa0 refactor: update ngx-markdown to v18 2025-06-12 10:54:00 +02:00
05aaa227a9 refactor: update capacitor to v6 2025-06-12 10:53:33 +02:00
af4c885e3e fix: angular warning 2025-06-12 10:50:43 +02:00
7b81e19c92 refactor: base migration to Angular v18 2025-06-12 10:50:31 +02:00
5dd966f23c refactor: remove deprecated "jetifier" 2025-06-12 10:48:20 +02:00
Rainer Killinger
436e1471a7 docs: update changelogs for release
ci: publish release
2024-12-02 11:26:24 +01:00
Rainer Killinger
4c9d330c88 fix: update jsonpath-plus depenency 2024-12-02 11:08:58 +01:00
Thea Schöbl
580ebee362 fix: user gets logged out when their token expires
Closes #230
2024-11-28 18:04:08 +00:00
Rainer Killinger
07e5c80223 docs: update changelogs for release
ci: publish release
2024-11-05 11:40:19 +01:00
Jovan Krunić
2ac840d845 fix: observable immediately returns false if scheduled beyond max delay
Closes #229
2024-11-04 18:04:48 +01:00
Thea Schöbl
8b581ef9ca feat: rework about pages changelog 2024-11-04 07:50:47 +00:00
Rainer Killinger
e2d5d4f187 docs: update changelogs for release
ci: publish release
2024-10-18 16:52:57 +02:00
Rainer Killinger
496b50d892 fix: update jsonpath-plus depenency 2024-10-18 16:12:29 +02:00
e6c17c860b fix: id cards are wiped/replaced when an error happens in the pipe 2024-10-11 15:33:03 +02:00
Jovan Krunić
bb1f596bfc fix: enable starting the app without backend
Closes #223
2024-09-30 11:03:53 +00:00
0c49fd8c34 refactor: update nix dependencies 2024-09-30 13:00:59 +02:00
Thea Schöbl
ce5016a992 fix: text of the feedback form not fully visible
Closes #227
2024-09-18 09:53:50 +00:00
3ac8c04765 fix: date range is now pipe has issues around boundaries 2024-08-06 17:43:59 +02:00
Rainer Killinger
5c260dd26b refactor: update android build dependencies for API level 34 2024-08-05 12:51:47 +00:00
Rainer Killinger
f2e1a6ddd5 docs: update changelogs for release
ci: publish release
2024-08-02 10:19:44 +02:00
Rainer Killinger
ccacfbf710 fix: publish-packages script 2024-08-02 10:18:57 +02:00
Rainer Killinger
b249b329f7 refactor: use development backend for non production environment 2024-08-01 17:44:21 +02:00
Jovan Krunić
7afc24f1bc fix: use major version config from storage when offline
Closes #218
2024-08-01 17:44:20 +02:00
Rainer Killinger
7276525dfa fix: app-builder image build due to nodejs versions <18.19 not being supported by angular 17 2024-08-01 15:24:59 +02:00
Jovan Krunić
c9e881582f refactor: set production version of the backend 2024-07-19 15:20:06 +02:00
Jovan Krunić
df681de083 feat: show title if available on library account profile
Closes #215
2024-07-19 15:20:06 +02:00
802a7a3fa7 fix: docs generation 2024-07-19 15:20:06 +02:00
Jovan Krunić
343d03e647 feat: library account adjustments
Closes #214
2024-07-19 15:20:06 +02:00
727b92911e fix: list item layout broken 2024-07-19 15:20:06 +02:00
11bc987807 fix: elasticsearch integration spams errors 2024-07-19 15:20:06 +02:00
Rainer Killinger
b8faae5988 docs: update changelogs for release
ci: publish release
2024-06-28 17:39:39 +02:00
67ab1fd613 fix: geo.point has wrong mapping 2024-06-28 17:25:32 +02:00
Rainer Killinger
142079bf0e refactor: set app backend to dev version 2024-06-28 12:02:59 +02:00
Rainer Killinger
5050ac90eb docs: update changelogs for release
ci: publish release
2024-06-28 09:28:39 +02:00
Rainer Killinger
688bc5f2e7 refactor: add changeset 2024-06-28 09:03:54 +02:00
Rainer Killinger
ad174dd7d7 refactor: use static map files from backend 2024-06-28 08:59:08 +02:00
Rainer Killinger
26e654f5b8 fix: app license overview 2024-06-27 14:57:24 +02:00
Rainer Killinger
5439484a90 fix: changelog typo 2024-06-27 12:06:52 +02:00
Rainer Killinger
68f3366a27 refactor: adjustments for recent PAIA changes 2024-06-27 09:23:55 +00:00
Jovan Krunić
dea9a82105 fix: do not fetch remote configuration if offline
Closes #206
2024-06-17 09:55:29 +00:00
39d2801114 feat: store id cards 2024-06-12 13:51:46 +02:00
Rainer Killinger
341b209092 refactor: display id-cards in their own modal 2024-06-05 11:07:30 +00:00
139 changed files with 17341 additions and 13539 deletions

View File

@@ -30,13 +30,11 @@ variables:
description: Bypass turbo cache description: Bypass turbo cache
default: default:
image: registry.gitlab.com/openstapps/openstapps/node-builder image: registry.gitlab.com/openstapps/openstapps/node-builder:node-22
tags: tags:
- saas-linux-xlarge-amd64 - saas-linux-xlarge-amd64
interruptible: true interruptible: true
before_script: before_script:
- corepack enable
- corepack prepare pnpm@latest-8 --activate
- echo TURBO_API=$TURBO_API >> .env.local - echo TURBO_API=$TURBO_API >> .env.local
- echo TURBO_TOKEN=$TURBO_TOKEN >> .env.local - echo TURBO_TOKEN=$TURBO_TOKEN >> .env.local
- echo TURBO_TEAM=$TURBO_TEAM >> .env.local - echo TURBO_TEAM=$TURBO_TEAM >> .env.local
@@ -63,7 +61,7 @@ build:
# - pnpm i --prefer-offline # - pnpm i --prefer-offline
# - pnpm build:full:skip || pnpm i -r --prefer-offline # - pnpm build:full:skip || pnpm i -r --prefer-offline
- pnpm install - pnpm install
# TODO: whats the benifit for CI? something doesn't work with git here... # TODO: whats the benfit for CI? something doesn't work with git here...
# "🦋 error Error: Failed to find where HEAD diverged from master. Does master exist?" # "🦋 error Error: Failed to find where HEAD diverged from master. Does master exist?"
# - pnpm changeset:status # - pnpm changeset:status
- pnpm build:full $TURBO_CACHE_BYPASS - pnpm build:full $TURBO_CACHE_BYPASS
@@ -101,7 +99,7 @@ stop review:
rules: *deploy-rules rules: *deploy-rules
unit: unit:
image: registry.gitlab.com/openstapps/openstapps/app-builder image: registry.gitlab.com/openstapps/openstapps/app-builder:node-22
stage: test stage: test
needs: ['build'] needs: ['build']
script: script:
@@ -109,9 +107,9 @@ unit:
# - pnpm i --prefer-offline # - pnpm i --prefer-offline
# - pnpm test:skip || pnpm i -r --prefer-offline # - pnpm test:skip || pnpm i -r --prefer-offline
- pnpm install - pnpm install
- cd node_modules/.pnpm/re2*/node_modules/re2 # - cd node_modules/.pnpm/re2*/node_modules/re2
- npm run install # - npm run install
- cd $CI_PROJECT_DIR # - cd $CI_PROJECT_DIR
- pnpm test $TURBO_CACHE_BYPASS - pnpm test $TURBO_CACHE_BYPASS
coverage: '/Statements[^:]*\:[^:]*\s+([\d\.]+)%/' coverage: '/Statements[^:]*\:[^:]*\s+([\d\.]+)%/'
artifacts: artifacts:

View File

@@ -1,6 +1,6 @@
.limit_scheduled_pipelines: .limit_scheduled_pipelines:
rules: rules:
- if: $CI_COMMIT_BRANCH == 'main' && $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_BRANCH == 'ionic-8-update' && $CI_PIPELINE_SOURCE == "schedule"
base image: base image:
image: docker image: docker
@@ -16,8 +16,7 @@ base image:
- > - >
docker login -u "${CI_REGISTRY_USER}" -p "${CI_JOB_TOKEN}" "${CI_REGISTRY}" && docker login -u "${CI_REGISTRY_USER}" -p "${CI_JOB_TOKEN}" "${CI_REGISTRY}" &&
docker build docker build
-t "${CI_REGISTRY_IMAGE}/${IMAGE_NAME}:$(grep -o '"version": "[^"]*' "${DEPLOY_DIR}/package.json" | cut -d'"' -f4)" -t "${CI_REGISTRY_IMAGE}/${IMAGE_NAME}:node-22" "${CI_PROJECT_DIR}/${DEPLOY_DIR}" &&
-t "${CI_REGISTRY_IMAGE}/${IMAGE_NAME}:latest" "${CI_PROJECT_DIR}/${DEPLOY_DIR}" &&
docker push "${CI_REGISTRY_IMAGE}/${IMAGE_NAME}" --all-tags docker push "${CI_REGISTRY_IMAGE}/${IMAGE_NAME}" --all-tags
cache: {} # disable irrelevant cache for this job cache: {} # disable irrelevant cache for this job
before_script: [] # do not run irrelevant before script for this job before_script: [] # do not run irrelevant before script for this job

View File

@@ -27,6 +27,7 @@ const config = {
'bin', 'bin',
'files', 'files',
'engines', 'engines',
'packageManager',
'scripts', 'scripts',
'dependencies', 'dependencies',
'devDependencies', 'devDependencies',

View File

@@ -1,3 +1,3 @@
nodejs 18.19.1 nodejs 22.16.0
pnpm 8.15.4 pnpm 10.12.1
python 3.11.5 python 3.11.5

View File

@@ -1,5 +1,5 @@
integration: integration:
image: registry.gitlab.com/openstapps/openstapps/node-builder image: registry.gitlab.com/openstapps/openstapps/node-builder:node-22
stage: test stage: test
needs: ['build'] needs: ['build']
variables: variables:

View File

@@ -1,5 +1,31 @@
# @openstapps/backend # @openstapps/backend
## 3.3.2
### Patch Changes
- @openstapps/core@3.3.2
- @openstapps/core-tools@3.3.2
## 3.3.1
### Patch Changes
- 67ab1fd6: fix for geo.point mapping
## 3.3.0
### Minor Changes
- 688bc5f2: v3.3.0 changes
### Patch Changes
- Updated dependencies [688bc5f2]
- @openstapps/core@3.3.0
- @openstapps/core-tools@3.3.0
- @openstapps/logger@3.0.0
## 3.2.0 ## 3.2.0
### Minor Changes ### Minor Changes

View File

@@ -2,7 +2,7 @@
The Goethe-Uni App got even better! The Goethe-Uni App got even better!
## Completelty new map view ## Completely new map view
We overhauled the map to offer you a clearer and faster and overview. We overhauled the map to offer you a clearer and faster and overview.

View File

@@ -1,7 +1,7 @@
{ {
"name": "@openstapps/backend", "name": "@openstapps/backend",
"description": "A reference implementation for a StApps backend", "description": "A reference implementation for a StApps backend",
"version": "3.2.0", "version": "3.3.2",
"private": true, "private": true,
"type": "module", "type": "module",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
@@ -51,7 +51,7 @@
"@types/cors": "2.8.13", "@types/cors": "2.8.13",
"@types/express": "4.17.17", "@types/express": "4.17.17",
"@types/geojson": "1.0.6", "@types/geojson": "1.0.6",
"@types/node": "18.15.3", "@types/node": "22.15.31",
"@types/node-cron": "3.0.7", "@types/node-cron": "3.0.7",
"@types/nodemailer": "6.4.7", "@types/nodemailer": "6.4.7",
"@types/promise-queue": "2.2.0", "@types/promise-queue": "2.2.0",

View File

@@ -25,7 +25,7 @@ export function buildDistanceFilter(
): QueryDslSpecificQueryContainer<'geo_distance'> { ): QueryDslSpecificQueryContainer<'geo_distance'> {
const geoObject: QueryDslGeoDistanceQuery = { const geoObject: QueryDslGeoDistanceQuery = {
distance: `${filter.arguments.distance}m`, distance: `${filter.arguments.distance}m`,
[`${filter.arguments.field}.point.coordinates`]: { [`${filter.arguments.field}.point`]: {
lat: filter.arguments.position[1], lat: filter.arguments.position[1],
lon: filter.arguments.position[0], lon: filter.arguments.position[0],
}, },

View File

@@ -25,7 +25,7 @@ export function buildDistanceSort(sort: SCDistanceSort): SortOptions {
mode: 'avg', mode: 'avg',
order: sort.order, order: sort.order,
unit: 'm', unit: 'm',
[`${sort.arguments.field}.point.coordinates`]: { [`${sort.arguments.field}.point`]: {
lat: sort.arguments.position[1], lat: sort.arguments.position[1],
lon: sort.arguments.position[0], lon: sort.arguments.position[0],
}, },

View File

@@ -466,7 +466,7 @@ describe('Query', function () {
const expectedFilter: QueryDslSpecificQueryContainer<'geo_distance'> = { const expectedFilter: QueryDslSpecificQueryContainer<'geo_distance'> = {
geo_distance: { geo_distance: {
'distance': '1000m', 'distance': '1000m',
'geo.point.coordinates': { 'geo.point': {
lat: 8.123, lat: 8.123,
lon: 50.123, lon: 50.123,
}, },
@@ -636,7 +636,7 @@ describe('Query', function () {
'mode': 'avg', 'mode': 'avg',
'order': 'desc', 'order': 'desc',
'unit': 'm', 'unit': 'm',
'geo.point.coordinates': { 'geo.point': {
lat: 50.123, lat: 50.123,
lon: 8.123, lon: 8.123,
}, },

View File

@@ -44,7 +44,7 @@
"@openstapps/logger": "workspace:*", "@openstapps/logger": "workspace:*",
"@types/config": "3.3.0", "@types/config": "3.3.0",
"@types/dockerode": "3.3.17", "@types/dockerode": "3.3.17",
"@types/node": "18.15.3", "@types/node": "22.15.31",
"@types/sha1": "1.1.3", "@types/sha1": "1.1.3",
"config": "3.3.9", "config": "3.3.9",
"dockerode": "3.3.5", "dockerode": "3.3.5",
@@ -63,7 +63,7 @@
"@types/dockerode": "3.3.17", "@types/dockerode": "3.3.17",
"@types/mocha": "10.0.1", "@types/mocha": "10.0.1",
"@types/mustache": "4.2.2", "@types/mustache": "4.2.2",
"@types/node": "18.15.3", "@types/node": "22.15.31",
"@types/proxyquire": "1.3.28", "@types/proxyquire": "1.3.28",
"@types/semver": "7.5.8", "@types/semver": "7.5.8",
"@types/sha1": "1.1.3", "@types/sha1": "1.1.3",

View File

@@ -17,7 +17,7 @@
], ],
"devDependencies": { "devDependencies": {
"@openstapps/tsconfig": "workspace:*", "@openstapps/tsconfig": "workspace:*",
"@types/node": "18.15.3", "@types/node": "22.15.31",
"eslint": "8.57.0", "eslint": "8.57.0",
"typescript": "5.4.2" "typescript": "5.4.2"
}, },

View File

@@ -12,4 +12,4 @@ const config = {
endOfLine: 'lf' endOfLine: 'lf'
} }
export default config; module.exports = config;

View File

@@ -2,7 +2,7 @@
"name": "@openstapps/prettier-config", "name": "@openstapps/prettier-config",
"description": "StApps Prettier Config", "description": "StApps Prettier Config",
"version": "3.2.0", "version": "3.2.0",
"type": "module", "type": "commonjs",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/prettier-config.git", "repository": "git@gitlab.com:openstapps/prettier-config.git",
"author": "Thea Schöbl <dev@theaninova.de>", "author": "Thea Schöbl <dev@theaninova.de>",

View File

@@ -56,7 +56,7 @@
"@types/glob": "8.1.0", "@types/glob": "8.1.0",
"@types/mocha": "10.0.1", "@types/mocha": "10.0.1",
"@types/mustache": "4.2.2", "@types/mustache": "4.2.2",
"@types/node": "18.15.3", "@types/node": "22.15.31",
"@types/tmp": "0.2.3", "@types/tmp": "0.2.3",
"c8": "7.14.0", "c8": "7.14.0",
"chai": "4.3.7", "chai": "4.3.7",

View File

@@ -1,5 +1,11 @@
# @openstapps/tsconfig # @openstapps/tsconfig
## 3.3.0
### Minor Changes
- 688bc5f2: v3.3.0 changes
## 3.0.0 ## 3.0.0
### Major Changes ### Major Changes
@@ -30,7 +36,7 @@
```js ```js
#!/usr/bin/env node #!/usr/bin/env node
import './lib/app.js'; import "./lib/app.js";
``` ```
- 64caebaf: Migrate to ESM - 64caebaf: Migrate to ESM
@@ -105,7 +111,7 @@
```js ```js
#!/usr/bin/env node #!/usr/bin/env node
import './lib/app.js'; import "./lib/app.js";
``` ```
- 64caebaf: Migrate to ESM - 64caebaf: Migrate to ESM

View File

@@ -1,7 +1,7 @@
{ {
"name": "@openstapps/tsconfig", "name": "@openstapps/tsconfig",
"description": "The tsconfig for the openstapps project", "description": "The tsconfig for the openstapps project",
"version": "3.0.0", "version": "3.3.0",
"type": "commonjs", "type": "commonjs",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/eslint-config.git", "repository": "git@gitlab.com:openstapps/eslint-config.git",

View File

@@ -9,7 +9,7 @@ const args = files.map(it => `${it.split('/', 2)[1]}='${it}'`);
console.log( console.log(
'Collecting coverage...', 'Collecting coverage...',
await promisify(exec)(`cobertura-merge -o ./coverage.xml ${args.join(' ')}`), await promisify(exec)(`merge-cobertura -o ./coverage.xml ${args.join(' ')}`),
); );
const reportFiles = await glob('./*/*/coverage/report-junit.xml'); const reportFiles = await glob('./*/*/coverage/report-junit.xml');

View File

@@ -1,12 +1,28 @@
# @openstapps/minimal-connector # @openstapps/minimal-connector
## 3.3.2
### Patch Changes
- @openstapps/core@3.3.2
- @openstapps/api@3.3.2
## 3.3.0
### Patch Changes
- Updated dependencies [688bc5f2]
- @openstapps/api@3.3.0
- @openstapps/core@3.3.0
- @openstapps/logger@3.0.0
## 3.2.0 ## 3.2.0
### Patch Changes ### Patch Changes
- Updated dependencies [912ae422] - Updated dependencies [912ae422]
- @openstapps/core@4.0.0 - @openstapps/core@3.2.0
- @openstapps/api@4.0.0 - @openstapps/api@3.2.0
- @openstapps/logger@3.0.0 - @openstapps/logger@3.0.0
## 3.1.1 ## 3.1.1

View File

@@ -1,7 +1,7 @@
{ {
"name": "@openstapps/minimal-connector", "name": "@openstapps/minimal-connector",
"description": "This is a minimal connector which serves as an example", "description": "This is a minimal connector which serves as an example",
"version": "3.2.0", "version": "3.3.2",
"private": true, "private": true,
"type": "module", "type": "module",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
@@ -45,7 +45,7 @@
"@types/chai": "4.3.5", "@types/chai": "4.3.5",
"@types/chai-as-promised": "7.1.5", "@types/chai-as-promised": "7.1.5",
"@types/mocha": "10.0.1", "@types/mocha": "10.0.1",
"@types/node": "18.15.3", "@types/node": "22.15.31",
"c8": "7.14.0", "c8": "7.14.0",
"chai": "4.3.7", "chai": "4.3.7",
"chai-as-promised": "7.1.1", "chai-as-promised": "7.1.1",

View File

@@ -1,13 +1,33 @@
# @openstapps/minimal-plugin # @openstapps/minimal-plugin
## 3.3.2
### Patch Changes
- @openstapps/core@3.3.2
- @openstapps/core-tools@3.3.2
- @openstapps/api-plugin@3.3.2
- @openstapps/api@3.3.2
## 3.3.0
### Patch Changes
- Updated dependencies [688bc5f2]
- @openstapps/api@3.3.0
- @openstapps/core@3.3.0
- @openstapps/api-plugin@3.3.0
- @openstapps/core-tools@3.3.0
- @openstapps/logger@3.0.0
## 3.2.0 ## 3.2.0
### Patch Changes ### Patch Changes
- Updated dependencies [912ae422] - Updated dependencies [912ae422]
- @openstapps/core@4.0.0 - @openstapps/core@3.2.0
- @openstapps/api@4.0.0 - @openstapps/api@3.2.0
- @openstapps/api-plugin@4.0.0 - @openstapps/api-plugin@3.2.0
- @openstapps/core-tools@3.0.0 - @openstapps/core-tools@3.0.0
- @openstapps/logger@3.0.0 - @openstapps/logger@3.0.0

View File

@@ -1,7 +1,7 @@
{ {
"name": "@openstapps/minimal-plugin", "name": "@openstapps/minimal-plugin",
"description": "Minimal Plugin", "description": "Minimal Plugin",
"version": "3.2.0", "version": "3.3.2",
"private": true, "private": true,
"type": "module", "type": "module",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
@@ -42,7 +42,7 @@
"@openstapps/prettier-config": "workspace:*", "@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*", "@openstapps/tsconfig": "workspace:*",
"@types/express": "4.17.17", "@types/express": "4.17.17",
"@types/node": "18.15.3", "@types/node": "22.15.31",
"tsup": "6.7.0", "tsup": "6.7.0",
"typescript": "5.4.2" "typescript": "5.4.2"
}, },

12
flake.lock generated
View File

@@ -5,11 +5,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1709126324, "lastModified": 1710146030,
"narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=", "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "d465f4819400de7c8d874d50b982301f28a84605", "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1709747860, "lastModified": 1717112898,
"narHash": "sha256-RT4zuBy579m+l8VyIQFOR66WXfcs4g1jntZUHjh6eoI=", "narHash": "sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "58ae79ea707579c40102ddf62d84b902a987c58b", "rev": "6132b0f6e344ce2fe34fc051b72fb46e34f668e0",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -4,31 +4,46 @@
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
}; };
outputs = { outputs =
{
self, self,
nixpkgs, nixpkgs,
flake-utils, flake-utils,
}: let }:
aapt2buildToolsVersion = "33.0.2"; let
aapt2buildToolsVersion = "34.0.0";
in in
flake-utils.lib.eachDefaultSystem (system: let flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
overlays = [ overlays = [
(final: prev: rec { (final: prev: {
fontMin = prev.python311.withPackages (ps: with ps; [brotli fonttools] ++ (with fonttools.optional-dependencies; [woff])); fontMin = prev.python311.withPackages (
ps:
with ps;
[
brotli
fonttools
]
++ (with fonttools.optional-dependencies; [ woff ])
);
android = prev.androidenv.composeAndroidPackages { android = prev.androidenv.composeAndroidPackages {
buildToolsVersions = ["30.0.3" aapt2buildToolsVersion]; buildToolsVersions = prev.lib.lists.unique [
platformVersions = ["33"]; "34.0.0"
aapt2buildToolsVersion
];
platformVersions = [ "34" ];
}; };
cypress = prev.cypress.overrideAttrs (cyPrev: rec { cypress = prev.cypress.overrideAttrs (cyPrev: rec {
version = "13.2.0"; version = "13.10.0";
src = prev.fetchzip { src = prev.fetchzip {
url = "https://cdn.cypress.io/desktop/${version}/linux-x64/cypress.zip"; url = "https://cdn.cypress.io/desktop/${version}/linux-x64/cypress.zip";
hash = "sha256-9o0nprGcJhudS1LNm+T7Vf0Dwd1RBauYKI+w1FBQ3ZM="; hash = "sha256-wKNXo2lWndsQaouOiul0rsOWah+RRQ6fljzdeC8/KW4=";
}; };
}); });
nodejs = prev.nodejs_18; nodejs = prev.nodejs_22;
}) })
]; ];
config = { config = {
@@ -38,7 +53,6 @@
}; };
androidFhs = pkgs.buildFHSUserEnv { androidFhs = pkgs.buildFHSUserEnv {
name = "android-env"; name = "android-env";
targetPkgs = pkgs: with pkgs; [];
runScript = "bash"; runScript = "bash";
profile = '' profile = ''
export ALLOW_NINJA_ENV=true export ALLOW_NINJA_ENV=true
@@ -46,12 +60,13 @@
export LD_LIBRARY_PATH=/usr/lib:/usr/lib32 export LD_LIBRARY_PATH=/usr/lib:/usr/lib32
''; '';
}; };
in { in
{
devShell = pkgs.mkShell rec { devShell = pkgs.mkShell rec {
nativeBuildInputs = [ androidFhs ]; nativeBuildInputs = [ androidFhs ];
buildInputs = with pkgs; [ buildInputs = with pkgs; [
nodejs nodejs
corepack nodePackages.pnpm
# tools # tools
curl curl
jq jq
@@ -66,6 +81,9 @@
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_SDK_ROOT}/build-tools/${aapt2buildToolsVersion}/aapt2"; GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_SDK_ROOT}/build-tools/${aapt2buildToolsVersion}/aapt2";
CYPRESS_INSTALL_BINARY = "0"; CYPRESS_INSTALL_BINARY = "0";
CYPRESS_RUN_BINARY = "${pkgs.cypress}/bin/Cypress"; CYPRESS_RUN_BINARY = "${pkgs.cypress}/bin/Cypress";
# https://github.com/nodejs/node/issues/48444#issuecomment-1591882694
UV_USE_IO_URING = "0";
}; };
}); }
);
} }

View File

@@ -1,12 +1,12 @@
e2e: e2e:
image: registry.gitlab.com/openstapps/openstapps/app-cypress:node-18 image: registry.gitlab.com/openstapps/openstapps/app-cypress:node-22
stage: test stage: test
script: script:
- pnpm --filter=@openstapps/app install - pnpm install
- pnpm --filter=@openstapps/app exec cypress install - pnpm --filter=@openstapps/app exec cypress install
- cd node_modules/.pnpm/re2*/node_modules/re2 # - cd node_modules/.pnpm/re2*/node_modules/re2
- npm run install # - npm run install
- cd $CI_PROJECT_DIR # - cd $CI_PROJECT_DIR
- pnpm test:integration:app - pnpm test:integration:app
artifacts: artifacts:
when: on_failure when: on_failure

View File

@@ -1,5 +1,44 @@
# @openstapps/app # @openstapps/app
## 3.3.5
### Patch Changes
- 4c9d330c: fix user logout when token expires
## 3.3.4
### Minor Changes
- 8b581ef9: Use user facing changelogs in the about pages as the primary source, with the technical changes accessible through a sub menu.
## 3.3.3
### Patch Changes
- 496b50d8: Bug fixes and Android target sdk version is now 34
## 3.3.2
### Patch Changes
- 7afc24f1: fixed config version comparison
- @openstapps/core@3.3.2
- @openstapps/api@3.3.2
## 3.3.0
### Minor Changes
- 688bc5f2: v3.3.0 changes
### Patch Changes
- Updated dependencies [688bc5f2]
- @openstapps/api@3.2.0
- @openstapps/core@3.2.0
- @openstapps/collection-utils@3.0.0
## 3.2.0 ## 3.2.0
### Patch Changes ### Patch Changes
@@ -295,3 +334,225 @@
- Updated dependencies [64caebaf] - Updated dependencies [64caebaf]
- @openstapps/api@3.0.0-next.0 - @openstapps/api@3.0.0-next.0
- @openstapps/core@3.0.0-next.0 - @openstapps/core@3.0.0-next.0
## 2.1.1
### Bug Fixes
- autofocus searchbar only when no default data is displayed ([e90286f](https://gitlab.com/openstapps/app/commit/e90286fc6814f5c40af3e297be42f23128b991be))
- breadcrumbs are under parallax ([9e160e8](https://gitlab.com/openstapps/app/commit/9e160e8d1ee9409e4fbe518c9dd9748705e680e1))
- browser logout only if endSession url defined ([7f6de94](https://gitlab.com/openstapps/app/commit/7f6de94ab572be66d7f10758c37dcf10af46b4e0)), closes [#395](https://gitlab.com/openstapps/app/issues/395)
- can't select some list elements on safari 16.4 ([3e99d7f](https://gitlab.com/openstapps/app/commit/3e99d7fa8fcae4538e1afe6a017570b0cb6ff45e))
- canteen view removes item select listener on view exit ([05e996a](https://gitlab.com/openstapps/app/commit/05e996ae9052b11c23dc1093ef526f08e3e2e6b6))
- catalog module semester selection ([afbd1fc](https://gitlab.com/openstapps/app/commit/afbd1fc048997acbc0113f8957016f8947b58626))
- catalog semester selection ([a8c7d5a](https://gitlab.com/openstapps/app/commit/a8c7d5ab5934adf853cc40c1df311178df54057e))
- data detail local favorite fallback causing duplicate nested favorite view ([dff4a95](https://gitlab.com/openstapps/app/commit/dff4a95acc55574b4872b0e4e39555cce0b2fd60))
- data-detail favorite button color leaking to list items ([95f2da2](https://gitlab.com/openstapps/app/commit/95f2da2def39ec56f40a660a63e5f7fddb4b3d53))
- location flow on iOS devices ([f207e02](https://gitlab.com/openstapps/app/commit/f207e029f1b30624bf411a57b3c552ef259e13ed))
- parallax broken since safari 16.4 ([0f970fa](https://gitlab.com/openstapps/app/commit/0f970fa1f164a310e24a85140d8bf0da21e5a5f1))
- remove infinite scroll e2e test ([47565e5](https://gitlab.com/openstapps/app/commit/47565e51b0dda5c8227238b6dc19f0d277408429))
- replace breadcrumb popover with simply expanding the breadcrumbs ([1318cbc](https://gitlab.com/openstapps/app/commit/1318cbca7f0cf72e10d96fff1a1116ba073fe8dc))
- schedule tabs navigating to the wrong url ([abf2ab6](https://gitlab.com/openstapps/app/commit/abf2ab6a5a94941d439adf54ec677332823892db))
- translate simple pipe doesn't update on language changes ([f5ca150](https://gitlab.com/openstapps/app/commit/f5ca1508fb9d95693615bfb9e03bc127bd83be00))
- typo in catalog provider query ([f49c44f](https://gitlab.com/openstapps/app/commit/f49c44f5c53780e4794dc1ef4cbacfb20cabbd97))
- workaround for side menu items not being active on page load ([947cab4](https://gitlab.com/openstapps/app/commit/947cab458ca770f116d28a1f22687ae620e71b59))
### Features
- add content to empty catalogs ([982fb16](https://gitlab.com/openstapps/app/commit/982fb1653b3c1253aac9366733f14c22c94d2537))
- add easy way to configure search filtering for nested properties ([2220ab2](https://gitlab.com/openstapps/app/commit/2220ab24b385188515da7c176bf9c1ac72651fd9))
- dark theme ([e75a466](https://gitlab.com/openstapps/app/commit/e75a46633ca3685d6044eb9a8b2fd670f2cd030f))
- dashboard search rework ([8c30a47](https://gitlab.com/openstapps/app/commit/8c30a47706f07eb222fac47ad7fed61f9044328a))
- implement custom cdk virtual scroll behavior ([968cb72](https://gitlab.com/openstapps/app/commit/968cb729575c529fd6d1d35da1b50a8689057c46))
- optional logout from identity provider ([8cd2d77](https://gitlab.com/openstapps/app/commit/8cd2d777ab3a67b1ab24f03aa50a3ff73550c3d2)), closes [#372](https://gitlab.com/openstapps/app/issues/372)
- revamp dashboard mensa section ([33a74d9](https://gitlab.com/openstapps/app/commit/33a74d96ae92137f53a775e90bff99e5f2d41f6a))
- rework settings page design ([2f1298c](https://gitlab.com/openstapps/app/commit/2f1298c9d7df25d2a16576245ea62c1b6094e507))
- show in-place in date series modal, resolves [#385](https://gitlab.com/openstapps/app/issues/385) ([22e70ae](https://gitlab.com/openstapps/app/commit/22e70ae92b35578b559e6644dccb8d4bfd06af1e)), closes [#386](https://gitlab.com/openstapps/app/issues/386) [#388](https://gitlab.com/openstapps/app/issues/388)
- transition to full sidebar at xl instead of lg ([cc939f3](https://gitlab.com/openstapps/app/commit/cc939f38873833b7cc0260525a2ecd536f27bfa5))
## 2.0.1
### Bug Fixes
- assessment segments can become unreadable ([939fb6e](https://gitlab.com/openstapps/app/commit/939fb6ef0f11b40cb71fbe61da90f50b1f75c3f7))
- login button not easily found ([11d1ac3](https://gitlab.com/openstapps/app/commit/11d1ac3f7ce27c2822ea8f839df3f3dffbd6c020))
- remove misleading assessment calculations ([aefae33](https://gitlab.com/openstapps/app/commit/aefae33d5c9fa9ee3efe346e45429aca79ae3c48))
- right-align add event detail chip ([1eee652](https://gitlab.com/openstapps/app/commit/1eee652533c6b8f613ce09df9c3421f89209419a))
### Features
- offline notice ([9b4caf5](https://gitlab.com/openstapps/app/commit/9b4caf526ffb53ec8d8885342323fcc52fd9fc09))
- separate prettier from eslint ([a88d000](https://gitlab.com/openstapps/app/commit/a88d000ccd6cbdeb5fbb07d209f2491023f9d76c))
# 2.0.0
### Bug Fixes
- daia_url missing in environment configs ([863a3ff](https://gitlab.com/openstapps/app/commit/863a3ffd488425e3313ab9b812c4b6d50c68a244))
- 404 on all surge links ([f1b4930](https://gitlab.com/openstapps/app/commit/f1b4930a3068e73aee20b4c3d71dac551ab60c35))
- add areaServed to person detail ([488150f](https://gitlab.com/openstapps/app/commit/488150f7f5558c05c1ec8a71afcb9f9a37e68a37))
- add event popover expanding beyond screen width ([046a95b](https://gitlab.com/openstapps/app/commit/046a95ba1dca3f5ded7e5555d3167f52f95be107))
- add location info to dates from timetable ([92adb9d](https://gitlab.com/openstapps/app/commit/92adb9dd2db18027dcc327433027e28c81ecbd4b)), closes [#344](https://gitlab.com/openstapps/app/issues/344)
- add missing profile translation ([cdb6ac4](https://gitlab.com/openstapps/app/commit/cdb6ac4084f8704d7f2336387a837b86f78c062b))
- add nav button to schedule page ([e628f39](https://gitlab.com/openstapps/app/commit/e628f396e22e51da2c9f2489fe89e42ccf474e2b))
- add openingHours config and catch its errors ([6125d43](https://gitlab.com/openstapps/app/commit/6125d43e8c18f2bf2afda67c0ff72e00d98ab34f))
- add PKCE parameters for PAIA auth ([f3e83bf](https://gitlab.com/openstapps/app/commit/f3e83bfcc88423f0935a060ccd0bf6198da58351))
- address late init from ionic components ([0bce9e5](https://gitlab.com/openstapps/app/commit/0bce9e5452fc5d05123756348dc30308de675bfa))
- adjust code to overcome the breaking changes (ionic 4 to 5) ([f779042](https://gitlab.com/openstapps/app/commit/f7790426cd2da4a6b33e2aa73783943f45b3de02)), closes [#70](https://gitlab.com/openstapps/app/issues/70)
- adjust hebis catalog templates ([83d9a4a](https://gitlab.com/openstapps/app/commit/83d9a4a8b8fe5b8687c72a717b3a2964524006e0))
- adjust library account user info ([bafabb1](https://gitlab.com/openstapps/app/commit/bafabb1d4ec299e2bea43cd4b8442ef33be2329a)), closes [#331](https://gitlab.com/openstapps/app/issues/331)
- adjust npm docker scripts and typos ([82bb15b](https://gitlab.com/openstapps/app/commit/82bb15b863e2d2e4df20244fda2f2e0d049ff43f))
- angular1 ng-if leftover ([25434d5](https://gitlab.com/openstapps/app/commit/25434d54e3800fd72a6c5d9188fb11f441d73aa9))
- assign navigation app name dynamically ([90b9733](https://gitlab.com/openstapps/app/commit/90b97339d3948b0864f634519416fe4a458b459f))
- background fetch crashing android app ([3316ad9](https://gitlab.com/openstapps/app/commit/3316ad9169ed2b29a2755405589213f824aec9d1))
- calculating SCDateSeries for next unit view ([82ba5f8](https://gitlab.com/openstapps/app/commit/82ba5f81211fb10cc5fde04991856567c4ac9680))
- canteen module layout ([b89f5c4](https://gitlab.com/openstapps/app/commit/b89f5c4edd1ed14f7529edc4e4ea54f9d98fda7c))
- canteen module layout ([6f7c680](https://gitlab.com/openstapps/app/commit/6f7c680ed89f027d863ebc02f5b24895d84f32e4))
- cards not having rounded corners in safari ([8a04a43](https://gitlab.com/openstapps/app/commit/8a04a439032920ead799f8b7483f29b896797c37))
- catch error `Setting "language" not provided` ([584878d](https://gitlab.com/openstapps/app/commit/584878d9503b8406b6ee7ec69dde5b8b3c4fd954))
- CI stage/job setup ([ae429ca](https://gitlab.com/openstapps/app/commit/ae429ca5fb3b5f10cad377d37b251806b3dabf6c))
- config.get issue by updating ionic ([9386351](https://gitlab.com/openstapps/app/commit/93863510fac32ed5b887011175a4807df3f522b8))
- **config:** fix catch ConfigFetchError in getValue ([161da63](https://gitlab.com/openstapps/app/commit/161da630ea59f6205ee53dece950972d4f36ada5)), closes [#46](https://gitlab.com/openstapps/app/issues/46)
- correct data path color ([0d755bc](https://gitlab.com/openstapps/app/commit/0d755bcbd3d6fea59a4f7a59981fd28844ff90d5))
- correct html whitespace handling for icon detection ([9bc3642](https://gitlab.com/openstapps/app/commit/9bc3642990b687dd524470fd26df80351aa85f1e))
- daia availability ([13cee2d](https://gitlab.com/openstapps/app/commit/13cee2d4261c7301c1c763446ae44dcdd005172d))
- dashboard next unit structural directive causing animation issues ([c8f6a27](https://gitlab.com/openstapps/app/commit/c8f6a27c571e51bcc0ac0120968e6bc9a20c8dd7))
- **data:** fix and adjust detail test using translation ([478f49a](https://gitlab.com/openstapps/app/commit/478f49a8744211b3c9458b3dadc791d62a46ae46)), closes [#50](https://gitlab.com/openstapps/app/issues/50)
- **data:** fix template and other consistency issues ([c3bc227](https://gitlab.com/openstapps/app/commit/c3bc227a3ca4a295b2b31bfe7fd34830b33c9f05))
- detail page when dish of mensa selected ([e5c2270](https://gitlab.com/openstapps/app/commit/e5c227073a183a1c562e17f3fe14a51048d01637)), closes [#140](https://gitlab.com/openstapps/app/issues/140)
- download events should respect selection ([28fbfef](https://gitlab.com/openstapps/app/commit/28fbfef18cc3b457f7020a70157ea7a4fff345d0))
- enable background fetch on iOS ([a1592f8](https://gitlab.com/openstapps/app/commit/a1592f84cc48f7cae8c55ef806cddbe806034bb5))
- enable overflow for day labels in schedule ([1195c5f](https://gitlab.com/openstapps/app/commit/1195c5ffc8cea07f1e224d92a7fb25aa5858bf0a))
- encode URI parameter to proxy URI ([bc13cc5](https://gitlab.com/openstapps/app/commit/bc13cc5e1fe6144fe0a0e53c0748a154844a5c29)), closes [#326](https://gitlab.com/openstapps/app/issues/326)
- extend landing period button not working on android ([0caa69c](https://gitlab.com/openstapps/app/commit/0caa69c28cbb2f962b70a1da13659739c1c6dd3e)), closes [#333](https://gitlab.com/openstapps/app/issues/333)
- failing native http requests with body ([fed4f20](https://gitlab.com/openstapps/app/commit/fed4f20c3cf43221512f3d2b6dd3c3fe7a4cf43a))
- feedback not allowing valid emails ([cf74c8e](https://gitlab.com/openstapps/app/commit/cf74c8e19f8bd34a31d5af931781e84be2c04dea)), closes [#349](https://gitlab.com/openstapps/app/issues/349)
- fix issues found by ng build for production ([a503811](https://gitlab.com/openstapps/app/commit/a503811c1cfcf909571af766ccd884856aad8ec9)), closes [#48](https://gitlab.com/openstapps/app/issues/48)
- fix various typos ([ad0dae4](https://gitlab.com/openstapps/app/commit/ad0dae46ff04d28551d2ece950d9a4d3442541d2))
- fixate webdriver to match used chrome version ([24dbb42](https://gitlab.com/openstapps/app/commit/24dbb42b345458b7dbdd17b2759824b3b68cb0e4))
- generate library online links properly ([9854541](https://gitlab.com/openstapps/app/commit/9854541a0c062c31bce167673586dccc8af81785)), closes [#340](https://gitlab.com/openstapps/app/issues/340)
- handle prices as an optional property ([9e71efc](https://gitlab.com/openstapps/app/commit/9e71efca9f7b1086db26f580192d6b349bdcb964)), closes [#219](https://gitlab.com/openstapps/app/issues/219)
- header logo changing size on ios navigate ([38f0a30](https://gitlab.com/openstapps/app/commit/38f0a300769a5b7cda35af0927c17099f93981b8))
- ignore null-island location ([d3188f5](https://gitlab.com/openstapps/app/commit/d3188f50905d610097de6c90bc58e6373d30e0dc)), closes [#149](https://gitlab.com/openstapps/app/issues/149)
- item not available in offer template ([7b402d6](https://gitlab.com/openstapps/app/commit/7b402d61c30aed81a5671d778a38c8393a5bc7c8)), closes [#110](https://gitlab.com/openstapps/app/issues/110)
- library account missing ready for pickup ([e504d8c](https://gitlab.com/openstapps/app/commit/e504d8cf6dd1c12fcb8f6a315527337313662385)), closes [#330](https://gitlab.com/openstapps/app/issues/330)
- library fines should load item title only if needed ([cbb949e](https://gitlab.com/openstapps/app/commit/cbb949e3977a5821e6bd1b654dec66a82e4d8c81)), closes [#342](https://gitlab.com/openstapps/app/issues/342)
- links of timetable tabs ([837c69b](https://gitlab.com/openstapps/app/commit/837c69bb21c92a91259051d5680e1073b390fc0e)), closes [#144](https://gitlab.com/openstapps/app/issues/144)
- logged out button not showing on profile ([ebdc14d](https://gitlab.com/openstapps/app/commit/ebdc14d3c398ac7564c077757c564f4e414fe244)), closes [#239](https://gitlab.com/openstapps/app/issues/239)
- make action chips react to changes of their item ([c0d0b1b](https://gitlab.com/openstapps/app/commit/c0d0b1bd9934e8d9e23f47825cae6a5d8ea2f38a))
- make keyboard dismissable on mobile devices ([b2cc1fd](https://gitlab.com/openstapps/app/commit/b2cc1fd91fc5bd66c994dcbe10771a22d91a1b3e))
- map search on iOS ([7d449b4](https://gitlab.com/openstapps/app/commit/7d449b43d0d86825f711848110ac6f044084eba0)), closes [#148](https://gitlab.com/openstapps/app/issues/148)
- map widget not loading tiles properly ([09aa7bb](https://gitlab.com/openstapps/app/commit/09aa7bb5c59e8d167fa91c69745f5f80229094d7)), closes [#127](https://gitlab.com/openstapps/app/issues/127)
- missing init (config setup) in default auth service ([f16e539](https://gitlab.com/openstapps/app/commit/f16e5394cce5a8019f4dfe367e5e0a2f0cca4ce2)), closes [#227](https://gitlab.com/openstapps/app/issues/227)
- missing translations ([30d801a](https://gitlab.com/openstapps/app/commit/30d801a3b419b382d40d96dda995bd35e493523d))
- modals not reacting after several uses ([f39c29f](https://gitlab.com/openstapps/app/commit/f39c29f10bc05ab986b74dfa8a8648df5fb307b4))
- news module not scrollable on large screens ([44b6a4a](https://gitlab.com/openstapps/app/commit/44b6a4aea008ca6c89f6cb289467429fb3f8c1fa))
- ngx-translate defaultLanguage not set ([581a5b2](https://gitlab.com/openstapps/app/commit/581a5b2e55ceda99cf7c41200366c3c5e09f1c63))
- omit sync native calendar when no uuids ([c9720dc](https://gitlab.com/openstapps/app/commit/c9720dc104cce78ae1a422d5efed7b8a58946836)), closes [#177](https://gitlab.com/openstapps/app/issues/177)
- opening hours pipe refreshing too often ([95e1734](https://gitlab.com/openstapps/app/commit/95e1734d26b175d8d1156abb10863155fed89ec0))
- overhaul auth to avoid issues ([99e8d6c](https://gitlab.com/openstapps/app/commit/99e8d6c9bcbc68b639b035af36bc05de0237b1f9)), closes [#336](https://gitlab.com/openstapps/app/issues/336)
- overview search bar scrolling behind header ([a037090](https://gitlab.com/openstapps/app/commit/a037090eec06e867a703b88a43620a74770287fe))
- performance degradation when scrolling ([f0a45d1](https://gitlab.com/openstapps/app/commit/f0a45d1b8eb2b33a6c68b94ed7f96f81db3a728b))
- person list should use long-inline-text ([8b2b853](https://gitlab.com/openstapps/app/commit/8b2b853942ac76904ff49d940dfef625b0397150))
- prevent multiple heavy setting inits ([f772637](https://gitlab.com/openstapps/app/commit/f7726378f443d9809a6174411d25a811e1d0b5e9))
- prevent opening invalid links ([fdee2db](https://gitlab.com/openstapps/app/commit/fdee2db8a42b8f6c99c4a72b3104ae0ba1a41c5a)), closes [#328](https://gitlab.com/openstapps/app/issues/328)
- profile module items show click effect on scroll ([8b2f2c0](https://gitlab.com/openstapps/app/commit/8b2f2c063c65278580d9469b00d9e67f01caaffb))
- recurring schedule offset and event limit ([9c6b513](https://gitlab.com/openstapps/app/commit/9c6b5131cd4cadb2572769d368b346054f19de1c))
- refresh token not used by default auth provider ([15ccbe4](https://gitlab.com/openstapps/app/commit/15ccbe4c1879417f2fc5849204fa9f6bdcce87fc)), closes [#311](https://gitlab.com/openstapps/app/issues/311)
- remove "extend landing" button when renewal not possible ([f60a228](https://gitlab.com/openstapps/app/commit/f60a22839200019a38586a14a0cce32e2c0029e7)), closes [#338](https://gitlab.com/openstapps/app/issues/338)
- remove item before adding it to secure storage ([ec511fb](https://gitlab.com/openstapps/app/commit/ec511fb8f40219e2559b08c62bd915d773d2a36f))
- schedule navigation bar layout ([e7d5f83](https://gitlab.com/openstapps/app/commit/e7d5f83100f43564b55249909a6658e583e3a9b2))
- set android status bar color correctly ([b38a969](https://gitlab.com/openstapps/app/commit/b38a96996a10e4e43ff1b06ecd2235a0e3bdfa1c))
- setting of default language ([ccf8b1a](https://gitlab.com/openstapps/app/commit/ccf8b1a5cc9fe834ba3e04a1ed67b2d082004403))
- show nothing for empty array properties ([1c56c89](https://gitlab.com/openstapps/app/commit/1c56c891e15b034826b5d6b2b35141fc872c922d)), closes [#154](https://gitlab.com/openstapps/app/issues/154)
- single map place width ([88684f0](https://gitlab.com/openstapps/app/commit/88684f068ab130f43e520c87f1d7383e0ae43944)), closes [#147](https://gitlab.com/openstapps/app/issues/147)
- some android devices don't support implied css animation units ([54cc883](https://gitlab.com/openstapps/app/commit/54cc8838aefe8f8c2d25d9228a136ef727a08230))
- status bar being black on Android 13 devices ([feee9e8](https://gitlab.com/openstapps/app/commit/feee9e8db90e66cf2346f7c5cc24f075eb70676c))
- suppress router event logs in console ([28caaf1](https://gitlab.com/openstapps/app/commit/28caaf1d21f7961b678cf339a712abf860ade5e7)), closes [#207](https://gitlab.com/openstapps/app/issues/207)
- swap missing icon after ionic update ([643b6c9](https://gitlab.com/openstapps/app/commit/643b6c967f3268cb305a24d614c3bc91275b0ac3))
- temporary disable flaky ui test ([6b9b1fa](https://gitlab.com/openstapps/app/commit/6b9b1fa8548d5c5fca04b2c1d63e893de39278a2))
- timetable dates cannot be removed ([9242438](https://gitlab.com/openstapps/app/commit/924243813207fa791d3c4938f8653a999b6382ff))
- translate back button title ([b8db0f3](https://gitlab.com/openstapps/app/commit/b8db0f3e70a46f2b493e183a244cb29d1954c257))
- translations ([5e1a902](https://gitlab.com/openstapps/app/commit/5e1a902d4c0d2345f21500fba5394c1907e04eb8))
- typo in translation ([7928534](https://gitlab.com/openstapps/app/commit/7928534d88a26db28b098bbceb47bc1103022a57))
- update core and apply stricter tslint rules ([911492d](https://gitlab.com/openstapps/app/commit/911492d064ff0280dd6626244cd8038cbfc0f408))
- use HashLocationStrategy for routes ([9d68212](https://gitlab.com/openstapps/app/commit/9d682125db55c87cab2b33c7633bfa133d2fc5a9)), closes [#54](https://gitlab.com/openstapps/app/issues/54)
- use localized date and time ([6ca0b97](https://gitlab.com/openstapps/app/commit/6ca0b9763761c5204a757a243056a087c5f35fd9))
- use stapps core version to compare with backends' core version ([66b8720](https://gitlab.com/openstapps/app/commit/66b8720da0f264824a396f2d9e598b0e48c8e3d1)), closes [#77](https://gitlab.com/openstapps/app/issues/77)
- user info card ([998edcb](https://gitlab.com/openstapps/app/commit/998edcb5cdfb588c2986f466f4a2951f172a8bb4)), closes [#305](https://gitlab.com/openstapps/app/issues/305)
### Features
- add "no results" screen to search ([c75ca68](https://gitlab.com/openstapps/app/commit/c75ca68c440a20e197213ecbb47d05fc293afd9c))
- add about module ([d420008](https://gitlab.com/openstapps/app/commit/d42000892694f4a3b29fa623c43fb45f8ba54687))
- add action chips to search results ([67fb4a4](https://gitlab.com/openstapps/app/commit/67fb4a43c95043caba50d43ace2ab276f3319b81))
- add auth support (default and paia) ([b5f239e](https://gitlab.com/openstapps/app/commit/b5f239ea4edebd0d27b1cdaad4a830998ce2f681))
- add backend toggle ([c1d3330](https://gitlab.com/openstapps/app/commit/c1d33303aa11da3b3e150c6717d77ef484a16ac1))
- add basic templates for data list items ([3e697b1](https://gitlab.com/openstapps/app/commit/3e697b17b4448c15781d0f6dd55577b638e9d974))
- add catalog module ([03084b1](https://gitlab.com/openstapps/app/commit/03084b1c966de98b3723d0bee2b2475589393c59))
- add ConfigModule and FakeBackendInterceptor ([406f400](https://gitlab.com/openstapps/app/commit/406f40055567bfde4ec5edf26cff942411bd073e)), closes [#34](https://gitlab.com/openstapps/app/issues/34) [#37](https://gitlab.com/openstapps/app/issues/37)
- add detail view for news ([2566a71](https://gitlab.com/openstapps/app/commit/2566a71a81a3408dbb16b97d3a453d95e25d1f00))
- add duration pipe with frequency capabilites ([49a1758](https://gitlab.com/openstapps/app/commit/49a1758da358958ffe590700c19aaf90ec748ee5))
- add favorites support ([e9452d6](https://gitlab.com/openstapps/app/commit/e9452d6520c34f6513623c16e291dc23d6ea9757))
- add feedback module ([867f9e9](https://gitlab.com/openstapps/app/commit/867f9e9b832e3bd54c04801fef63a11543e8f3dd))
- add filter chips for news ([5435f85](https://gitlab.com/openstapps/app/commit/5435f85cc43dc3baa774a5008d2920ac7b3783f6))
- add formatting pipes basted on Intl ([4b932af](https://gitlab.com/openstapps/app/commit/4b932af1c07e1af4369414667a987d31181c073c))
- add HeBIS HDS search ([9a3241c](https://gitlab.com/openstapps/app/commit/9a3241c42ab59e15c0084178f76dc4a2450a2bb8))
- add library account screens ([080e6fa](https://gitlab.com/openstapps/app/commit/080e6fa3e8c18e9608d7fa2efc95e4fd65c43a15))
- add library action confirmations ([42b860e](https://gitlab.com/openstapps/app/commit/42b860e41793fc3983a39237a4f7128416485fae)), closes [#334](https://gitlab.com/openstapps/app/issues/334)
- add logger ([a0c798f](https://gitlab.com/openstapps/app/commit/a0c798f765d87c5eebcbed65b70f3b05f285d0ce))
- add map module ([c1c9a92](https://gitlab.com/openstapps/app/commit/c1c9a92ec900403218b887fdebfe5132b232e1e0))
- add new font and new icons ([915fd72](https://gitlab.com/openstapps/app/commit/915fd72bd4bfed16e15fcc3c57879db0ec0379e2))
- add not found screen ([e3d9ef4](https://gitlab.com/openstapps/app/commit/e3d9ef40ccd626c81c67ea2d790eecbe6e025780))
- add permission check for geoLocation setting ([d5fa2fd](https://gitlab.com/openstapps/app/commit/d5fa2fd9a578d48cd2513eeb1380f1d2bc4d3754))
- add service and pipe for core translator ([4565600](https://gitlab.com/openstapps/app/commit/456560026cc9550a10a9f42657d942122be34d53))
- apply new layout overhaul ([7bbdba5](https://gitlab.com/openstapps/app/commit/7bbdba5c0b886e2789d2a603c4be627dfd16b60e))
- assessment tree view ([0b037f9](https://gitlab.com/openstapps/app/commit/0b037f96e634b412fbaaee24747df08afdc0e565))
- assessments module ([e68d1b7](https://gitlab.com/openstapps/app/commit/e68d1b73f94b36abcefe9b2bf42e98de00b69188))
- calendar plugin ([a57c302](https://gitlab.com/openstapps/app/commit/a57c3029df61ac3157c856744380a85dc001abc6))
- dashboard ui tests ([9f8ab5c](https://gitlab.com/openstapps/app/commit/9f8ab5c7a15a918f7bd05423f0a43f22a33d9228))
- **data:** add basic methods of data provider ([ffe05e4](https://gitlab.com/openstapps/app/commit/ffe05e4548fc399183ef651047cb02a3cdc80c67)), closes [#1](https://gitlab.com/openstapps/app/issues/1)
- **data:** add data detail templates ([5855acc](https://gitlab.com/openstapps/app/commit/5855accc169579d87f5779fd602d4f00f2b479a1))
- **data:** add method that checks if data item has been saved ([017fc67](https://gitlab.com/openstapps/app/commit/017fc67765bdb75542045830fb85f8eb4b899485))
- **data:** implement basic facets handling ([b6f92a7](https://gitlab.com/openstapps/app/commit/b6f92a74494e1a39d5c828d593eb70c7002bb0f6)), closes [#1](https://gitlab.com/openstapps/app/issues/1)
- **data:** show skeleton screens before data is loaded ([e1039aa](https://gitlab.com/openstapps/app/commit/e1039aa2260a0e9d8ccca5a18ded480bf2f12530)), closes [#4](https://gitlab.com/openstapps/app/issues/4)
- **data:** use data provider for list and detail view ([7caaa18](https://gitlab.com/openstapps/app/commit/7caaa18b7eb94ed8e4ff4e54d760ba1dba3ae3be))
- **data:** use general template for all offers ([58960a2](https://gitlab.com/openstapps/app/commit/58960a29ea0cd73f26a43186b41f5b53864810e0))
- display availability and item data for library items ([d571b1d](https://gitlab.com/openstapps/app/commit/d571b1dbe59f8e2270d88dd050b94283bf0a7056))
- dynamic news page rows ([848d257](https://gitlab.com/openstapps/app/commit/848d2574c7046d6f84b91e32aa83ca57fca8ad2e))
- expandable accordion in grades module ([3f81afd](https://gitlab.com/openstapps/app/commit/3f81afda82cf87bb393fbb3b71d27eee77ae42d9))
- get tab navigation items from config ([c3130a3](https://gitlab.com/openstapps/app/commit/c3130a392a53c9ec3657e24a53ed0b3333ba162b))
- Implement variable for styling ([8ecf347](https://gitlab.com/openstapps/app/commit/8ecf347c9a8abac9347dca45e99ebe35eca8f252))
- include font licenses in the licenses section ([82479f4](https://gitlab.com/openstapps/app/commit/82479f463cbee834507a0c8faf895a729631eb06))
- Ionic v6 breadcrumbs in catalog module ([7b491ed](https://gitlab.com/openstapps/app/commit/7b491ed3bb5466a845493bd8ea0bbb836a4f03d2))
- lazy load all news ([e48134e](https://gitlab.com/openstapps/app/commit/e48134eddcd1ca4d5ec5dbf910571e33a3311ba1))
- **menu:** add context menu ([1dbf451](https://gitlab.com/openstapps/app/commit/1dbf4515fe57cc8250a7fa2213ced682e3e5e0fc)), closes [#3](https://gitlab.com/openstapps/app/issues/3)
- navigation rail ([6b08af6](https://gitlab.com/openstapps/app/commit/6b08af6a746bf12005d3297ec97c130e84477615))
- news module ([22cd0af](https://gitlab.com/openstapps/app/commit/22cd0af1bf92a4576316f5510c22f012e085a237))
- profile page sections ([e395e9d](https://gitlab.com/openstapps/app/commit/e395e9d270f41bd4f6e5ecd88e438a28dde92465)), closes [#233](https://gitlab.com/openstapps/app/issues/233) [#261](https://gitlab.com/openstapps/app/issues/261) [#267](https://gitlab.com/openstapps/app/issues/267)
- refresh on pull for news module ([1f3d9ad](https://gitlab.com/openstapps/app/commit/1f3d9ad5f0c0557add2dcf242cf0b7dd7685bb1b))
- resume at origin path after login ([a5e5a5b](https://gitlab.com/openstapps/app/commit/a5e5a5b40799e7505557f7ebd764b9c563be0f4b)), closes [#168](https://gitlab.com/openstapps/app/issues/168)
- schedule layout ([e416590](https://gitlab.com/openstapps/app/commit/e4165901bb5efa6b38e397cdf5d66138e396d7f2))
- scroll schedule cursor into view ([bc4c3d7](https://gitlab.com/openstapps/app/commit/bc4c3d78dbd906243dcddac4db9ac8ccaca79056))
- search url query param handling ([f349bd7](https://gitlab.com/openstapps/app/commit/f349bd72335c47d292d0a007b1a4ce7f5c694a61))
- seperate dishes by menu sections ([400c6b8](https://gitlab.com/openstapps/app/commit/400c6b8d8c5300035862186096e38883f781d297))
- show availability in offers ([5fdef95](https://gitlab.com/openstapps/app/commit/5fdef95c065e7b467a13045adbb56a641cb2dc12))
- show feedback when map search yields no results ([c54ea86](https://gitlab.com/openstapps/app/commit/c54ea867bd64af187446dbf8dc03967aae600961))
- show menu for multiple days for canteens and cafes ([3c079cd](https://gitlab.com/openstapps/app/commit/3c079cd189e3b75d3b203bd46ab57378f99e88cc)), closes [#19](https://gitlab.com/openstapps/app/issues/19) [#79](https://gitlab.com/openstapps/app/issues/79)
- **storage:** add search using regex ([86b9bff](https://gitlab.com/openstapps/app/commit/86b9bff09a51b17151efa5ec322cddcc50a54cb2))
- **storage:** support deletion of multiple entries ([63266f5](https://gitlab.com/openstapps/app/commit/63266f588f6ddb2476e2cea4bab42a3864f77470))
- **storage:** support search using a string ([4334cad](https://gitlab.com/openstapps/app/commit/4334cad68c7d0abb7443e245b6eb983804547925))
- support deep links ([2e3f668](https://gitlab.com/openstapps/app/commit/2e3f6684ef5fbac8e4fb127c536b2b438399ce37))
- support overlapping timetable dates ([93c37b2](https://gitlab.com/openstapps/app/commit/93c37b26cca7764dea66fb12c0e53acc8fd78d2b))
- tab navigation bar animations and state ([7ecba0b](https://gitlab.com/openstapps/app/commit/7ecba0b7819ae5a7ab32d86f6049de0ab6c68e55))
- timetable module - schedule and calendar ([d8ede00](https://gitlab.com/openstapps/app/commit/d8ede006dfbd613dfbc752eb6de80db2e7e84531))
- turn on oauth2 state check for PAIA ([5bd0b50](https://gitlab.com/openstapps/app/commit/5bd0b50816973548a0a4aa5dbed3f2d0902590dd)), closes [#172](https://gitlab.com/openstapps/app/issues/172)
- use http interceptor for backendless development ([2558163](https://gitlab.com/openstapps/app/commit/2558163ad6c3038445a79e6338f1827cb1e6510e)), closes [#37](https://gitlab.com/openstapps/app/issues/37)
- use school-neutral news image fallback via css ([bb94c71](https://gitlab.com/openstapps/app/commit/bb94c71761ca5d3bf6639476ba0c143678cfabbd))
- webpack bundle analyzer ([552911c](https://gitlab.com/openstapps/app/commit/552911cfcfecec8d9be8b8bbb155b597d1f70fa2))
## 0.0.1
### Features
- add the app ([8b23159](https://gitlab.com/openstapps/app/commit/8b23159e678773b08252bc5826510de2a302fa1d))

View File

@@ -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 + <script-name>`. So we have the following commands available: 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 + <script-name>`. So we have the following commands available:
``` ```sh
npm run docker:pull 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. 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 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. 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 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. 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 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? ## How to build and start the app using the default backend?
``` ```sh
npm run build npm run build
npm run start npm run start
``` ```
@@ -86,52 +86,98 @@ addToIonicDB(
You'll need to run _Chromium_ using You'll need to run _Chromium_ using
```shell ```sh
pnpm chromium:no-cors pnpm chromium:no-cors
``` ```
### Help, I can't log in! ### Help, I can't log in!
Login services will often block hosts not coming from the production Login services will often block hosts not coming from the production
server. You can circumvent this locally by using the `:virtual-host` server.
scripts:
```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 # Start the dev server on mobile.app.uni-frankfurt.de
pnpm start:virtual-host pnpm start:virtual-host
# Run chromium with flags that redirect mobile.app.uni-frankfurt.de to localhost:8100 # Run chromium with flags that redirect mobile.app.uni-frankfurt.de to localhost:8100
pnpm chromium:virtual-host 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
```
**This alone will not make auth work**, only the login flow.
If you need to test login, you have to disable live reload:
```sh
pnpm ionic capacitor run android
```
_**CAUTION:** a remote chrome debugging session can in some
cases hijack the device's ADB connection. If the connection
fails for no obvious reason, close chrome and uninstall the
app, then try again._
#### 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 ### Running the app
Install the npm packages needed for running the app (as for any other node project which uses npm): Install the npm packages needed for running the app (as for any other node project which uses npm):
``` ```sh
npm install npm install
``` ```
Check the code for linter issues: Check the code for linter issues:
``` ```sh
npm run lint npm run lint
``` ```
Automatically fix linter issues (those where autofix is possible): Automatically fix linter issues (those where autofix is possible):
``` ```sh
npm run lint:fix npm run lint:fix
``` ```
Build the app (transpile etc.): Build the app (transpile etc.):
``` ```sh
npm run build npm run build
``` ```
Open the app in the browser: Open the app in the browser:
``` ```sh
ionic serve ionic serve
``` ```
@@ -139,7 +185,7 @@ ionic serve
Run the app for testing on an android device (with live reload in the webview / device, when files are changed): 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 build # if needed
npm run resources:android # generate needed resources (icons and splashscreens) 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 npm run docker:run:android # runs "ionic capacitor run android --livereload --external" on a selected device
@@ -159,7 +205,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): 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 npm run docker:build:android
``` ```
@@ -169,13 +215,13 @@ The mentioned `docker:*:android` npm commands are executed in a docker container
Execute unit tests: Execute unit tests:
``` ```sh
npm test npm test
``` ```
Execute e2e tests: Execute e2e tests:
``` ```sh
npm run e2e npm run e2e
``` ```

View File

@@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
android { android {
namespace "de.anyschool.app" namespace "de.anyschool.app"
compileSdkVersion rootProject.ext.compileSdkVersion compileSdk rootProject.ext.compileSdkVersion
defaultConfig { defaultConfig {
applicationId "de.anyschool.app" applicationId "de.anyschool.app"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion

View File

@@ -9,6 +9,7 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies { dependencies {
implementation project(':capacitor-community-screen-brightness')
implementation project(':capacitor-app') implementation project(':capacitor-app')
implementation project(':capacitor-browser') implementation project(':capacitor-browser')
implementation project(':capacitor-clipboard') implementation project(':capacitor-clipboard')
@@ -21,6 +22,7 @@ dependencies {
implementation project(':capacitor-local-notifications') implementation project(':capacitor-local-notifications')
implementation project(':capacitor-network') implementation project(':capacitor-network')
implementation project(':capacitor-preferences') implementation project(':capacitor-preferences')
implementation project(':capacitor-screen-orientation')
implementation project(':capacitor-share') implementation project(':capacitor-share')
implementation project(':capacitor-splash-screen') implementation project(':capacitor-splash-screen')
implementation project(':transistorsoft-capacitor-background-fetch') implementation project(':transistorsoft-capacitor-background-fetch')

View File

@@ -1,4 +1,8 @@
[ [
{
"pkg": "@capacitor-community/screen-brightness",
"classpath": "com.elylucas.capscreenbrightness.ScreenBrightnessPlugin"
},
{ {
"pkg": "@capacitor/app", "pkg": "@capacitor/app",
"classpath": "com.capacitorjs.plugins.app.AppPlugin" "classpath": "com.capacitorjs.plugins.app.AppPlugin"
@@ -47,6 +51,10 @@
"pkg": "@capacitor/preferences", "pkg": "@capacitor/preferences",
"classpath": "com.capacitorjs.plugins.preferences.PreferencesPlugin" "classpath": "com.capacitorjs.plugins.preferences.PreferencesPlugin"
}, },
{
"pkg": "@capacitor/screen-orientation",
"classpath": "com.capacitorjs.plugins.screenorientation.ScreenOrientationPlugin"
},
{ {
"pkg": "@capacitor/share", "pkg": "@capacitor/share",
"classpath": "com.capacitorjs.plugins.share.SharePlugin" "classpath": "com.capacitorjs.plugins.share.SharePlugin"

View File

@@ -7,8 +7,8 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.0.0' classpath 'com.android.tools.build:gradle:8.2.1'
classpath 'com.google.gms:google-services:4.3.15' classpath 'com.google.gms:google-services:4.4.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

View File

@@ -1,51 +1,57 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android' 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@6.1.1_@capacitor+core@6.1.1/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@6.1.1/node_modules/@capacitor-community/screen-brightness/android')
include ':capacitor-app' 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@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/app/android')
include ':capacitor-browser' 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@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/browser/android')
include ':capacitor-clipboard' 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@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/clipboard/android')
include ':capacitor-device' 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@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/device/android')
include ':capacitor-dialog' 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@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/dialog/android')
include ':capacitor-filesystem' 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@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/filesystem/android')
include ':capacitor-geolocation' 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@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/geolocation/android')
include ':capacitor-haptics' 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@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/haptics/android')
include ':capacitor-keyboard' 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@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/keyboard/android')
include ':capacitor-local-notifications' 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@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/local-notifications/android')
include ':capacitor-network' 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@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/network/android')
include ':capacitor-preferences' 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@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/preferences/android')
include ':capacitor-screen-orientation'
project(':capacitor-screen-orientation').projectDir = new File('../../../node_modules/.pnpm/@capacitor+screen-orientation@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/screen-orientation/android')
include ':capacitor-share' 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@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/share/android')
include ':capacitor-splash-screen' 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@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/splash-screen/android')
include ':transistorsoft-capacitor-background-fetch' 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@6.0.0_@capacitor+core@6.0.0/node_modules/@transistorsoft/capacitor-background-fetch/android')
include ':capacitor-secure-storage-plugin' 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.10.0_@capacitor+core@6.0.0/node_modules/capacitor-secure-storage-plugin/android')

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip
networkTimeout=10000 networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -1,14 +1,14 @@
ext { ext {
minSdkVersion = 22 minSdkVersion = 22
compileSdkVersion = 33 compileSdkVersion = 34
targetSdkVersion = 33 targetSdkVersion = 34
androidxActivityVersion = '1.7.0' androidxActivityVersion = '1.8.0'
androidxAppCompatVersion = '1.6.1' androidxAppCompatVersion = '1.6.1'
androidxCoordinatorLayoutVersion = '1.2.0' androidxCoordinatorLayoutVersion = '1.2.0'
androidxCoreVersion = '1.10.0' androidxCoreVersion = '1.12.0'
androidxFragmentVersion = '1.5.6' androidxFragmentVersion = '1.6.2'
coreSplashScreenVersion = '1.0.0' coreSplashScreenVersion = '1.0.1'
androidxWebkitVersion = '1.6.1' androidxWebkitVersion = '1.9.0'
junitVersion = '4.13.2' junitVersion = '4.13.2'
androidxJunitVersion = '1.1.5' androidxJunitVersion = '1.1.5'
androidxEspressoCoreVersion = '3.5.1' androidxEspressoCoreVersion = '3.5.1'

View File

@@ -35,6 +35,11 @@
"glob": "**/*", "glob": "**/*",
"input": "src/assets", "input": "src/assets",
"output": "assets" "output": "assets"
},
{
"glob": "CHANGELOG.md",
"input": ".",
"output": "assets"
} }
], ],
"styles": [ "styles": [

View File

@@ -1,11 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
rm coverage/integration-report-junit-*.xml rm coverage/integration-report-junit-*.xml || true
ng e2e --watch=false --headless=true --browser="$BROWSER" ng e2e --watch=false --headless=true --browser="$BROWSER"
exit_code=$? exit_code=$?
jrm coverage/integration-report-junit.xml coverage/integration-report-junit-*.xml jrm coverage/integration-report-junit.xml coverage/integration-report-junit-*.xml || true
rm coverage/integration-report-junit-*.xml rm coverage/integration-report-junit-*.xml || true
exit $exit_code exit $exit_code

View File

@@ -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@6.1.1_@capacitor+core@6.1.1/node_modules/@capacitor/ios/scripts/pods_helpers'
platform :ios, '13.0' platform :ios, '13.0'
use_frameworks! use_frameworks!
@@ -9,24 +9,26 @@ use_frameworks!
install! 'cocoapods', :disable_input_output_paths => true install! 'cocoapods', :disable_input_output_paths => true
def capacitor_pods def capacitor_pods
pod 'Capacitor', :path => '../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios' pod 'Capacitor', :path => '../../../../node_modules/.pnpm/@capacitor+ios@6.1.1_@capacitor+core@6.1.1/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 'CapacitorCordova', :path => '../../../../node_modules/.pnpm/@capacitor+ios@6.1.1_@capacitor+core@6.1.1/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 'CapacitorCommunityScreenBrightness', :path => '../../../../node_modules/.pnpm/@capacitor-community+screen-brightness@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor-community/screen-brightness'
pod 'CapacitorBrowser', :path => '../../../../node_modules/.pnpm/@capacitor+browser@5.1.0_@capacitor+core@5.5.0/node_modules/@capacitor/browser' pod 'CapacitorApp', :path => '../../../../node_modules/.pnpm/@capacitor+app@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/app'
pod 'CapacitorClipboard', :path => '../../../../node_modules/.pnpm/@capacitor+clipboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/clipboard' pod 'CapacitorBrowser', :path => '../../../../node_modules/.pnpm/@capacitor+browser@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/browser'
pod 'CapacitorDevice', :path => '../../../../node_modules/.pnpm/@capacitor+device@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/device' pod 'CapacitorClipboard', :path => '../../../../node_modules/.pnpm/@capacitor+clipboard@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/clipboard'
pod 'CapacitorDialog', :path => '../../../../node_modules/.pnpm/@capacitor+dialog@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/dialog' pod 'CapacitorDevice', :path => '../../../../node_modules/.pnpm/@capacitor+device@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/device'
pod 'CapacitorFilesystem', :path => '../../../../node_modules/.pnpm/@capacitor+filesystem@5.1.4_@capacitor+core@5.5.0/node_modules/@capacitor/filesystem' pod 'CapacitorDialog', :path => '../../../../node_modules/.pnpm/@capacitor+dialog@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/dialog'
pod 'CapacitorGeolocation', :path => '../../../../node_modules/.pnpm/@capacitor+geolocation@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/geolocation' pod 'CapacitorFilesystem', :path => '../../../../node_modules/.pnpm/@capacitor+filesystem@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/filesystem'
pod 'CapacitorHaptics', :path => '../../../../node_modules/.pnpm/@capacitor+haptics@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/haptics' pod 'CapacitorGeolocation', :path => '../../../../node_modules/.pnpm/@capacitor+geolocation@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/geolocation'
pod 'CapacitorKeyboard', :path => '../../../../node_modules/.pnpm/@capacitor+keyboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/keyboard' pod 'CapacitorHaptics', :path => '../../../../node_modules/.pnpm/@capacitor+haptics@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/haptics'
pod 'CapacitorLocalNotifications', :path => '../../../../node_modules/.pnpm/@capacitor+local-notifications@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/local-notifications' pod 'CapacitorKeyboard', :path => '../../../../node_modules/.pnpm/@capacitor+keyboard@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/keyboard'
pod 'CapacitorNetwork', :path => '../../../../node_modules/.pnpm/@capacitor+network@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/network' pod 'CapacitorLocalNotifications', :path => '../../../../node_modules/.pnpm/@capacitor+local-notifications@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/local-notifications'
pod 'CapacitorPreferences', :path => '../../../../node_modules/.pnpm/@capacitor+preferences@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/preferences' pod 'CapacitorNetwork', :path => '../../../../node_modules/.pnpm/@capacitor+network@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/network'
pod 'CapacitorShare', :path => '../../../../node_modules/.pnpm/@capacitor+share@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/share' pod 'CapacitorPreferences', :path => '../../../../node_modules/.pnpm/@capacitor+preferences@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/preferences'
pod 'CapacitorSplashScreen', :path => '../../../../node_modules/.pnpm/@capacitor+splash-screen@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/splash-screen' pod 'CapacitorScreenOrientation', :path => '../../../../node_modules/.pnpm/@capacitor+screen-orientation@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/screen-orientation'
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 'CapacitorShare', :path => '../../../../node_modules/.pnpm/@capacitor+share@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/share'
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 'CapacitorSplashScreen', :path => '../../../../node_modules/.pnpm/@capacitor+splash-screen@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/splash-screen'
pod 'TransistorsoftCapacitorBackgroundFetch', :path => '../../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.2.0_@capacitor+core@6.1.1/node_modules/@transistorsoft/capacitor-background-fetch'
pod 'CapacitorSecureStoragePlugin', :path => '../../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@6.1.1/node_modules/capacitor-secure-storage-plugin'
pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins' pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'
end end

View File

@@ -1,64 +1,70 @@
PODS: PODS:
- Capacitor (5.5.0): - Capacitor (6.1.1):
- CapacitorCordova - CapacitorCordova
- CapacitorApp (5.0.6): - CapacitorApp (6.0.0):
- Capacitor - Capacitor
- CapacitorBrowser (5.1.0): - CapacitorBrowser (6.0.1):
- Capacitor - Capacitor
- CapacitorClipboard (5.0.6): - CapacitorClipboard (6.0.0):
- Capacitor - Capacitor
- CapacitorCordova (5.5.0) - CapacitorCommunityScreenBrightness (6.0.0):
- CapacitorDevice (5.0.6):
- Capacitor - Capacitor
- CapacitorDialog (5.0.6): - CapacitorCordova (6.1.1)
- CapacitorDevice (6.0.0):
- Capacitor - Capacitor
- CapacitorFilesystem (5.1.4): - CapacitorDialog (6.0.0):
- Capacitor - Capacitor
- CapacitorGeolocation (5.0.6): - CapacitorFilesystem (6.0.0):
- Capacitor - Capacitor
- CapacitorHaptics (5.0.6): - CapacitorGeolocation (6.0.0):
- Capacitor - Capacitor
- CapacitorKeyboard (5.0.6): - CapacitorHaptics (6.0.0):
- Capacitor - Capacitor
- CapacitorLocalNotifications (5.0.6): - CapacitorKeyboard (6.0.1):
- Capacitor - Capacitor
- CapacitorNetwork (5.0.6): - CapacitorLocalNotifications (6.0.0):
- Capacitor - Capacitor
- CapacitorPreferences (5.0.6): - CapacitorNetwork (6.0.1):
- Capacitor
- CapacitorPreferences (6.0.1):
- Capacitor
- CapacitorScreenOrientation (6.0.1):
- Capacitor - Capacitor
- CapacitorSecureStoragePlugin (0.9.0): - CapacitorSecureStoragePlugin (0.9.0):
- Capacitor - Capacitor
- SwiftKeychainWrapper - SwiftKeychainWrapper
- CapacitorShare (5.0.6): - CapacitorShare (6.0.1):
- Capacitor - Capacitor
- CapacitorSplashScreen (5.0.6): - CapacitorSplashScreen (6.0.1):
- Capacitor - Capacitor
- CordovaPlugins (5.5.0): - CordovaPlugins (6.1.1):
- CapacitorCordova - CapacitorCordova
- SwiftKeychainWrapper (4.0.1) - SwiftKeychainWrapper (4.0.1)
- TransistorsoftCapacitorBackgroundFetch (5.1.1): - TransistorsoftCapacitorBackgroundFetch (5.2.0):
- Capacitor - Capacitor
DEPENDENCIES: DEPENDENCIES:
- "Capacitor (from `../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios`)" - "Capacitor (from `../../../../node_modules/.pnpm/@capacitor+ios@6.1.1_@capacitor+core@6.1.1/node_modules/@capacitor/ios`)"
- "CapacitorApp (from `../../../../node_modules/.pnpm/@capacitor+app@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/app`)" - "CapacitorApp (from `../../../../node_modules/.pnpm/@capacitor+app@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/app`)"
- "CapacitorBrowser (from `../../../../node_modules/.pnpm/@capacitor+browser@5.1.0_@capacitor+core@5.5.0/node_modules/@capacitor/browser`)" - "CapacitorBrowser (from `../../../../node_modules/.pnpm/@capacitor+browser@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/browser`)"
- "CapacitorClipboard (from `../../../../node_modules/.pnpm/@capacitor+clipboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/clipboard`)" - "CapacitorClipboard (from `../../../../node_modules/.pnpm/@capacitor+clipboard@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/clipboard`)"
- "CapacitorCordova (from `../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios`)" - "CapacitorCommunityScreenBrightness (from `../../../../node_modules/.pnpm/@capacitor-community+screen-brightness@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor-community/screen-brightness`)"
- "CapacitorDevice (from `../../../../node_modules/.pnpm/@capacitor+device@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/device`)" - "CapacitorCordova (from `../../../../node_modules/.pnpm/@capacitor+ios@6.1.1_@capacitor+core@6.1.1/node_modules/@capacitor/ios`)"
- "CapacitorDialog (from `../../../../node_modules/.pnpm/@capacitor+dialog@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/dialog`)" - "CapacitorDevice (from `../../../../node_modules/.pnpm/@capacitor+device@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/device`)"
- "CapacitorFilesystem (from `../../../../node_modules/.pnpm/@capacitor+filesystem@5.1.4_@capacitor+core@5.5.0/node_modules/@capacitor/filesystem`)" - "CapacitorDialog (from `../../../../node_modules/.pnpm/@capacitor+dialog@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/dialog`)"
- "CapacitorGeolocation (from `../../../../node_modules/.pnpm/@capacitor+geolocation@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/geolocation`)" - "CapacitorFilesystem (from `../../../../node_modules/.pnpm/@capacitor+filesystem@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/filesystem`)"
- "CapacitorHaptics (from `../../../../node_modules/.pnpm/@capacitor+haptics@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/haptics`)" - "CapacitorGeolocation (from `../../../../node_modules/.pnpm/@capacitor+geolocation@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/geolocation`)"
- "CapacitorKeyboard (from `../../../../node_modules/.pnpm/@capacitor+keyboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/keyboard`)" - "CapacitorHaptics (from `../../../../node_modules/.pnpm/@capacitor+haptics@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/haptics`)"
- "CapacitorLocalNotifications (from `../../../../node_modules/.pnpm/@capacitor+local-notifications@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/local-notifications`)" - "CapacitorKeyboard (from `../../../../node_modules/.pnpm/@capacitor+keyboard@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/keyboard`)"
- "CapacitorNetwork (from `../../../../node_modules/.pnpm/@capacitor+network@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/network`)" - "CapacitorLocalNotifications (from `../../../../node_modules/.pnpm/@capacitor+local-notifications@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/local-notifications`)"
- "CapacitorPreferences (from `../../../../node_modules/.pnpm/@capacitor+preferences@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/preferences`)" - "CapacitorNetwork (from `../../../../node_modules/.pnpm/@capacitor+network@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/network`)"
- "CapacitorSecureStoragePlugin (from `../../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.5.0/node_modules/capacitor-secure-storage-plugin`)" - "CapacitorPreferences (from `../../../../node_modules/.pnpm/@capacitor+preferences@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/preferences`)"
- "CapacitorShare (from `../../../../node_modules/.pnpm/@capacitor+share@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/share`)" - "CapacitorScreenOrientation (from `../../../../node_modules/.pnpm/@capacitor+screen-orientation@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/screen-orientation`)"
- "CapacitorSplashScreen (from `../../../../node_modules/.pnpm/@capacitor+splash-screen@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/splash-screen`)" - "CapacitorSecureStoragePlugin (from `../../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@6.1.1/node_modules/capacitor-secure-storage-plugin`)"
- "CapacitorShare (from `../../../../node_modules/.pnpm/@capacitor+share@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/share`)"
- "CapacitorSplashScreen (from `../../../../node_modules/.pnpm/@capacitor+splash-screen@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/splash-screen`)"
- CordovaPlugins (from `../capacitor-cordova-ios-plugins`) - CordovaPlugins (from `../capacitor-cordova-ios-plugins`)
- "TransistorsoftCapacitorBackgroundFetch (from `../../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.1.1_@capacitor+core@5.5.0/node_modules/@transistorsoft/capacitor-background-fetch`)" - "TransistorsoftCapacitorBackgroundFetch (from `../../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.2.0_@capacitor+core@6.1.1/node_modules/@transistorsoft/capacitor-background-fetch`)"
SPEC REPOS: SPEC REPOS:
trunk: trunk:
@@ -66,66 +72,72 @@ SPEC REPOS:
EXTERNAL SOURCES: EXTERNAL SOURCES:
Capacitor: Capacitor:
:path: "../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios" :path: "../../../../node_modules/.pnpm/@capacitor+ios@6.1.1_@capacitor+core@6.1.1/node_modules/@capacitor/ios"
CapacitorApp: CapacitorApp:
:path: "../../../../node_modules/.pnpm/@capacitor+app@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/app" :path: "../../../../node_modules/.pnpm/@capacitor+app@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/app"
CapacitorBrowser: CapacitorBrowser:
:path: "../../../../node_modules/.pnpm/@capacitor+browser@5.1.0_@capacitor+core@5.5.0/node_modules/@capacitor/browser" :path: "../../../../node_modules/.pnpm/@capacitor+browser@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/browser"
CapacitorClipboard: CapacitorClipboard:
:path: "../../../../node_modules/.pnpm/@capacitor+clipboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/clipboard" :path: "../../../../node_modules/.pnpm/@capacitor+clipboard@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/clipboard"
CapacitorCommunityScreenBrightness:
:path: "../../../../node_modules/.pnpm/@capacitor-community+screen-brightness@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor-community/screen-brightness"
CapacitorCordova: CapacitorCordova:
:path: "../../../../node_modules/.pnpm/@capacitor+ios@5.5.0_@capacitor+core@5.5.0/node_modules/@capacitor/ios" :path: "../../../../node_modules/.pnpm/@capacitor+ios@6.1.1_@capacitor+core@6.1.1/node_modules/@capacitor/ios"
CapacitorDevice: CapacitorDevice:
:path: "../../../../node_modules/.pnpm/@capacitor+device@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/device" :path: "../../../../node_modules/.pnpm/@capacitor+device@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/device"
CapacitorDialog: CapacitorDialog:
:path: "../../../../node_modules/.pnpm/@capacitor+dialog@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/dialog" :path: "../../../../node_modules/.pnpm/@capacitor+dialog@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/dialog"
CapacitorFilesystem: CapacitorFilesystem:
:path: "../../../../node_modules/.pnpm/@capacitor+filesystem@5.1.4_@capacitor+core@5.5.0/node_modules/@capacitor/filesystem" :path: "../../../../node_modules/.pnpm/@capacitor+filesystem@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/filesystem"
CapacitorGeolocation: CapacitorGeolocation:
:path: "../../../../node_modules/.pnpm/@capacitor+geolocation@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/geolocation" :path: "../../../../node_modules/.pnpm/@capacitor+geolocation@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/geolocation"
CapacitorHaptics: CapacitorHaptics:
:path: "../../../../node_modules/.pnpm/@capacitor+haptics@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/haptics" :path: "../../../../node_modules/.pnpm/@capacitor+haptics@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/haptics"
CapacitorKeyboard: CapacitorKeyboard:
:path: "../../../../node_modules/.pnpm/@capacitor+keyboard@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/keyboard" :path: "../../../../node_modules/.pnpm/@capacitor+keyboard@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/keyboard"
CapacitorLocalNotifications: CapacitorLocalNotifications:
:path: "../../../../node_modules/.pnpm/@capacitor+local-notifications@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/local-notifications" :path: "../../../../node_modules/.pnpm/@capacitor+local-notifications@6.0.0_@capacitor+core@6.1.1/node_modules/@capacitor/local-notifications"
CapacitorNetwork: CapacitorNetwork:
:path: "../../../../node_modules/.pnpm/@capacitor+network@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/network" :path: "../../../../node_modules/.pnpm/@capacitor+network@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/network"
CapacitorPreferences: CapacitorPreferences:
:path: "../../../../node_modules/.pnpm/@capacitor+preferences@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/preferences" :path: "../../../../node_modules/.pnpm/@capacitor+preferences@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/preferences"
CapacitorScreenOrientation:
:path: "../../../../node_modules/.pnpm/@capacitor+screen-orientation@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/screen-orientation"
CapacitorSecureStoragePlugin: CapacitorSecureStoragePlugin:
:path: "../../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.5.0/node_modules/capacitor-secure-storage-plugin" :path: "../../../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@6.1.1/node_modules/capacitor-secure-storage-plugin"
CapacitorShare: CapacitorShare:
:path: "../../../../node_modules/.pnpm/@capacitor+share@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/share" :path: "../../../../node_modules/.pnpm/@capacitor+share@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/share"
CapacitorSplashScreen: CapacitorSplashScreen:
:path: "../../../../node_modules/.pnpm/@capacitor+splash-screen@5.0.6_@capacitor+core@5.5.0/node_modules/@capacitor/splash-screen" :path: "../../../../node_modules/.pnpm/@capacitor+splash-screen@6.0.1_@capacitor+core@6.1.1/node_modules/@capacitor/splash-screen"
CordovaPlugins: CordovaPlugins:
:path: "../capacitor-cordova-ios-plugins" :path: "../capacitor-cordova-ios-plugins"
TransistorsoftCapacitorBackgroundFetch: TransistorsoftCapacitorBackgroundFetch:
:path: "../../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.1.1_@capacitor+core@5.5.0/node_modules/@transistorsoft/capacitor-background-fetch" :path: "../../../../node_modules/.pnpm/@transistorsoft+capacitor-background-fetch@5.2.0_@capacitor+core@6.1.1/node_modules/@transistorsoft/capacitor-background-fetch"
SPEC CHECKSUMS: SPEC CHECKSUMS:
Capacitor: 57890b363df14d5d2d5d8461aa23e886cb34da2a Capacitor: 8941aba4364ba9d1b22188569001f2ce45cc2b00
CapacitorApp: 024e1b1bea5f883d79f6330d309bc441c88ad04a CapacitorApp: 9d53aec7101f7b030a950c5bdc4df8612576b279
CapacitorBrowser: 7a0fb6a1011abfaaf2dfedfd8248f942a8eda3d6 CapacitorBrowser: 473c7fd70ddbe541608ff09ec1be14da0078279e
CapacitorClipboard: 77edf49827ea21da2a9c05c690a4a6a4d07199c4 CapacitorClipboard: 80282f684154124b9019ebf401235b70b0cf4994
CapacitorCordova: 3d3908a3d208a11a75f9df3b18c4405c4de76e1d CapacitorCommunityScreenBrightness: 250184917bd0738a681c026a9513e18ddb0cb49b
CapacitorDevice: 2c968f98a1ec4d22357418c1521e7ddc46c675e6 CapacitorCordova: 8f2cc8d8d3619c566e9418fe8772064a94266106
CapacitorDialog: 0f3c15dfe9414b83bc64aef4078f1b92bcfead26 CapacitorDevice: f8fd88f9edd1261c55a109f32015b09bbbfdc4a0
CapacitorFilesystem: af704badfbc69f6f8623d9ed313e5490e3723dcb CapacitorDialog: 64aa82840ee5e9c066f9e1a49c1e186afd3f24d2
CapacitorGeolocation: 7be5f77abc205c0efe319fff8587a7183e7b0240 CapacitorFilesystem: 60e59ba274c234a979e7a3be2552feaadcee4263
CapacitorHaptics: 1fffc1217c7e64a472d7845be50fb0c2f7d4204c CapacitorGeolocation: 1f12bbe372b65116e851bd8e3a94cf0ef9bacebb
CapacitorKeyboard: b978154b024a5f65e044908e37d15b7de58b9d12 CapacitorHaptics: 9ebc9363f0e9b8eb4295088a0b474530acf1859b
CapacitorLocalNotifications: c2d8b14794064fd4814b1d6c4ddbac8029afa295 CapacitorKeyboard: 5f32a712adf41e07a61caafb82cf29fb6d8ba123
CapacitorNetwork: d80b3e79bef6ec37640ee2806c19771f07ff2d0c CapacitorLocalNotifications: 4ab68f0be5f697a579558fadd307d823a9ec1c26
CapacitorPreferences: f03954bcb0ff09c792909e46bff88e3183c16b10 CapacitorNetwork: 5c94acfdddc22043f2ffaff224ce9b4aa5a179f0
CapacitorPreferences: 72909b165bc7807103778ddbb86d5d8ce06abf71
CapacitorScreenOrientation: 3d4965dcbda5d901b6a48350a5d270ad7d404e05
CapacitorSecureStoragePlugin: e91d7df060f2495a1acff9583641a6953e3aacba CapacitorSecureStoragePlugin: e91d7df060f2495a1acff9583641a6953e3aacba
CapacitorShare: cd41743331cb71d217c029de54b681cbd91e0fcc CapacitorShare: 02222f2457ff003e642370a9c1ecd101baaa27c8
CapacitorSplashScreen: 5fa2ab5e46cf5cc530cf16a51c80c7a986579ccd CapacitorSplashScreen: 61645214e8f955ff2b80f16a6a3648960fe4c89f
CordovaPlugins: de5669381702d76ed5b1d442177a6a5fc3252a9d CordovaPlugins: 1078156cfc354dd440b38ce4062e69fd9b07033c
SwiftKeychainWrapper: 807ba1d63c33a7d0613288512399cd1eda1e470c SwiftKeychainWrapper: 807ba1d63c33a7d0613288512399cd1eda1e470c
TransistorsoftCapacitorBackgroundFetch: ce4b3e01b898cef516e68485d2160a078016ee97 TransistorsoftCapacitorBackgroundFetch: f130c5dcb6048401e74cc531e691e1d513045366
PODFILE CHECKSUM: 229278f2c257e8ab555325c7115b2e187e8e628d PODFILE CHECKSUM: d0561b89b66368df409c77587e3e02b48e737203
COCOAPODS: 1.13.0 COCOAPODS: 1.15.2

View File

@@ -1,7 +1,7 @@
{ {
"name": "@openstapps/app", "name": "@openstapps/app",
"description": "The generic app tailored to fulfill needs of German universities, written using Ionic Framework.", "description": "The generic app tailored to fulfill needs of German universities, written using Ionic Framework.",
"version": "3.2.0", "version": "3.3.5",
"private": true, "private": true,
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>", "author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>",
@@ -14,9 +14,7 @@
"Thea Schöbl <dev@theaninova.de>" "Thea Schöbl <dev@theaninova.de>"
], ],
"scripts": { "scripts": {
"analyze": "webpack-bundle-analyzer www/stats.json", "build": "pnpm check-icons && ng build --configuration=production --stats-json",
"build": "pnpm check-icons && ng build --configuration=production --stats-json && webpack-bundle-analyzer www/stats.json --mode static --report www/bundle-info.html --no-open",
"build:analyze": "npm run build:stats && npm run analyze",
"build:android": "ionic capacitor build android --no-open && cd android && ./gradlew clean assemble && cd ..", "build:android": "ionic capacitor build android --no-open && cd android && ./gradlew clean assemble && cd ..",
"build:prod": "ng build --configuration=production", "build:prod": "ng build --configuration=production",
"build:stats": "ng build --configuration=production --stats-json", "build:stats": "ng build --configuration=production --stats-json",
@@ -39,7 +37,6 @@
"lint": "ng lint && stylelint \"**/*.scss\"", "lint": "ng lint && stylelint \"**/*.scss\"",
"lint:fix": "eslint --fix -c .eslintrc.json --ignore-path .eslintignore --ext .ts,.html src/ && stylelint --fix \"**/*.scss\"", "lint:fix": "eslint --fix -c .eslintrc.json --ignore-path .eslintignore --ext .ts,.html src/ && stylelint --fix \"**/*.scss\"",
"minify-icons": "node scripts/minify-icon-font.mjs", "minify-icons": "node scripts/minify-icon-font.mjs",
"postinstall": "jetify && echo \"skipping jetify in production mode\"",
"preview": "http-server www --p 8101 -o", "preview": "http-server www --p 8101 -o",
"push": "git push && git push origin \"v$npm_package_version\"", "push": "git push && git push origin \"v$npm_package_version\"",
"resources:ios": "capacitor-assets generate --ios --iconBackgroundColor $(grep -oE \"^@include ion-color\\(primary, #[a-fA-F0-9]{3,6}\" src/theme/colors.scss | grep -oE \"#[a-fA-F0-9]{3,6}\") --splashBackgroundColor $(grep -oE \"^@include ion-color\\(primary, #[a-fA-F0-9]{3,6}\" src/theme/colors.scss | grep -oE \"#[a-fA-F0-9]{3,6}\")", "resources:ios": "capacitor-assets generate --ios --iconBackgroundColor $(grep -oE \"^@include ion-color\\(primary, #[a-fA-F0-9]{3,6}\" src/theme/colors.scss | grep -oE \"#[a-fA-F0-9]{3,6}\") --splashBackgroundColor $(grep -oE \"^@include ion-color\\(primary, #[a-fA-F0-9]{3,6}\" src/theme/colors.scss | grep -oE \"#[a-fA-F0-9]{3,6}\")",
@@ -52,32 +49,34 @@
"test:integration": "sh integration-test.sh" "test:integration": "sh integration-test.sh"
}, },
"dependencies": { "dependencies": {
"@angular/animations": "17.3.0", "@angular/animations": "18.0.1",
"@angular/cdk": "17.3.0", "@angular/cdk": "18.0.1",
"@angular/common": "17.3.0", "@angular/common": "18.0.1",
"@angular/core": "17.3.0", "@angular/core": "18.0.1",
"@angular/forms": "17.3.0", "@angular/forms": "18.0.1",
"@angular/platform-browser": "17.3.0", "@angular/platform-browser": "18.0.1",
"@angular/router": "17.3.0", "@angular/router": "18.0.1",
"@awesome-cordova-plugins/calendar": "6.6.0", "@awesome-cordova-plugins/calendar": "6.6.0",
"@awesome-cordova-plugins/core": "6.6.0", "@awesome-cordova-plugins/core": "6.6.0",
"@capacitor/app": "5.0.7", "@capacitor-community/screen-brightness": "6.0.0",
"@capacitor/browser": "5.2.0", "@capacitor/app": "6.0.0",
"@capacitor/clipboard": "5.0.7", "@capacitor/browser": "6.0.1",
"@capacitor/core": "5.7.3", "@capacitor/clipboard": "6.0.0",
"@capacitor/device": "5.0.7", "@capacitor/core": "6.1.1",
"@capacitor/dialog": "5.0.7", "@capacitor/device": "6.0.0",
"@capacitor/filesystem": "5.2.1", "@capacitor/dialog": "6.0.0",
"@capacitor/geolocation": "5.0.7", "@capacitor/filesystem": "6.0.0",
"@capacitor/haptics": "5.0.7", "@capacitor/geolocation": "6.0.0",
"@capacitor/keyboard": "5.0.8", "@capacitor/haptics": "6.0.0",
"@capacitor/local-notifications": "5.0.7", "@capacitor/keyboard": "6.0.1",
"@capacitor/network": "5.0.7", "@capacitor/local-notifications": "6.0.0",
"@capacitor/preferences": "5.0.7", "@capacitor/network": "6.0.1",
"@capacitor/share": "5.0.7", "@capacitor/preferences": "6.0.1",
"@capacitor/splash-screen": "5.0.7", "@capacitor/screen-orientation": "6.0.1",
"@capacitor/share": "6.0.1",
"@capacitor/splash-screen": "6.0.1",
"@ionic-native/core": "5.36.0", "@ionic-native/core": "5.36.0",
"@ionic/angular": "7.8.0", "@ionic/angular": "8.2.0",
"@ionic/storage-angular": "4.0.0", "@ionic/storage-angular": "4.0.0",
"@maplibre/ngx-maplibre-gl": "17.4.1", "@maplibre/ngx-maplibre-gl": "17.4.1",
"@ngx-translate/core": "15.0.0", "@ngx-translate/core": "15.0.0",
@@ -86,25 +85,26 @@
"@openstapps/api": "workspace:*", "@openstapps/api": "workspace:*",
"@openstapps/collection-utils": "workspace:*", "@openstapps/collection-utils": "workspace:*",
"@openstapps/core": "workspace:*", "@openstapps/core": "workspace:*",
"@transistorsoft/capacitor-background-fetch": "5.2.0", "@transistorsoft/capacitor-background-fetch": "6.0.0",
"@types/dom-view-transitions": "1.0.4", "@types/dom-view-transitions": "1.0.4",
"capacitor-secure-storage-plugin": "0.9.0", "capacitor-secure-storage-plugin": "0.10.0",
"cordova-plugin-calendar": "5.1.6", "cordova-plugin-calendar": "5.1.6",
"date-fns": "3.6.0", "date-fns": "3.6.0",
"deepmerge": "4.3.1", "deepmerge": "4.3.1",
"form-data": "4.0.0", "form-data": "4.0.0",
"geojson": "0.5.0", "geojson": "0.5.0",
"ionic-appauth": "0.9.0", "ionic-appauth": "0.9.0",
"jsonpath-plus": "6.0.1", "jsonpath-plus": "10.3.0",
"maplibre-gl": "4.0.2", "maplibre-gl": "4.0.2",
"material-symbols": "0.17.1", "material-symbols": "0.17.1",
"moment": "2.30.1", "moment": "2.30.1",
"ngx-date-fns": "11.0.0", "ngx-date-fns": "11.0.0",
"ngx-logger": "5.0.12", "ngx-logger": "5.0.12",
"ngx-markdown": "17.1.1", "ngx-markdown": "18.0.0",
"ngx-moment": "6.0.2", "ngx-moment": "6.0.2",
"opening_hours": "3.8.0", "opening_hours": "3.8.0",
"pmtiles": "3.0.3", "pmtiles": "3.0.3",
"prettier": "3.1.1",
"rxjs": "7.8.1", "rxjs": "7.8.1",
"semver": "7.6.0", "semver": "7.6.0",
"swiper": "8.4.5", "swiper": "8.4.5",
@@ -112,25 +112,25 @@
"zone.js": "0.14.4" "zone.js": "0.14.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/architect": "0.1703.0", "@angular-devkit/architect": "0.1800.2",
"@angular-devkit/build-angular": "17.3.0", "@angular-devkit/build-angular": "18.0.2",
"@angular-devkit/core": "17.3.0", "@angular-devkit/core": "18.0.2",
"@angular-devkit/schematics": "17.3.0", "@angular-devkit/schematics": "18.0.2",
"@angular-eslint/builder": "17.3.0", "@angular-eslint/builder": "18.0.1",
"@angular-eslint/eslint-plugin": "17.3.0", "@angular-eslint/eslint-plugin": "18.0.1",
"@angular-eslint/eslint-plugin-template": "17.3.0", "@angular-eslint/eslint-plugin-template": "18.0.1",
"@angular-eslint/schematics": "17.3.0", "@angular-eslint/schematics": "18.0.1",
"@angular-eslint/template-parser": "17.3.0", "@angular-eslint/template-parser": "18.0.1",
"@angular/cli": "17.3.0", "@angular/cli": "18.0.2",
"@angular/compiler": "17.3.0", "@angular/compiler": "18.0.1",
"@angular/compiler-cli": "17.3.0", "@angular/compiler-cli": "18.0.1",
"@angular/language-server": "17.3.0", "@angular/language-server": "17.3.0",
"@angular/language-service": "17.3.0", "@angular/language-service": "18.0.1",
"@angular/platform-browser-dynamic": "17.3.0", "@angular/platform-browser-dynamic": "18.0.1",
"@capacitor/android": "5.7.3", "@capacitor/android": "6.1.1",
"@capacitor/assets": "3.0.4", "@capacitor/assets": "3.0.4",
"@capacitor/cli": "5.7.3", "@capacitor/cli": "7.3.0",
"@capacitor/ios": "5.7.3", "@capacitor/ios": "6.1.1",
"@compodoc/compodoc": "1.1.23", "@compodoc/compodoc": "1.1.23",
"@cypress/schematic": "2.5.1", "@cypress/schematic": "2.5.1",
"@ionic/angular-toolkit": "11.0.1", "@ionic/angular-toolkit": "11.0.1",
@@ -146,12 +146,12 @@
"@types/karma": "6.3.8", "@types/karma": "6.3.8",
"@types/karma-coverage": "2.0.3", "@types/karma-coverage": "2.0.3",
"@types/karma-jasmine": "4.0.5", "@types/karma-jasmine": "4.0.5",
"@types/node": "18.15.3", "@types/node": "22.15.31",
"@types/semver": "7.5.8", "@types/semver": "7.5.8",
"@typescript-eslint/eslint-plugin": "7.2.0", "@typescript-eslint/eslint-plugin": "7.2.0",
"@typescript-eslint/parser": "7.2.0", "@typescript-eslint/parser": "7.2.0",
"cordova-res": "0.15.4", "cordova-res": "0.15.4",
"cypress": "13.7.0", "cypress": "13.10.0",
"eslint": "8.57.0", "eslint": "8.57.0",
"eslint-plugin-jsdoc": "48.2.1", "eslint-plugin-jsdoc": "48.2.1",
"eslint-plugin-prettier": "5.1.3", "eslint-plugin-prettier": "5.1.3",
@@ -163,7 +163,6 @@
"is-docker": "2.2.1", "is-docker": "2.2.1",
"jasmine-core": "5.1.2", "jasmine-core": "5.1.2",
"jasmine-spec-reporter": "7.0.0", "jasmine-spec-reporter": "7.0.0",
"jetifier": "2.0.0",
"junit-report-merger": "6.0.3", "junit-report-merger": "6.0.3",
"karma": "6.4.3", "karma": "6.4.3",
"karma-chrome-launcher": "3.2.0", "karma-chrome-launcher": "3.2.0",
@@ -179,8 +178,7 @@
"stylelint-config-standard-scss": "13.0.0", "stylelint-config-standard-scss": "13.0.0",
"surge": "0.23.1", "surge": "0.23.1",
"ts-node": "10.9.2", "ts-node": "10.9.2",
"typescript": "5.4.2", "typescript": "5.4.2"
"webpack-bundle-analyzer": "4.10.1"
}, },
"cordova": { "cordova": {
"plugins": {}, "plugins": {},

View File

@@ -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,
);

View File

@@ -21,7 +21,7 @@ import {ModalController, Platform} from '@ionic/angular';
import {TranslateService} from '@ngx-translate/core'; import {TranslateService} from '@ngx-translate/core';
import {ThingTranslateService} from './translation/thing-translate.service'; import {ThingTranslateService} from './translation/thing-translate.service';
import {HttpClientTestingModule} from '@angular/common/http/testing'; import {provideHttpClientTesting} from '@angular/common/http/testing';
import {AppComponent} from './app.component'; import {AppComponent} from './app.component';
import {AuthModule} from './modules/auth/auth.module'; import {AuthModule} from './modules/auth/auth.module';
import {ConfigProvider} from './modules/config/config.provider'; import {ConfigProvider} from './modules/config/config.provider';
@@ -32,6 +32,7 @@ import {ScheduleSyncService} from './modules/background/schedule/schedule-sync.s
import {sampleAuthConfiguration} from './_helpers/data/sample-configuration'; import {sampleAuthConfiguration} from './_helpers/data/sample-configuration';
import {StorageProvider} from './modules/storage/storage.provider'; import {StorageProvider} from './modules/storage/storage.provider';
import {SimpleBrowser} from './util/browser.factory'; import {SimpleBrowser} from './util/browser.factory';
import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
describe('AppComponent', () => { describe('AppComponent', () => {
let platformReadySpy: any; let platformReadySpy: any;
@@ -75,8 +76,9 @@ describe('AppComponent', () => {
modalController = jasmine.createSpyObj('ModalController', ['create', 'dismiss', 'getTop']); modalController = jasmine.createSpyObj('ModalController', ['create', 'dismiss', 'getTop']);
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes([]), HttpClientTestingModule, AuthModule],
declarations: [AppComponent], declarations: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [RouterTestingModule.withRoutes([]), AuthModule],
providers: [ providers: [
{provide: Platform, useValue: platformSpy}, {provide: Platform, useValue: platformSpy},
{provide: TranslateService, useValue: translateServiceSpy}, {provide: TranslateService, useValue: translateServiceSpy},
@@ -88,8 +90,9 @@ describe('AppComponent', () => {
{provide: StorageProvider, useValue: storageProvider}, {provide: StorageProvider, useValue: storageProvider},
{provide: SimpleBrowser, useValue: simpleBrowser}, {provide: SimpleBrowser, useValue: simpleBrowser},
{provide: ModalController, useValue: modalController}, {provide: ModalController, useValue: modalController},
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents(); }).compileComponents();
}); });

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {CommonModule, LocationStrategy, PathLocationStrategy, registerLocaleData} from '@angular/common'; import {CommonModule, LocationStrategy, PathLocationStrategy, registerLocaleData} from '@angular/common';
import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule} from '@angular/common/http'; import {HTTP_INTERCEPTORS, HttpClient, provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import localeDe from '@angular/common/locales/de'; import localeDe from '@angular/common/locales/de';
import {APP_INITIALIZER, NgModule} from '@angular/core'; import {APP_INITIALIZER, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
@@ -161,7 +161,6 @@ export function createTranslateLoader(http: HttpClient) {
JobModule, JobModule,
FavoritesModule, FavoritesModule,
LibraryModule, LibraryModule,
HttpClientModule,
ProfilePageModule, ProfilePageModule,
FeedbackModule, FeedbackModule,
MapModule, MapModule,
@@ -221,6 +220,7 @@ export function createTranslateLoader(http: HttpClient) {
useClass: ServiceHandlerInterceptor, useClass: ServiceHandlerInterceptor,
multi: true, multi: true,
}, },
provideHttpClient(withInterceptorsFromDi()),
], ],
}) })
export class AppModule { export class AppModule {

View File

@@ -13,11 +13,15 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Component} from '@angular/core'; import {ChangeDetectionStrategy, Component, inject} from '@angular/core';
import {ConfigProvider} from '../config/config.provider';
@Component({ @Component({
selector: 'about-changelog', selector: 'about-changelog',
templateUrl: 'about-changelog.html', templateUrl: 'about-changelog.html',
styleUrls: ['about-changelog.scss', './about-page/about-page.scss'], styleUrls: ['about-changelog.scss', './about-page/about-page.scss', 'release-notes-markdown.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class AboutChangelogComponent {} export class AboutChangelogComponent {
config = inject(ConfigProvider);
}

View File

@@ -16,14 +16,14 @@
<ion-header> <ion-header>
<ion-toolbar color="primary" mode="ios"> <ion-toolbar color="primary" mode="ios">
<ion-buttons slot="start"> <ion-buttons slot="start">
<ion-back-button></ion-back-button> <ion-back-button defaultHref="/about/changelog"></ion-back-button>
</ion-buttons> </ion-buttons>
<ion-title>Changelog</ion-title> <ion-title>{{ 'about.TECHNICAL_CHANGELOG' | translate }}</ion-title>
<!-- TODO: translation -->
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content parallax> <ion-content parallax>
<p class="disclaimer">{{ 'about.TECHNICAL_CHANGELOG_DISCLAIMER' | translate }}</p>
<div class="about-changelog"> <div class="about-changelog">
<markdown src="assets/about/CHANGELOG.md"></markdown> <markdown class="release-notes" src="assets/CHANGELOG.md"></markdown>
</div> </div>
</ion-content> </ion-content>

View File

@@ -13,6 +13,13 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
ion-content { ion-toggle {
--padding-start: 16px; width: fit-content;
padding-inline: var(--spacing-md);
color: var(--ion-color-primary-contrast);
}
.disclaimer {
margin-block: var(--spacing-xs);
color: var(--ion-color-primary-contrast);
} }

View File

@@ -12,24 +12,6 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
ion-content > div {
height: 100%;
}
cdk-virtual-scroll-viewport {
width: 100%;
height: 100%;
}
:host ::ng-deep {
.cdk-virtual-scroll-content-wrapper {
width: 100%;
}
}
.virtual-scroll-expander {
clear: both;
}
.supertext-icon { .supertext-icon {
height: 14px; height: 14px;

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2023 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {ChangeDetectionStrategy, Component, inject} from '@angular/core';
import {ConfigProvider} from '../config/config.provider';
@Component({
selector: 'about-changelog',
templateUrl: 'about-release-notes.html',
styleUrls: ['about-release-notes.scss', './about-page/about-page.scss', 'release-notes-markdown.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AboutReleaseNotesComponent {
config = inject(ConfigProvider);
}

View File

@@ -0,0 +1,32 @@
<!--
~ Copyright (C) 2023 StApps
~ This program is free software: you can redistribute it and/or modify it
~ under the terms of the GNU General Public License as published by the Free
~ Software Foundation, version 3.
~
~ This program 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 General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along with
~ this program. If not, see <https://www.gnu.org/licenses/>.
-->
<ion-header>
<ion-toolbar color="primary" mode="ios">
<ion-buttons slot="start">
<ion-back-button defaultHref="/about"></ion-back-button>
</ion-buttons>
<ion-title>{{ 'about.CHANGELOG' | translate }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content parallax>
<ion-button fill="clear" [routerLink]="['/about', 'stapps-changelog']"
>{{ 'about.TECHNICAL_CHANGELOG' | translate
}}<ion-icon slot="end" name="arrow_forward" ios="arrow_forward_ios"></ion-icon
></ion-button>
@for (version of config.config.app.versionHistory; track version) {
<markdown class="release-notes" [data]="'releaseNotes' | translateSimple: version"></markdown>
}
</ion-content>

View File

@@ -0,0 +1,31 @@
/*!
* Copyright (C) 2021 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
@import '../../../theme/util/mixins';
ion-button {
--color: var(--ion-color-primary-contrast);
width: fit-content;
margin-block: 0;
}
.release-notes {
@include border-radius-in-parallax(var(--border-radius-default));
display: block;
margin: var(--spacing-md);
margin-block-start: 0;
background: var(--ion-item-background);
}

View File

@@ -29,10 +29,12 @@ import {AboutLicenseModalComponent} from './about-license-modal.component';
import {AboutChangelogComponent} from './about-changelog.component'; import {AboutChangelogComponent} from './about-changelog.component';
import {UtilModule} from '../../util/util.module'; import {UtilModule} from '../../util/util.module';
import {IonIconModule} from '../../util/ion-icon/ion-icon.module'; import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
import {AboutReleaseNotesComponent} from './about-release-notes.component';
const settingsRoutes: Routes = [ const settingsRoutes: Routes = [
{path: 'about', component: AboutPageComponent}, {path: 'about', component: AboutPageComponent},
{path: 'about/changelog', component: AboutChangelogComponent}, {path: 'about/changelog', component: AboutReleaseNotesComponent},
{path: 'about/stapps-changelog', component: AboutChangelogComponent},
{path: 'about/imprint', component: AboutPageComponent}, {path: 'about/imprint', component: AboutPageComponent},
{path: 'about/privacy', component: AboutPageComponent}, {path: 'about/privacy', component: AboutPageComponent},
{path: 'about/terms', component: AboutPageComponent}, {path: 'about/terms', component: AboutPageComponent},
@@ -49,6 +51,7 @@ const settingsRoutes: Routes = [
AboutLicensesComponent, AboutLicensesComponent,
AboutLicenseModalComponent, AboutLicenseModalComponent,
AboutChangelogComponent, AboutChangelogComponent,
AboutReleaseNotesComponent,
], ],
imports: [ imports: [
CommonModule, CommonModule,

View File

@@ -2,11 +2,11 @@ import {Injectable} from '@angular/core';
import {StorageProvider} from '../storage/storage.provider'; import {StorageProvider} from '../storage/storage.provider';
import {ConfigProvider} from '../config/config.provider'; import {ConfigProvider} from '../config/config.provider';
import {ModalController} from '@ionic/angular'; import {ModalController} from '@ionic/angular';
import {Capacitor} from '@capacitor/core';
import {ReleaseNotesComponent} from './release-notes.component'; import {ReleaseNotesComponent} from './release-notes.component';
import {SCAppVersionInfo} from '@openstapps/core'; import {SCAppVersionInfo} from '@openstapps/core';
import {App} from '@capacitor/app'; import {App} from '@capacitor/app';
import {coerce} from 'semver'; import {coerce} from 'semver';
import {Capacitor} from '@capacitor/core';
export const RELEASE_NOTES_SHOWN_KEY = 'release_notes_shown'; export const RELEASE_NOTES_SHOWN_KEY = 'release_notes_shown';
@@ -25,12 +25,11 @@ export class AppVersionService {
if (Capacitor.getPlatform() === 'web') { if (Capacitor.getPlatform() === 'web') {
return; return;
} }
const storedVersion = coerce(
(await this.storage.has(RELEASE_NOTES_SHOWN_KEY))
? await this.storage.get<string>(RELEASE_NOTES_SHOWN_KEY)
: '0.0.0',
)!;
const currentVersion = coerce(await App.getInfo().then(info => info.version))!; const currentVersion = coerce(await App.getInfo().then(info => info.version))!;
if (!(await this.storage.has(RELEASE_NOTES_SHOWN_KEY))) {
await this.storage.put(RELEASE_NOTES_SHOWN_KEY, currentVersion);
}
const storedVersion = coerce(await this.storage.get<string>(RELEASE_NOTES_SHOWN_KEY))!;
return this.config.config.app.versionHistory return this.config.config.app.versionHistory
?.filter(({version}) => { ?.filter(({version}) => {
@@ -39,7 +38,7 @@ export class AppVersionService {
const isNotFutureVersion = semanticVersion.compare(currentVersion) <= 0; const isNotFutureVersion = semanticVersion.compare(currentVersion) <= 0;
return wasNotShown && isNotFutureVersion; return wasNotShown && isNotFutureVersion;
}) })
?.sort((a, b) => coerce(a.version)!.compare(b.version)); ?.sort((a, b) => -coerce(a.version)!.compare(b.version));
} }
/** /**

View File

@@ -0,0 +1,26 @@
.release-notes {
max-width: 16cm;
}
.release-notes ::ng-deep {
h1 {
font-size: 1.5em;
font-weight: bold;
color: var(--ion-color-primary);
}
h2 {
font-size: 1.2em;
font-weight: bold;
}
h3 {
font-size: 1.1em;
font-weight: bold;
}
blockquote {
background: var(--ion-item-background);
border-left: 0.25em solid var(--ion-color-medium);
}
}

View File

@@ -9,7 +9,7 @@ import {UtilModule} from '../../util/util.module';
@Component({ @Component({
selector: 'stapps-release-notes', selector: 'stapps-release-notes',
templateUrl: 'release-notes.html', templateUrl: 'release-notes.html',
styleUrls: ['release-notes.scss'], styleUrls: ['release-notes.scss', 'release-notes-markdown.scss'],
standalone: true, standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [UtilModule, MarkdownModule, ThingTranslateModule, IonicModule, TranslateModule], imports: [UtilModule, MarkdownModule, ThingTranslateModule, IonicModule, TranslateModule],

View File

@@ -13,7 +13,7 @@
<ion-content parallax> <ion-content parallax>
@for (versionInfo of versionInfos; track versionInfo) { @for (versionInfo of versionInfos; track versionInfo) {
<markdown <markdown
class="content-card ion-padding" class="content-card ion-padding release-notes"
[data]="'releaseNotes' | translateSimple: versionInfo" [data]="'releaseNotes' | translateSimple: versionInfo"
></markdown> ></markdown>
} }

View File

@@ -9,5 +9,6 @@ ion-title {
display: block; display: block;
margin: var(--spacing-md); margin: var(--spacing-md);
margin-block-start: 0;
background: var(--ion-item-background); background: var(--ion-item-background);
} }

View File

@@ -22,7 +22,7 @@ import {Requestor, StorageBackend} from '@openid/appauth';
import {TranslateService} from '@ngx-translate/core'; import {TranslateService} from '@ngx-translate/core';
import {PAIAAuthService} from './paia/paia-auth.service'; import {PAIAAuthService} from './paia/paia-auth.service';
import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider'; import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
import {HttpClientModule} from '@angular/common/http'; import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {SimpleBrowser} from '../../util/browser.factory'; import {SimpleBrowser} from '../../util/browser.factory';
import {LoggerTestingModule} from 'ngx-logger/testing'; import {LoggerTestingModule} from 'ngx-logger/testing';
@@ -54,7 +54,7 @@ describe('AuthHelperService', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [HttpClientModule, LoggerTestingModule], imports: [LoggerTestingModule],
providers: [ providers: [
StAppsWebHttpClient, StAppsWebHttpClient,
{ {
@@ -88,6 +88,7 @@ describe('AuthHelperService', () => {
provide: SimpleBrowser, provide: SimpleBrowser,
useValue: simpleBrowserMock, useValue: simpleBrowserMock,
}, },
provideHttpClient(withInterceptorsFromDi()),
], ],
}); });
authHelperService = TestBed.inject(AuthHelperService); authHelperService = TestBed.inject(AuthHelperService);

View File

@@ -81,7 +81,6 @@ export class AuthHelperService {
user[key as keyof SCUserConfiguration] = JSONPath({ user[key as keyof SCUserConfiguration] = JSONPath({
path: this.userConfigurationMap[key as keyof SCUserConfiguration] as string, path: this.userConfigurationMap[key as keyof SCUserConfiguration] as string,
json: userInfo, json: userInfo,
preventEval: true,
})[0]; })[0];
} }
if (user.givenName && user.givenName.length > 0 && user.familyName && user.familyName.length > 0) { if (user.givenName && user.givenName.length > 0 && user.familyName && user.familyName.length > 0) {

View File

@@ -86,6 +86,8 @@ export abstract class AuthService implements IAuthService {
private _authenticatedSubject = new BehaviorSubject<boolean>(false); private _authenticatedSubject = new BehaviorSubject<boolean>(false);
private _loggedInSubject = new BehaviorSubject<boolean>(false);
private _initComplete = new BehaviorSubject<boolean>(false); private _initComplete = new BehaviorSubject<boolean>(false);
protected tokenHandler: TokenRequestHandler; protected tokenHandler: TokenRequestHandler;
@@ -133,6 +135,13 @@ export abstract class AuthService implements IAuthService {
return this._authenticatedSubject.asObservable(); return this._authenticatedSubject.asObservable();
} }
/**
* Similar to isAuthenticated$, but will also return true if the token is expired
*/
get isLoggedIn$(): Observable<boolean> {
return this._loggedInSubject.asObservable();
}
get initComplete$(): Observable<boolean> { get initComplete$(): Observable<boolean> {
return this._initComplete.asObservable(); return this._initComplete.asObservable();
} }
@@ -183,19 +192,20 @@ export abstract class AuthService implements IAuthService {
protected notifyActionListers(action: IAuthAction) { protected notifyActionListers(action: IAuthAction) {
/* eslint-disable unicorn/no-useless-undefined */ /* eslint-disable unicorn/no-useless-undefined */
switch (action.action) { switch (action.action) {
case AuthActions.RefreshFailed:
case AuthActions.SignInFailed: case AuthActions.SignInFailed:
case AuthActions.SignOutSuccess: case AuthActions.SignOutSuccess:
case AuthActions.SignOutFailed: { case AuthActions.SignOutFailed: {
this._tokenSubject.next(undefined); this._tokenSubject.next(undefined);
this._userSubject.next(undefined); this._userSubject.next(undefined);
this._authenticatedSubject.next(false); this._authenticatedSubject.next(false);
this._loggedInSubject.next(false);
break; break;
} }
case AuthActions.LoadTokenFromStorageFailed: { case AuthActions.LoadTokenFromStorageFailed: {
this._tokenSubject.next(undefined); this._tokenSubject.next(undefined);
this._userSubject.next(undefined); this._userSubject.next(undefined);
this._authenticatedSubject.next(false); this._authenticatedSubject.next(false);
this._loggedInSubject.next(false);
this._initComplete.next(true); this._initComplete.next(true);
break; break;
} }
@@ -203,11 +213,13 @@ export abstract class AuthService implements IAuthService {
case AuthActions.RefreshSuccess: { case AuthActions.RefreshSuccess: {
this._tokenSubject.next(action.tokenResponse); this._tokenSubject.next(action.tokenResponse);
this._authenticatedSubject.next(true); this._authenticatedSubject.next(true);
this._loggedInSubject.next(true);
break; break;
} }
case AuthActions.LoadTokenFromStorageSuccess: { case AuthActions.LoadTokenFromStorageSuccess: {
this._tokenSubject.next(action.tokenResponse); this._tokenSubject.next(action.tokenResponse);
this._authenticatedSubject.next((action.tokenResponse as TokenResponse).isValid(0)); this._authenticatedSubject.next((action.tokenResponse as TokenResponse).isValid(0));
this._loggedInSubject.next(true);
this._initComplete.next(true); this._initComplete.next(true);
break; break;
} }
@@ -442,7 +454,6 @@ export abstract class AuthService implements IAuthService {
public async refreshToken() { public async refreshToken() {
await this.requestTokenRefresh().catch(error => { await this.requestTokenRefresh().catch(error => {
this.storage.removeItem(TOKEN_RESPONSE_KEY);
this.notifyActionListers(AuthActionBuilder.RefreshFailed(error)); this.notifyActionListers(AuthActionBuilder.RefreshFailed(error));
}); });
} }

View File

@@ -20,7 +20,7 @@ import {Browser} from 'ionic-appauth';
import {nowInSeconds, Requestor, StorageBackend} from '@openid/appauth'; import {nowInSeconds, Requestor, StorageBackend} from '@openid/appauth';
import {TranslateService} from '@ngx-translate/core'; import {TranslateService} from '@ngx-translate/core';
import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider'; import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
import {HttpClientModule} from '@angular/common/http'; import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {IonicStorage} from 'ionic-appauth/lib'; import {IonicStorage} from 'ionic-appauth/lib';
import {RouterModule} from '@angular/router'; import {RouterModule} from '@angular/router';
import {LoggerTestingModule} from 'ngx-logger/testing'; import {LoggerTestingModule} from 'ngx-logger/testing';
@@ -35,7 +35,7 @@ describe('AuthService', () => {
storageBackendSpy = jasmine.createSpyObj('StorageBackend', ['getItem']); storageBackendSpy = jasmine.createSpyObj('StorageBackend', ['getItem']);
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [HttpClientModule, LoggerTestingModule, RouterModule.forRoot([])], imports: [LoggerTestingModule, RouterModule.forRoot([])],
providers: [ providers: [
StAppsWebHttpClient, StAppsWebHttpClient,
{ {
@@ -54,6 +54,7 @@ describe('AuthService', () => {
useValue: storageBackendSpy, useValue: storageBackendSpy,
}, },
Requestor, Requestor,
provideHttpClient(withInterceptorsFromDi()),
], ],
}); });
defaultAuthService = TestBed.inject(DefaultAuthService); defaultAuthService = TestBed.inject(DefaultAuthService);

View File

@@ -81,6 +81,8 @@ export class PAIAAuthService {
private _authenticatedSubject = new BehaviorSubject<boolean>(false); private _authenticatedSubject = new BehaviorSubject<boolean>(false);
private _loggedIn = new BehaviorSubject<boolean>(false);
private _initComplete = new BehaviorSubject<boolean>(false); private _initComplete = new BehaviorSubject<boolean>(false);
protected tokenHandler: PAIATokenRequestHandler; protected tokenHandler: PAIATokenRequestHandler;
@@ -118,6 +120,13 @@ export class PAIAAuthService {
return this._authenticatedSubject.asObservable(); return this._authenticatedSubject.asObservable();
} }
/**
* Similar to isAuthenticated$, but will also return true if the token is expired
*/
get isLoggedIn$(): Observable<boolean> {
return this._loggedIn.asObservable();
}
get initComplete$(): Observable<boolean> { get initComplete$(): Observable<boolean> {
return this._initComplete.asObservable(); return this._initComplete.asObservable();
} }
@@ -170,23 +179,27 @@ export class PAIAAuthService {
this._tokenSubject.next(undefined); this._tokenSubject.next(undefined);
this._userSubject.next(undefined); this._userSubject.next(undefined);
this._authenticatedSubject.next(false); this._authenticatedSubject.next(false);
this._loggedIn.next(false);
break; break;
} }
case AuthActions.LoadTokenFromStorageFailed: { case AuthActions.LoadTokenFromStorageFailed: {
this._tokenSubject.next(undefined); this._tokenSubject.next(undefined);
this._userSubject.next(undefined); this._userSubject.next(undefined);
this._authenticatedSubject.next(false); this._authenticatedSubject.next(false);
this._loggedIn.next(false);
this._initComplete.next(true); this._initComplete.next(true);
break; break;
} }
case AuthActions.SignInSuccess: { case AuthActions.SignInSuccess: {
this._tokenSubject.next(action.tokenResponse); this._tokenSubject.next(action.tokenResponse);
this._authenticatedSubject.next(true); this._authenticatedSubject.next(true);
this._loggedIn.next(true);
break; break;
} }
case AuthActions.LoadTokenFromStorageSuccess: { case AuthActions.LoadTokenFromStorageSuccess: {
this._tokenSubject.next(action.tokenResponse); this._tokenSubject.next(action.tokenResponse);
this._authenticatedSubject.next((action.tokenResponse as TokenResponse).isValid(0)); this._authenticatedSubject.next((action.tokenResponse as TokenResponse).isValid(0));
this._loggedIn.next(true);
this._initComplete.next(true); this._initComplete.next(true);
break; break;
} }

View File

@@ -24,13 +24,17 @@ import {
} from './errors'; } from './errors';
import {NGXLogger} from 'ngx-logger'; import {NGXLogger} from 'ngx-logger';
import {sampleIndexResponse} from '../../_helpers/data/sample-configuration'; import {sampleIndexResponse} from '../../_helpers/data/sample-configuration';
import {BehaviorSubject} from 'rxjs';
import {InternetConnectionService} from '../../util/internet-connection.service';
describe('ConfigProvider', () => { describe('ConfigProvider', () => {
let internetConnectionServiceMock: {offline$: BehaviorSubject<boolean>};
let configProvider: ConfigProvider; let configProvider: ConfigProvider;
let storageProviderSpy: jasmine.SpyObj<StorageProvider>; let storageProviderSpy: jasmine.SpyObj<StorageProvider>;
let ngxLogger: jasmine.SpyObj<NGXLogger>; let ngxLogger: jasmine.SpyObj<NGXLogger>;
beforeEach(() => { beforeEach(() => {
internetConnectionServiceMock = {offline$: new BehaviorSubject<boolean>(false)};
storageProviderSpy = jasmine.createSpyObj('StorageProvider', ['init', 'get', 'has', 'put']); storageProviderSpy = jasmine.createSpyObj('StorageProvider', ['init', 'get', 'has', 'put']);
const webHttpClientMethodSpy = jasmine.createSpyObj('StAppsWebHttpClient', ['request']); const webHttpClientMethodSpy = jasmine.createSpyObj('StAppsWebHttpClient', ['request']);
ngxLogger = jasmine.createSpyObj('NGXLogger', ['log', 'error', 'warn']); ngxLogger = jasmine.createSpyObj('NGXLogger', ['log', 'error', 'warn']);
@@ -51,6 +55,10 @@ describe('ConfigProvider', () => {
provide: NGXLogger, provide: NGXLogger,
useValue: ngxLogger, useValue: ngxLogger,
}, },
{
provide: InternetConnectionService,
useValue: internetConnectionServiceMock,
},
], ],
}); });
@@ -59,7 +67,8 @@ describe('ConfigProvider', () => {
it('should fetch app configuration', async () => { it('should fetch app configuration', async () => {
spyOn(configProvider.client, 'handshake').and.returnValue(Promise.resolve(sampleIndexResponse)); spyOn(configProvider.client, 'handshake').and.returnValue(Promise.resolve(sampleIndexResponse));
const result = await configProvider.fetch(); await configProvider.fetch();
const result = configProvider.config;
expect(result).toEqual(sampleIndexResponse); expect(result).toEqual(sampleIndexResponse);
}); });
@@ -75,6 +84,22 @@ describe('ConfigProvider', () => {
expect(error).toEqual(new ConfigFetchError()); expect(error).toEqual(new ConfigFetchError());
}); });
it('should throw device offline error when offline', async () => {
// eslint-disable-next-line unicorn/error-message
let error = new Error('');
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
internetConnectionServiceMock.offline$ = new BehaviorSubject<boolean>(true);
try {
await configProvider.fetch();
} catch (error_) {
error = error_ as Error;
expect(error).toBeInstanceOf(ConfigFetchError);
expect(error.message).toContain('Device is offline.');
}
});
it('should init from remote and saved config not available', async () => { it('should init from remote and saved config not available', async () => {
storageProviderSpy.has.and.returnValue(Promise.resolve(false)); storageProviderSpy.has.and.returnValue(Promise.resolve(false));
spyOn(configProvider.client, 'handshake').and.returnValue(Promise.resolve(sampleIndexResponse)); spyOn(configProvider.client, 'handshake').and.returnValue(Promise.resolve(sampleIndexResponse));
@@ -86,7 +111,7 @@ describe('ConfigProvider', () => {
expect(storageProviderSpy.has).toHaveBeenCalled(); expect(storageProviderSpy.has).toHaveBeenCalled();
expect(storageProviderSpy.get).toHaveBeenCalledTimes(0); expect(storageProviderSpy.get).toHaveBeenCalledTimes(0);
expect(configProvider.client.handshake).toHaveBeenCalled(); expect(configProvider.client.handshake).toHaveBeenCalled();
expect(await configProvider.getValue('name')).toEqual(sampleIndexResponse.app.name); expect(configProvider.getValue('name')).toEqual(sampleIndexResponse.app.name);
}); });
it('should throw error on failed initialisation', async () => { it('should throw error on failed initialisation', async () => {
@@ -108,6 +133,8 @@ describe('ConfigProvider', () => {
wrongConfig.backend.SCVersion = '0.1.0'; wrongConfig.backend.SCVersion = '0.1.0';
storageProviderSpy.get.and.returnValue(Promise.resolve(wrongConfig)); storageProviderSpy.get.and.returnValue(Promise.resolve(wrongConfig));
spyOn(configProvider.client, 'handshake').and.returnValue(Promise.resolve(sampleIndexResponse)); spyOn(configProvider.client, 'handshake').and.returnValue(Promise.resolve(sampleIndexResponse));
// sets StApps core version which has a different major version
configProvider.scVersion = '1.0.0';
await configProvider.init(); await configProvider.init();
expect(ngxLogger.warn).toHaveBeenCalledWith( expect(ngxLogger.warn).toHaveBeenCalledWith(
@@ -115,6 +142,19 @@ describe('ConfigProvider', () => {
); );
}); });
it('should load from storage if offline and same major version stored', async () => {
internetConnectionServiceMock = {offline$: new BehaviorSubject<boolean>(true)};
storageProviderSpy.has.and.returnValue(Promise.resolve(true));
const configInStorage = structuredClone(sampleIndexResponse);
configInStorage.backend.SCVersion = '1.0.0';
storageProviderSpy.get.and.returnValue(Promise.resolve(configInStorage));
// sets StApps core version which has the same major version
configProvider.scVersion = '1.1.1';
await configProvider.init();
expect(configProvider.getAnyValue('app')).toEqual(configInStorage.app);
});
it('should throw error on saved app configuration not available', async () => { it('should throw error on saved app configuration not available', async () => {
storageProviderSpy.has.and.returnValue(Promise.resolve(false)); storageProviderSpy.has.and.returnValue(Promise.resolve(false));
// eslint-disable-next-line unicorn/error-message // eslint-disable-next-line unicorn/error-message
@@ -153,4 +193,31 @@ describe('ConfigProvider', () => {
expect(configProvider.getValue('name')).toEqual(sampleIndexResponse.app.name); expect(configProvider.getValue('name')).toEqual(sampleIndexResponse.app.name);
}); });
it('should fetch new config from remote on init', async () => {
storageProviderSpy.has.and.returnValue(Promise.resolve(true));
storageProviderSpy.get.and.returnValue(Promise.resolve(sampleIndexResponse));
spyOn(configProvider, 'fetch');
await configProvider.init();
expect(configProvider.fetch).toHaveBeenCalled();
});
it('should update the local config with the one from remote', async () => {
storageProviderSpy.has.and.returnValue(Promise.resolve(true));
storageProviderSpy.get.and.returnValue(Promise.resolve(sampleIndexResponse));
const newConfig = structuredClone(sampleIndexResponse);
newConfig.app.name = 'New app name';
spyOn(configProvider.client, 'handshake').and.returnValue(Promise.resolve(newConfig));
await configProvider.init();
// Validate that the initial configuration is loaded
expect(configProvider.getValue('name')).toEqual(sampleIndexResponse.app.name);
// Fetch the new configuration from the remote
await configProvider.fetch();
// Validate that the new configuration is now set
expect(configProvider.getValue('name')).toEqual(newConfig.app.name);
});
}); });

View File

@@ -27,6 +27,8 @@ import {
SavedConfigNotAvailable, SavedConfigNotAvailable,
WrongConfigVersionInStorage, WrongConfigVersionInStorage,
} from './errors'; } from './errors';
import {InternetConnectionService} from '../../util/internet-connection.service';
import {firstValueFrom} from 'rxjs';
/** /**
* Key to store config in storage module * Key to store config in storage module
@@ -72,6 +74,7 @@ export class ConfigProvider {
private readonly storageProvider: StorageProvider, private readonly storageProvider: StorageProvider,
swHttpClient: StAppsWebHttpClient, swHttpClient: StAppsWebHttpClient,
private readonly logger: NGXLogger, private readonly logger: NGXLogger,
private readonly internetConnectionService: InternetConnectionService,
) { ) {
console.log('config init'); console.log('config init');
this.client = new Client(swHttpClient, environment.backend_url, environment.backend_version); this.client = new Client(swHttpClient, environment.backend_url, environment.backend_version);
@@ -80,11 +83,20 @@ export class ConfigProvider {
/** /**
* Fetches configuration from backend * Fetches configuration from backend
*/ */
async fetch(): Promise<SCIndexResponse> { async fetch(): Promise<void> {
try { try {
return await this.client.handshake(this.scVersion); const isOffline = await firstValueFrom(this.internetConnectionService.offline$);
} catch { if (isOffline) {
throw new ConfigFetchError(); throw new Error('Device is offline.');
} else {
const fetchedConfig: SCIndexResponse = await this.client.handshake(this.scVersion);
await this.set(fetchedConfig);
this.logger.log(`Configuration updated from remote`);
}
} catch (error) {
const error_ = error instanceof Error ? new ConfigFetchError(error.message) : new ConfigFetchError();
this.logger.warn(`Failed to fetch remote configuration:`, error_);
throw error_; // Rethrow the error to handle it in init()
} }
} }
@@ -112,40 +124,33 @@ export class ConfigProvider {
/** /**
* Initialises the ConfigProvider * Initialises the ConfigProvider
* @throws ConfigInitError if no configuration could be loaded. * @throws ConfigInitError if no configuration could be loaded both locally and remote.
* @throws WrongConfigVersionInStorage if fetch failed and saved config has wrong SCVersion
*/ */
async init(): Promise<void> { async init(): Promise<void> {
let loadError;
let fetchError;
// load saved configuration
try { try {
// Attempt to load the configuration from local storage
this.config = await this.loadLocal(); this.config = await this.loadLocal();
this.firstSession = false; this.firstSession = false;
this.logger.log(`initialised configuration from storage`); this.logger.log(`initialised configuration from storage`);
if (this.config.backend.SCVersion !== this.scVersion) {
loadError = new WrongConfigVersionInStorage(this.scVersion, this.config.backend.SCVersion); // Check if the stored configuration has the correct version
if (this.config.backend.SCVersion.split('.')[0] !== this.scVersion.split('.')[0]) {
throw new WrongConfigVersionInStorage(this.scVersion, this.config.backend.SCVersion);
} }
} catch (error) {
loadError = error; // Fetch the remote configuration in a non-blocking manner
} void this.fetch();
// fetch remote configuration from backend } catch (loadError) {
this.logger.warn(loadError);
try { try {
const fetchedConfig: SCIndexResponse = await this.fetch(); // If local loading fails, immediately try to fetch the configuration from remote
await this.set(fetchedConfig); await this.fetch();
this.logger.log(`initialised configuration from remote`); } catch (fetchError) {
} catch (error) { this.logger.warn(`Failed to fetch remote configuration:`, fetchError);
fetchError = error; // If both local loading and remote fetching fail, throw ConfigInitError
}
// check for occurred errors and throw them
if (loadError !== undefined && fetchError !== undefined) {
throw new ConfigInitError(); throw new ConfigInitError();
} }
if (loadError !== undefined) {
this.logger.warn(loadError);
}
if (fetchError !== undefined) {
this.logger.warn(fetchError);
} }
} }

View File

@@ -19,8 +19,10 @@ import {AppError} from '../../_helpers/errors';
* Error that is thrown when fetching from backend fails * Error that is thrown when fetching from backend fails
*/ */
export class ConfigFetchError extends AppError { export class ConfigFetchError extends AppError {
constructor() { constructor(reason?: string) {
super('ConfigFetchError', 'App configuration could not be fetched!'); const defaultMessage = 'App configuration could not be fetched!';
const message = reason ? `${defaultMessage} ${reason}` : defaultMessage;
super('ConfigFetchError', message);
} }
} }

View File

@@ -37,8 +37,5 @@
{{ 'dashboard.jobs.noJobs' | translate }} {{ 'dashboard.jobs.noJobs' | translate }}
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-button slot="button-end" fill="clear" color="medium" [routerLink]="['/jobs']">
<ion-icon slot="icon-only" name="search" [size]="24"></ion-icon>
</ion-button>
} }
</stapps-section> </stapps-section>

View File

@@ -14,7 +14,7 @@
*/ */
import {ScrollingModule} from '@angular/cdk/scrolling'; import {ScrollingModule} from '@angular/cdk/scrolling';
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {HttpClientModule} from '@angular/common/http'; import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms'; import {FormsModule} from '@angular/forms';
import {IonicModule, Platform} from '@ionic/angular'; import {IonicModule, Platform} from '@ionic/angular';
@@ -181,12 +181,33 @@ import {ShareButtonComponent} from './elements/share-button.component';
PeriodicalDetailContentComponent, PeriodicalDetailContentComponent,
ShareButtonComponent, ShareButtonComponent,
], ],
exports: [
DataDetailComponent,
DataDetailContentComponent,
DataIconPipe,
DataListComponent,
DataListItemComponent,
DateSeriesListItemComponent,
PlaceListItemComponent,
SimpleCardComponent,
SkeletonListItemComponent,
SkeletonSimpleCardComponent,
SearchPageComponent,
SimpleDataListComponent,
OriginDetailComponent,
FavoriteButtonComponent,
TreeListComponent,
ExternalLinkComponent,
ArticleDetailContentComponent,
BookDetailContentComponent,
PeriodicalDetailContentComponent,
TitleCardComponent,
],
imports: [ imports: [
CommonModule, CommonModule,
DataRoutingModule, DataRoutingModule,
FormsModule, FormsModule,
MapWidgetComponent, MapWidgetComponent,
HttpClientModule,
IonicModule.forRoot(), IonicModule.forRoot(),
MarkdownModule.forRoot(), MarkdownModule.forRoot(),
MenuModule, MenuModule,
@@ -218,28 +239,7 @@ import {ShareButtonComponent} from './elements/share-button.component';
useFactory: browserFactory, useFactory: browserFactory,
deps: [Platform], deps: [Platform],
}, },
], provideHttpClient(withInterceptorsFromDi()),
exports: [
DataDetailComponent,
DataDetailContentComponent,
DataIconPipe,
DataListComponent,
DataListItemComponent,
DateSeriesListItemComponent,
PlaceListItemComponent,
SimpleCardComponent,
SkeletonListItemComponent,
SkeletonSimpleCardComponent,
SearchPageComponent,
SimpleDataListComponent,
OriginDetailComponent,
FavoriteButtonComponent,
TreeListComponent,
ExternalLinkComponent,
ArticleDetailContentComponent,
BookDetailContentComponent,
PeriodicalDetailContentComponent,
TitleCardComponent,
], ],
}) })
export class DataModule {} export class DataModule {}

View File

@@ -33,9 +33,9 @@ ion-item {
margin: var(--spacing-sm); margin: var(--spacing-sm);
ion-thumbnail { ion-thumbnail {
--ion-margin: var(--spacing-xs); --size: 36px;
margin-block: auto; margin: 0;
margin-inline: var(--spacing-md); margin-inline: var(--spacing-md);
padding: 0; padding: 0;
} }

View File

@@ -80,13 +80,12 @@
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-checkbox <ion-checkbox
class="ion-text-wrap"
color="primary" color="primary"
label-placement="end" label-placement="end"
justify="start" justify="start"
[(ngModel)]="termsAgree" [(ngModel)]="termsAgree"
name="termsAgree" name="termsAgree"
>{{ 'feedback.form.termsAgree.0' | translate }}</ion-checkbox ><span class="ion-text-wrap">{{ 'feedback.form.termsAgree.0' | translate }}</span></ion-checkbox
> >
</ion-item> </ion-item>
<ion-item lines="none"> <ion-item lines="none">
@@ -104,7 +103,9 @@
justify="start" justify="start"
[(ngModel)]="protocolDataAgree" [(ngModel)]="protocolDataAgree"
name="protocolDataAgree" name="protocolDataAgree"
>{{ 'feedback.form.protocolDataAgree' | translate }}</ion-checkbox ><span class="ion-text-wrap">{{
'feedback.form.protocolDataAgree' | translate
}}</span></ion-checkbox
> >
</ion-item> </ion-item>
<ion-card> <ion-card>

View File

@@ -21,7 +21,7 @@ import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
import {StorageProvider} from '../storage/storage.provider'; import {StorageProvider} from '../storage/storage.provider';
import {LoggerTestingModule} from 'ngx-logger/testing'; import {LoggerTestingModule} from 'ngx-logger/testing';
import {MapModule} from '../map/map.module'; import {MapModule} from '../map/map.module';
import {HttpClientModule} from '@angular/common/http'; import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {StorageModule} from '../storage/storage.module'; import {StorageModule} from '../storage/storage.module';
import {DaiaHolding, DaiaService} from './protocol/response'; import {DaiaHolding, DaiaService} from './protocol/response';
import {Observable, of} from 'rxjs'; import {Observable, of} from 'rxjs';
@@ -47,7 +47,6 @@ describe('DaiaDataProvider', () => {
imports: [ imports: [
HebisModule, HebisModule,
MapModule, MapModule,
HttpClientModule,
StorageModule, StorageModule,
LoggerTestingModule, LoggerTestingModule,
TranslateModule.forRoot({ TranslateModule.forRoot({
@@ -62,6 +61,7 @@ describe('DaiaDataProvider', () => {
StAppsWebHttpClient, StAppsWebHttpClient,
StorageProvider, StorageProvider,
DaiaDataProvider, DaiaDataProvider,
provideHttpClient(withInterceptorsFromDi()),
], ],
}); });
daiaDataProvider = TestBed.inject(DaiaDataProvider); daiaDataProvider = TestBed.inject(DaiaDataProvider);

View File

@@ -14,7 +14,7 @@
*/ */
import {ScrollingModule} from '@angular/cdk/scrolling'; import {ScrollingModule} from '@angular/cdk/scrolling';
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {HttpClientModule} from '@angular/common/http'; import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms'; import {FormsModule} from '@angular/forms';
import {IonicModule} from '@ionic/angular'; import {IonicModule} from '@ionic/angular';
@@ -54,7 +54,6 @@ import {DaiaHoldingComponent} from './daia-availability/daia-holding.component';
FormsModule, FormsModule,
HebisRoutingModule, HebisRoutingModule,
IonIconModule, IonIconModule,
HttpClientModule,
IonicModule.forRoot(), IonicModule.forRoot(),
MarkdownModule.forRoot(), MarkdownModule.forRoot(),
MenuModule, MenuModule,
@@ -69,6 +68,11 @@ import {DaiaHoldingComponent} from './daia-availability/daia-holding.component';
ThingTranslateModule.forChild(), ThingTranslateModule.forChild(),
UtilModule, UtilModule,
], ],
providers: [HebisDataProvider, DaiaDataProvider, StAppsWebHttpClient], providers: [
HebisDataProvider,
DaiaDataProvider,
StAppsWebHttpClient,
provideHttpClient(withInterceptorsFromDi()),
],
}) })
export class HebisModule {} export class HebisModule {}

View File

@@ -11,7 +11,7 @@ export class LibraryAccountPageComponent {
constructor(private readonly libraryAccountService: LibraryAccountService) {} constructor(private readonly libraryAccountService: LibraryAccountService) {}
async ionViewWillEnter(): Promise<void> { async ionViewWillEnter(): Promise<void> {
const patron = await this.libraryAccountService.getProfile(); const patron = await this.libraryAccountService.getPatron();
this.name = patron?.name; this.name = patron?.name;
} }
} }

View File

@@ -39,6 +39,8 @@ export class CheckedOutPageComponent {
async fetchItems() { async fetchItems() {
try { try {
// Prepare patron (status) for the items
await this.libraryAccountService.getPatron();
this.checkedOutItems = undefined; this.checkedOutItems = undefined;
this.checkedOutItems = await this.libraryAccountService.getFilteredItems([PAIADocumentStatus.Held]); this.checkedOutItems = await this.libraryAccountService.getFilteredItems([PAIADocumentStatus.Held]);
} catch { } catch {

View File

@@ -27,7 +27,7 @@
@for (checkedOutItem of checkedOutItems; track checkedOutItem) { @for (checkedOutItem of checkedOutItems; track checkedOutItem) {
<stapps-paia-item <stapps-paia-item
[item]="checkedOutItem" [item]="checkedOutItem"
[propertiesToShow]="['label', 'renewals', 'endtime']" [propertiesToShow]="['label', 'renewals', 'duedate']"
(documentAction)="onDocumentAction($event)" (documentAction)="onDocumentAction($event)"
listName="checked_out" listName="checked_out"
> >

View File

@@ -14,7 +14,8 @@
*/ */
import {Component, EventEmitter, Input, Output} from '@angular/core'; import {Component, EventEmitter, Input, Output} from '@angular/core';
import {DocumentAction, PAIADocument} from '../../../types'; import {DocumentAction, PAIADocument, PAIADocumentStatus, PAIADocumentVisualStatus} from '../../../types';
import {LibraryAccountService} from '../../library-account.service';
@Component({ @Component({
selector: 'stapps-paia-item', selector: 'stapps-paia-item',
@@ -22,7 +23,24 @@ import {DocumentAction, PAIADocument} from '../../../types';
styleUrls: ['./paiaitem.scss'], styleUrls: ['./paiaitem.scss'],
}) })
export class PAIAItemComponent { export class PAIAItemComponent {
@Input() item: PAIADocument; private _item: PAIADocument;
renewable: boolean;
visualStatus?: PAIADocumentVisualStatus;
constructor(private readonly libraryAccountService: LibraryAccountService) {}
@Input()
set item(value: PAIADocument) {
this._item = value;
void this.setRenewable();
this.visualStatus = this.getVisualStatus(Number(this.item.status));
}
get item(): PAIADocument {
return this._item;
}
@Input() @Input()
propertiesToShow: (keyof PAIADocument)[]; propertiesToShow: (keyof PAIADocument)[];
@@ -36,4 +54,23 @@ export class PAIAItemComponent {
async onClick(action: DocumentAction['action']) { async onClick(action: DocumentAction['action']) {
this.documentAction.emit({doc: this.item, action}); this.documentAction.emit({doc: this.item, action});
} }
private async setRenewable() {
const isActive = await this.libraryAccountService.isActivePatron();
this.renewable = isActive && Number(this.item.status) === PAIADocumentStatus.Held;
}
private getVisualStatus(status: PAIADocumentStatus): PAIADocumentVisualStatus | undefined {
switch (status) {
case PAIADocumentStatus.Ordered: {
return {color: 'warning', status: status, statusText: 'ordered'};
}
case PAIADocumentStatus.Provided: {
return {color: 'success', status: status, statusText: 'ready'};
}
default: {
return undefined;
}
}
}
} }

View File

@@ -14,16 +14,22 @@
--> -->
<ion-item> <ion-item>
<!-- TODO: text not selectable in Chrome, bugfix needed https://github.com/ionic-team/ionic-framework/issues/24956 -->
<ion-label class="ion-text-wrap"> <ion-label class="ion-text-wrap">
@if (item.about) { @if (item.about) {
<h2 class="name">{{ item.about }}</h2> <h2 class="name">
@if (visualStatus) {
<ion-badge [color]="visualStatus.color" slot="start">
{{ 'library.account.pages' + '.' + listName + '.' + visualStatus.statusText | translate }}
</ion-badge>
}
{{ item.about }}
</h2>
} }
@for (property of propertiesToShow; track property) { @for (property of propertiesToShow; track property) {
@if (item[property]) { @if (item[property]) {
<p> <p>
{{ 'library.account.pages' + '.' + listName + '.' + 'labels' + '.' + property | translate }}: {{ 'library.account.pages' + '.' + listName + '.' + 'labels' + '.' + property | translate }}:
@if (!['endtime', 'duedate'].includes(property)) { @if (!['starttime', 'duedate'].includes(property)) {
{{ item[property] }} {{ item[property] }}
} @else { } @else {
{{ $any(item[property]) | amDateFormat: 'll' }} {{ $any(item[property]) | amDateFormat: 'll' }}
@@ -40,7 +46,7 @@
<!-- >--> <!-- >-->
<!-- {{ 'library.account.actions.cancel.header' | translate }}</ion-button--> <!-- {{ 'library.account.actions.cancel.header' | translate }}</ion-button-->
<!-- >--> <!-- >-->
@if (item.canrenew) { @if (renewable && item.canrenew) {
<ion-button color="primary" (click)="onClick('renew')"> <ion-button color="primary" (click)="onClick('renew')">
{{ 'library.account.actions.renew.header' | translate }}</ion-button {{ 'library.account.actions.renew.header' | translate }}</ion-button
> >

View File

@@ -12,3 +12,6 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
ion-badge {
vertical-align: bottom;
}

View File

@@ -45,6 +45,8 @@ export class HoldsPageComponent {
? [PAIADocumentStatus.Reserved] ? [PAIADocumentStatus.Reserved]
: [PAIADocumentStatus.Ordered, PAIADocumentStatus.Provided]; : [PAIADocumentStatus.Ordered, PAIADocumentStatus.Provided];
try { try {
// Prepare patron (status) for the items
await this.libraryAccountService.getPatron();
this.paiaDocuments = await this.libraryAccountService.getFilteredItems(itemsStatus); this.paiaDocuments = await this.libraryAccountService.getFilteredItems(itemsStatus);
} catch { } catch {
await this.libraryAccountService.handleError(); await this.libraryAccountService.handleError();

View File

@@ -35,7 +35,6 @@
@switch (activeSegment) { @switch (activeSegment) {
@case ('orders') { @case ('orders') {
@for (hold of paiaDocuments; track hold) { @for (hold of paiaDocuments; track hold) {
@if (toNumber(hold.status) === paiaDocumentStatus.Provided) {
<stapps-paia-item <stapps-paia-item
[item]="hold" [item]="hold"
[propertiesToShow]="['label', 'storage']" [propertiesToShow]="['label', 'storage']"
@@ -43,22 +42,13 @@
listName="holds" listName="holds"
> >
</stapps-paia-item> </stapps-paia-item>
} @else {
<stapps-paia-item
[item]="hold"
[propertiesToShow]="['label']"
(documentAction)="onDocumentAction($event)"
listName="holds"
>
</stapps-paia-item>
}
} }
} }
@case ('reservations') { @case ('reservations') {
@for (hold of paiaDocuments; track hold) { @for (hold of paiaDocuments; track hold) {
<stapps-paia-item <stapps-paia-item
[item]="hold" [item]="hold"
[propertiesToShow]="['label']" [propertiesToShow]="['label', 'starttime', 'storage', 'queue']"
(documentAction)="onDocumentAction($event)" (documentAction)="onDocumentAction($event)"
listName="holds" listName="holds"
> >

View File

@@ -20,7 +20,15 @@ import {
SCFeatureConfiguration, SCFeatureConfiguration,
SCFeatureConfigurationExtern, SCFeatureConfigurationExtern,
} from '@openstapps/core'; } from '@openstapps/core';
import {DocumentAction, PAIADocument, PAIADocumentStatus, PAIAFees, PAIAItems, PAIAPatron} from '../types'; import {
DocumentAction,
PAIADocument,
PAIADocumentStatus,
PAIAFees,
PAIAItems,
PAIAPatron,
PAIAPatronStatus,
} from '../types';
import {HebisDataProvider} from '../../hebis/hebis-data.provider'; import {HebisDataProvider} from '../../hebis/hebis-data.provider';
import {PAIATokenResponse} from '../../auth/paia/paia-token-response'; import {PAIATokenResponse} from '../../auth/paia/paia-token-response';
import {AuthHelperService} from '../../auth/auth-helper.service'; import {AuthHelperService} from '../../auth/auth-helper.service';
@@ -43,6 +51,11 @@ export class LibraryAccountService {
*/ */
authType: SCAuthorizationProviderType; authType: SCAuthorizationProviderType;
/**
* Account (Patron) status
*/
private status?: PAIAPatronStatus;
constructor( constructor(
protected requestor: Requestor = new JQueryRequestor(), protected requestor: Requestor = new JQueryRequestor(),
private readonly hebisDataProvider: HebisDataProvider, private readonly hebisDataProvider: HebisDataProvider,
@@ -60,12 +73,23 @@ export class LibraryAccountService {
this.authType = config.authProvider as SCAuthorizationProviderType; this.authType = config.authProvider as SCAuthorizationProviderType;
} }
async getProfile() { async getPatron() {
const patron = ((await this.getValidToken()) as PAIATokenResponse).patron; const patronId = ((await this.getValidToken()) as PAIATokenResponse).patron;
return { const patron = {
...(await this.performRequest<PAIAPatron>(`${this.baseUrl}/{patron}`)), ...(await this.performRequest<PAIAPatron>(`${this.baseUrl}/{patron}`)),
id: patron, id: patronId,
} as PAIAPatron; } as PAIAPatron;
// Refresh the status
this.status = Number(patron.status);
return patron;
}
async getPatronStatus() {
return this.status ?? (this.status = Number((await this.getPatron()).status) as PAIAPatronStatus);
}
async isActivePatron() {
return (await this.getPatronStatus()) === PAIAPatronStatus.Active;
} }
async getItems() { async getItems() {

View File

@@ -25,15 +25,17 @@ import {PAIAPatron} from '../../types';
export class ProfilePageComponent { export class ProfilePageComponent {
patron?: PAIAPatron; patron?: PAIAPatron;
propertiesToShow: (keyof PAIAPatron)[] = ['id', 'name', 'email', 'address', 'expires', 'note'];
constructor(private readonly libraryAccountService: LibraryAccountService) {} constructor(private readonly libraryAccountService: LibraryAccountService) {}
async ionViewWillEnter(): Promise<void> { async ionViewWillEnter(): Promise<void> {
try { try {
this.patron = await this.libraryAccountService.getProfile(); this.patron = await this.libraryAccountService.getPatron();
} catch { } catch {
await this.libraryAccountService.handleError(); await this.libraryAccountService.handleError();
} }
} }
isUnlimitedExpiry(date = ''): boolean {
return new Date(date).getFullYear() === 9999;
}
} }

View File

@@ -26,27 +26,45 @@
<ion-card> <ion-card>
<ion-card-content> <ion-card-content>
<ion-grid> <ion-grid>
@for (property of propertiesToShow; track property) {
@if (patron[property]) {
<ion-row> <ion-row>
<ion-col> {{ 'library.account.pages.profile.labels.id' | translate }}: </ion-col>
<ion-col> <ion-col>
{{ 'library.account.pages.profile.labels' + '.' + property | translate }}: {{ patron.id }}
</ion-col> </ion-col>
</ion-row>
<ion-row>
<ion-col> {{ 'library.account.pages.profile.labels.name' | translate }}: </ion-col>
<ion-col> {{ patron.name_details?.title ?? '' }} {{ patron.name }} </ion-col>
</ion-row>
@if (patron.email) {
<ion-row>
<ion-col> {{ 'library.account.pages.profile.labels.email' | translate }}: </ion-col>
<ion-col> <ion-col>
@if (!['expires'].includes(property)) { {{ patron.email }}
{{ patron[property] }}
} @else {
@if (patron[property] === '9999-12-31') {
{{ 'library.account.pages.profile.values.unlimited' | translate }}
} @else {
{{ 'library.account.pages.profile.values.expires' | translate }}:&nbsp;{{
patron[property] | amDateFormat: 'll'
}}
}
}
</ion-col> </ion-col>
</ion-row> </ion-row>
} }
@if (patron.address) {
<ion-row>
<ion-col> {{ 'library.account.pages.profile.labels.address' | translate }}: </ion-col>
<ion-col>
{{ patron.address }}
</ion-col>
</ion-row>
}
@if (patron.expires) {
<ion-row>
<ion-col> {{ 'library.account.pages.profile.labels.expires' | translate }}: </ion-col>
<ion-col>
@if (isUnlimitedExpiry(patron.expires)) {
{{ 'library.account.pages.profile.values.unlimited' | translate }}
} @else {
{{ 'library.account.pages.profile.values.expires' | translate }}:&nbsp;{{
patron.expires | amDateFormat: 'll'
}}
}
</ion-col>
</ion-row>
} }
</ion-grid> </ion-grid>
</ion-card-content> </ion-card-content>

View File

@@ -19,9 +19,24 @@ export interface PAIAPatron {
email?: string; email?: string;
address?: string; address?: string;
expires?: string; expires?: string;
status?: string; status?: PAIAPatronStatus;
type?: string; type?: string;
note?: string; note?: string;
// HeBIS specific property (not in PAIA documentation)
name_details?: {
firstname?: string;
lastname?: string;
gender?: string;
title?: string;
};
}
export enum PAIAPatronStatus {
Active = 0,
Inactive = 1,
InactiveExpired = 2,
InactiveOutstandingFees = 3,
InactiveExpiredOutstandingFees = 4,
} }
/* /*
@@ -39,7 +54,7 @@ export interface PAIADocument {
queue?: string; queue?: string;
renewals?: string; renewals?: string;
reminder?: string; reminder?: string;
endtime?: string; starttime?: string;
duedate?: string; duedate?: string;
cancancel?: boolean; cancancel?: boolean;
canrenew?: boolean; canrenew?: boolean;
@@ -80,3 +95,9 @@ export interface DocumentAction {
action: 'cancel' | 'renew'; action: 'cancel' | 'renew';
doc: PAIADocument; doc: PAIADocument;
} }
export interface PAIADocumentVisualStatus {
color: 'warning' | 'success';
status: PAIADocumentStatus;
statusText: 'ordered' | 'ready';
}

View File

@@ -1,5 +1,6 @@
import {Directive, Input, Host} from '@angular/core'; import {Directive, Input, Host} from '@angular/core';
import {MapComponent} from '@maplibre/ngx-maplibre-gl'; import {MapComponent} from '@maplibre/ngx-maplibre-gl';
import {environment} from '../../../environments/environment';
@Directive({ @Directive({
selector: 'mgl-map[styleName]', selector: 'mgl-map[styleName]',
@@ -10,7 +11,7 @@ export class MapStyleDirective {
@Input() @Input()
set styleName(name: string) { set styleName(name: string) {
const style = `https://maps.server.uni-frankfurt.de/static/styles/${name}/style.json`; const style = `${environment.backend_url}/_static/map/styles/${name}/style.json`;
if (this.map.style) { if (this.map.style) {
this.map.mapInstance.setStyle(style); this.map.mapInstance.setStyle(style);
} else { } else {

View File

@@ -14,7 +14,7 @@
*/ */
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {MapModule} from './map.module'; import {MapModule} from './map.module';
import {HttpClientModule} from '@angular/common/http'; import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {StorageModule} from '../storage/storage.module'; import {StorageModule} from '../storage/storage.module';
import {MapPosition, PositionService} from './position.service'; import {MapPosition, PositionService} from './position.service';
import {ConfigProvider} from '../config/config.provider'; import {ConfigProvider} from '../config/config.provider';
@@ -39,12 +39,13 @@ describe('PositionService', () => {
}, },
}); });
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [MapModule, HttpClientModule, StorageModule, LoggerTestingModule], imports: [MapModule, StorageModule, LoggerTestingModule],
providers: [ providers: [
{ {
provider: ConfigProvider, provider: ConfigProvider,
useValue: configProviderMock, useValue: configProviderMock,
}, },
provideHttpClient(withInterceptorsFromDi()),
], ],
}); });
positionService = TestBed.inject(PositionService); positionService = TestBed.inject(PositionService);

View File

@@ -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 {SCIdCard} from '@openstapps/core';
import {FullScreenImageDirective} from '../../util/full-screen-image.directive';
import {ThingTranslateModule} from '../../translation/thing-translate.module'; import {ThingTranslateModule} from '../../translation/thing-translate.module';
import {AsyncPipe, TitleCasePipe} from '@angular/common'; import {AsyncPipe, TitleCasePipe} from '@angular/common';
import {InRangeNowPipe, ToDateRangePipe} from '../../util/in-range.pipe'; import {IntervalIsNowPipe, ToDateIntervalPipe} from '../../util/in-range.pipe';
import {TranslateModule} from '@ngx-translate/core'; 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({ @Component({
selector: 'stapps-id-card', selector: 'stapps-id-card',
@@ -12,12 +16,10 @@ import {TranslateModule} from '@ngx-translate/core';
styleUrls: ['id-card.scss'], styleUrls: ['id-card.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
hostDirectives: [FullScreenImageDirective],
imports: [ imports: [
FullScreenImageDirective,
ThingTranslateModule, ThingTranslateModule,
InRangeNowPipe, IntervalIsNowPipe,
ToDateRangePipe, ToDateIntervalPipe,
AsyncPipe, AsyncPipe,
TranslateModule, TranslateModule,
TitleCasePipe, TitleCasePipe,
@@ -25,4 +27,126 @@ import {TranslateModule} from '@ngx-translate/core';
}) })
export class IdCardComponent { export class IdCardComponent {
@Input({required: true}) item: SCIdCard; @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();
}
} }

View File

@@ -1,4 +1,9 @@
<img [src]="item.image" [alt]="'name' | thingTranslate: item" draggable="false" /> <img
@if (item.validity && (item.validity | toDateRange | isInRangeNow | async) === false) { [src]="item.image"
[alt]="'name' | thingTranslate: item"
draggable="false"
(click)="presentFullscreen()"
/>
@if (item.validity && (item.validity | rangeToDateInterval | dfnsIntervalIsNow | async) === false) {
<div class="expired">{{ 'profile.userInfo.expired' | translate | titlecase }}</div> <div class="expired">{{ 'profile.userInfo.expired' | translate | titlecase }}</div>
} }

View File

@@ -3,11 +3,6 @@
overflow: hidden; overflow: hidden;
} }
:host:fullscreen {
margin: 0;
padding: 0;
}
img { img {
border-radius: 3mm; border-radius: 3mm;
} }

View File

@@ -19,7 +19,6 @@ import {IonicModule} from '@ionic/angular';
import {AsyncPipe, TitleCasePipe} from '@angular/common'; import {AsyncPipe, TitleCasePipe} from '@angular/common';
import {ThingTranslateModule} from '../../translation/thing-translate.module'; import {ThingTranslateModule} from '../../translation/thing-translate.module';
import {UtilModule} from '../../util/util.module'; import {UtilModule} from '../../util/util.module';
import {FullScreenImageDirective} from '../../util/full-screen-image.directive';
import {IdCardComponent} from './id-card.component'; import {IdCardComponent} from './id-card.component';
import {TranslateModule} from '@ngx-translate/core'; import {TranslateModule} from '@ngx-translate/core';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
@@ -36,7 +35,6 @@ import {Observable} from 'rxjs';
AsyncPipe, AsyncPipe,
ThingTranslateModule, ThingTranslateModule,
UtilModule, UtilModule,
FullScreenImageDirective,
IdCardComponent, IdCardComponent,
TranslateModule, TranslateModule,
TitleCasePipe, TitleCasePipe,

View File

@@ -1,10 +1,11 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {SCIdCard, SCThingOriginType, SCThingType, SCUserConfiguration} from '@openstapps/core'; import {SCIdCard, SCThingOriginType, SCThingType, SCUserConfiguration} from '@openstapps/core';
import {from, Observable, of} from 'rxjs'; import {from, of, Observable} from 'rxjs';
import {AuthHelperService} from '../auth/auth-helper.service'; import {AuthHelperService} from '../auth/auth-helper.service';
import {mergeMap, filter, map, startWith} from 'rxjs/operators'; import {mergeMap, concatWith, filter, map, startWith, catchError, tap} from 'rxjs/operators';
import {ConfigProvider} from '../config/config.provider'; import {ConfigProvider} from '../config/config.provider';
import {HttpClient} from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import {EncryptedStorageProvider} from '../storage/encrypted-storage.provider';
@Injectable({providedIn: 'root'}) @Injectable({providedIn: 'root'})
export class IdCardsProvider { export class IdCardsProvider {
@@ -12,18 +13,27 @@ export class IdCardsProvider {
private authHelper: AuthHelperService, private authHelper: AuthHelperService,
private config: ConfigProvider, private config: ConfigProvider,
private httpClient: HttpClient, private httpClient: HttpClient,
private encryptedStorageProvider: EncryptedStorageProvider,
) {} ) {}
getIdCards(): Observable<SCIdCard[]> { getIdCards(): Observable<SCIdCard[]> {
const feature = this.config.config.app.features.extern?.['idCards']; const feature = this.config.config.app.features.extern?.['idCards'];
const auth = this.authHelper.getProvider(feature?.authProvider ?? 'default'); const auth = this.authHelper.getProvider(feature?.authProvider ?? 'default');
const storedIdCards = from(
this.encryptedStorageProvider.get<SCIdCard[]>('id-cards') as Promise<SCIdCard[]>,
).pipe(filter(it => it !== undefined));
return auth.isAuthenticated$.pipe( return auth.isLoggedIn$.pipe(
mergeMap(isAuthenticated => mergeMap(isLoggedIn =>
isAuthenticated isLoggedIn
? feature ? feature
? from(auth.getValidToken()).pipe( ? storedIdCards.pipe(
concatWith(
from(auth.getValidToken()).pipe(
mergeMap(token => this.fetchIdCards(feature.url, token.accessToken)), mergeMap(token => this.fetchIdCards(feature.url, token.accessToken)),
catchError(() => storedIdCards),
),
),
) )
: auth.user$.pipe( : auth.user$.pipe(
filter(user => user !== undefined), filter(user => user !== undefined),
@@ -31,19 +41,20 @@ export class IdCardsProvider {
mergeMap(user => this.fetchFallbackIdCards(user)), mergeMap(user => this.fetchFallbackIdCards(user)),
startWith([]), startWith([]),
) )
: // TODO: find a better solution here (async pipe stuff...) : of([]).pipe(tap({next: () => this.encryptedStorageProvider.delete('id-cards')})),
of([]),
), ),
); );
} }
private fetchIdCards(url: string, token: string): Observable<SCIdCard[]> { private fetchIdCards(url: string, token: string): Observable<SCIdCard[]> {
return this.httpClient.get<SCIdCard[]>(url, { return this.httpClient
.get<SCIdCard[]>(url, {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}, },
responseType: 'json', responseType: 'json',
}); })
.pipe(tap({next: idCards => this.encryptedStorageProvider.set('id-cards', idCards)}));
} }
private fetchFallbackIdCards(user: SCUserConfiguration): Observable<SCIdCard[]> { private fetchFallbackIdCards(user: SCUserConfiguration): Observable<SCIdCard[]> {

View File

@@ -4,9 +4,10 @@ import {HttpClient} from '@angular/common/http';
import {AuthHelperService} from '../auth/auth-helper.service'; import {AuthHelperService} from '../auth/auth-helper.service';
import {BehaviorSubject, firstValueFrom, of} from 'rxjs'; import {BehaviorSubject, firstValueFrom, of} from 'rxjs';
import {SCAuthorizationProviderType} from '@openstapps/core'; import {SCAuthorizationProviderType} from '@openstapps/core';
import {EncryptedStorageProvider} from '../storage/encrypted-storage.provider';
class FakeAuth { class FakeAuth {
isAuthenticated$ = new BehaviorSubject(false); isLoggedIn$ = new BehaviorSubject(false);
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
getValidToken() {} getValidToken() {}
@@ -16,6 +17,7 @@ describe('IdCards', () => {
let configProvider: ConfigProvider; let configProvider: ConfigProvider;
let httpClient: HttpClient; let httpClient: HttpClient;
let authHelper: AuthHelperService; let authHelper: AuthHelperService;
let encryptedStorageProvider: EncryptedStorageProvider;
let fakeAuth: FakeAuth; let fakeAuth: FakeAuth;
beforeEach(() => { beforeEach(() => {
@@ -27,19 +29,23 @@ describe('IdCards', () => {
fakeAuth = new FakeAuth(); fakeAuth = new FakeAuth();
authHelper = jasmine.createSpyObj('AuthHelperService', ['getProvider']); authHelper = jasmine.createSpyObj('AuthHelperService', ['getProvider']);
authHelper.getProvider = jasmine.createSpy().and.returnValue(fakeAuth); authHelper.getProvider = jasmine.createSpy().and.returnValue(fakeAuth);
encryptedStorageProvider = jasmine.createSpyObj('EncryptedStorageProvider', ['get', 'set', 'delete']);
encryptedStorageProvider.get = jasmine.createSpy().and.resolveTo();
encryptedStorageProvider.set = jasmine.createSpy().and.resolveTo();
encryptedStorageProvider.delete = jasmine.createSpy().and.resolveTo();
}); });
it('should emit undefined if not logged in', async () => { it('should emit undefined if not logged in', async () => {
const provider = new IdCardsProvider(authHelper, configProvider, httpClient); const provider = new IdCardsProvider(authHelper, configProvider, httpClient, encryptedStorageProvider);
expect(await firstValueFrom(provider.getIdCards())).toEqual([]); expect(await firstValueFrom(provider.getIdCards())).toEqual([]);
expect(authHelper.getProvider).toHaveBeenCalledOnceWith('fakeAuth' as SCAuthorizationProviderType); expect(authHelper.getProvider).toHaveBeenCalledOnceWith('fakeAuth' as SCAuthorizationProviderType);
}); });
it('should emit network result when logged in', async () => { it('should emit network result when logged in', async () => {
fakeAuth.isAuthenticated$.next(true); fakeAuth.isLoggedIn$.next(true);
httpClient.get = jasmine.createSpy().and.returnValue(of(['abc'])); httpClient.get = jasmine.createSpy().and.returnValue(of(['abc']));
fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'}); fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'});
const provider = new IdCardsProvider(authHelper, configProvider, httpClient); const provider = new IdCardsProvider(authHelper, configProvider, httpClient, encryptedStorageProvider);
expect(await firstValueFrom(provider.getIdCards())).toEqual(['abc' as never]); expect(await firstValueFrom(provider.getIdCards())).toEqual(['abc' as never]);
expect(authHelper.getProvider).toHaveBeenCalledOnceWith('fakeAuth' as SCAuthorizationProviderType); expect(authHelper.getProvider).toHaveBeenCalledOnceWith('fakeAuth' as SCAuthorizationProviderType);
// eslint-disable-next-line unicorn/no-null // eslint-disable-next-line unicorn/no-null
@@ -52,12 +58,12 @@ describe('IdCards', () => {
}); });
it('should react to logins', async () => { it('should react to logins', async () => {
const provider = new IdCardsProvider(authHelper, configProvider, httpClient); const provider = new IdCardsProvider(authHelper, configProvider, httpClient, encryptedStorageProvider);
const observable = provider.getIdCards(); const observable = provider.getIdCards();
expect(await firstValueFrom(observable)).toEqual([]); expect(await firstValueFrom(observable)).toEqual([]);
httpClient.get = jasmine.createSpy().and.returnValue(of(['abc'])); httpClient.get = jasmine.createSpy().and.returnValue(of(['abc']));
fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'}); fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'});
fakeAuth.isAuthenticated$.next(true); fakeAuth.isLoggedIn$.next(true);
// this is counter-intuitive, but because we unsubscribed above the first value // this is counter-intuitive, but because we unsubscribed above the first value
// will now contain the network result. // will now contain the network result.
expect(await firstValueFrom(observable)).toEqual(['abc' as never]); expect(await firstValueFrom(observable)).toEqual(['abc' as never]);

View File

@@ -6,74 +6,56 @@
* *
* This program is distributed in the hope that it will be useful, but WITHOUT * This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * FITNESS FOR A PARTICULAr purpose. see the gnu general public license for
* more details. * more details.
* *
* You should have received a copy of the GNU General Public License along with * you should have received a copy of the gnu general public license along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. if not, see <https://www.gnu.org/licenses/>.
*/ */
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core'; import {Component, inject, input} from '@angular/core';
import {SCSection} from '../../../../config/profile-page-sections'; import {SCSection} from '../../../../config/profile-page-sections';
import {AuthHelperService} from '../../auth/auth-helper.service'; import {AuthHelperService} from '../../auth/auth-helper.service';
import {Observable} from 'rxjs'; import {mergeMap, of} from 'rxjs';
import {SCAuthorizationProviderType} from '@openstapps/core';
import Swiper from 'swiper'; import Swiper from 'swiper';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; import {toObservable, toSignal} from '@angular/core/rxjs-interop';
@Component({ @Component({
selector: 'stapps-profile-page-section', selector: 'stapps-profile-page-section',
templateUrl: 'profile-page-section.html', templateUrl: 'profile-page-section.html',
styleUrls: ['profile-page-section.scss'], styleUrls: ['profile-page-section.scss'],
}) })
export class ProfilePageSectionComponent implements OnInit { export class ProfilePageSectionComponent {
@Input() item: SCSection; item = input.required<SCSection>();
@Input() minSlideWidth = 110; minSlideWidth = input(110);
isLoggedIn: boolean; authHelper = inject(AuthHelperService);
loggedIn = toSignal(
toObservable(this.item).pipe(
mergeMap(item =>
item.authProvider ? this.authHelper.getProvider(item.authProvider).isLoggedIn$ : of(false),
),
),
);
isEnd = false; isEnd = false;
isBeginning = true; isBeginning = true;
slidesPerView: number; slidesPerView?: number;
slidesFillScreen = false; slidesFillScreen = false;
data: {
[key in SCAuthorizationProviderType]: {loggedIn$: Observable<boolean>};
} = {
default: {
loggedIn$: this.authHelper.getProvider('default').isAuthenticated$,
},
paia: {
loggedIn$: this.authHelper.getProvider('paia').isAuthenticated$,
},
};
destroy$ = inject(DestroyRef);
constructor(private authHelper: AuthHelperService) {}
ngOnInit() {
if (this.item.authProvider) {
this.data[this.item.authProvider].loggedIn$
.pipe(takeUntilDestroyed(this.destroy$))
.subscribe(loggedIn => {
this.isLoggedIn = loggedIn;
});
}
}
activeIndexChange(swiper: Swiper) { activeIndexChange(swiper: Swiper) {
this.isBeginning = swiper.isBeginning; this.isBeginning = swiper.isBeginning;
this.isEnd = swiper.isEnd; this.isEnd = swiper.isEnd;
this.slidesFillScreen = this.slidesPerView >= swiper.slides.length; this.slidesFillScreen = this.slidesPerView! >= swiper.slides.length;
} }
resizeSwiper(resizeEvent: ResizeObserverEntry, swiper: Swiper) { resizeSwiper(resizeEvent: ResizeObserverEntry, swiper: Swiper) {
const slidesPerView = const slidesPerView =
Math.floor((resizeEvent.contentRect.width - this.minSlideWidth / 2) / this.minSlideWidth) + 0.5; Math.floor((resizeEvent.contentRect.width - this.minSlideWidth() / 2) / this.minSlideWidth()) + 0.5;
if (slidesPerView > 1 && slidesPerView !== this.slidesPerView) { if (slidesPerView > 1 && slidesPerView !== this.slidesPerView) {
this.slidesPerView = slidesPerView; this.slidesPerView = slidesPerView;
@@ -84,16 +66,13 @@ export class ProfilePageSectionComponent implements OnInit {
} }
async toggleLogIn() { async toggleLogIn() {
if (!this.item.authProvider) return; const providerType = this.item().authProvider;
await (this.isLoggedIn ? this.signOut(this.item.authProvider) : this.signIn(this.item.authProvider)); if (!providerType) return;
} if (this.loggedIn()) {
async signIn(providerType: SCAuthorizationProviderType) {
await this.authHelper.getProvider(providerType).signIn();
}
async signOut(providerType: SCAuthorizationProviderType) {
await this.authHelper.getProvider(providerType).signOut(); await this.authHelper.getProvider(providerType).signOut();
await this.authHelper.endBrowserSession(providerType); await this.authHelper.endBrowserSession(providerType);
} else {
await this.authHelper.getProvider(providerType).signIn();
}
} }
} }

View File

@@ -13,24 +13,24 @@
~ this program. If not, see <https://www.gnu.org/licenses/>. ~ this program. If not, see <https://www.gnu.org/licenses/>.
--> -->
<stapps-section [title]="'name' | translateSimple: item"> <stapps-section [title]="'name' | translateSimple: item()">
@if (item.authProvider) { @if (item().authProvider) {
<ion-button slot="button-end" fill="clear" color="dark" (click)="toggleLogIn()"> <ion-button slot="button-end" fill="clear" color="dark" (click)="toggleLogIn()">
@if (isLoggedIn) { @if (loggedIn()) {
<ion-icon slot="end" name="logout"></ion-icon> <ion-icon slot="end" name="logout"></ion-icon>
} @else { } @else {
<ion-icon slot="end" name="login"></ion-icon> <ion-icon slot="end" name="login"></ion-icon>
} }
<ion-label>{{ 'profile.buttons.default.log_' + (isLoggedIn ? 'out' : 'in') | translate }}</ion-label> <ion-label>{{ 'profile.buttons.default.log_' + (loggedIn() ? 'out' : 'in') | translate }}</ion-label>
</ion-button> </ion-button>
} }
<simple-swiper> <simple-swiper>
@for (link of item.links; track link) { @for (link of item().links; track link) {
<ion-item <ion-item
lines="none" lines="none"
[routerLink]="link.link" [routerLink]="link.link"
[disabled]="link.needsAuth && !isLoggedIn" [disabled]="link.needsAuth && !loggedIn()"
[detail]="false" [detail]="false"
> >
<div> <div>

View File

@@ -15,7 +15,7 @@
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing'; import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HttpClientTestingModule} from '@angular/common/http/testing'; import {provideHttpClientTesting} from '@angular/common/http/testing';
import {RouterTestingModule} from '@angular/router/testing'; import {RouterTestingModule} from '@angular/router/testing';
import {AuthModule} from '../../auth/auth.module'; import {AuthModule} from '../../auth/auth.module';
import {ProfilePageComponent} from './profile-page.component'; import {ProfilePageComponent} from './profile-page.component';
@@ -27,6 +27,7 @@ import {ScheduleProvider} from '../../calendar/schedule.provider';
import {DataProvider} from '../../data/data.provider'; import {DataProvider} from '../../data/data.provider';
import {StAppsWebHttpClient} from '../../data/stapps-web-http-client.provider'; import {StAppsWebHttpClient} from '../../data/stapps-web-http-client.provider';
import {SimpleBrowser} from '../../../util/browser.factory'; import {SimpleBrowser} from '../../../util/browser.factory';
import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
describe('ProfilePage', () => { describe('ProfilePage', () => {
let component: ProfilePageComponent; let component: ProfilePageComponent;
@@ -47,7 +48,8 @@ describe('ProfilePage', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ProfilePageComponent], declarations: [ProfilePageComponent],
imports: [HttpClientTestingModule, RouterTestingModule, AuthModule, TranslateModule.forRoot()], schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [RouterTestingModule, AuthModule, TranslateModule.forRoot()],
providers: [ providers: [
{provide: ConfigProvider, useValue: configProvider}, {provide: ConfigProvider, useValue: configProvider},
{provide: StorageProvider, useValue: storageProvider}, {provide: StorageProvider, useValue: storageProvider},
@@ -55,8 +57,9 @@ describe('ProfilePage', () => {
{provide: SimpleBrowser, useValue: simpleBrowser}, {provide: SimpleBrowser, useValue: simpleBrowser},
ScheduleProvider, ScheduleProvider,
DataProvider, DataProvider,
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents(); }).compileComponents();
}); });

View File

@@ -0,0 +1,92 @@
import {Injectable} from '@angular/core';
import {StorageProvider} from './storage.provider';
import {Capacitor} from '@capacitor/core';
import {SecureStoragePlugin} from 'capacitor-secure-storage-plugin';
@Injectable({providedIn: 'root'})
export class EncryptedStorageProvider {
constructor(private storageProvider: StorageProvider) {}
/**
* Retrieve a large value from an encrypted storage
* Also returns undefined if a secure context is not available (i.e. web).
* @param key Unique identifier of the wanted resource in storage
* @returns The value of the resource, if found
*/
async get<T>(key: string): Promise<T | undefined> {
if (!Capacitor.isNativePlatform()) return undefined;
try {
const jwt = JSON.parse((await SecureStoragePlugin.get({key: `stapps:key:${key}`})).value);
const aesKey = await crypto.subtle.importKey('jwk', jwt, {name: 'AES-GCM'}, true, [
'encrypt',
'decrypt',
]);
const iv = await this.storageProvider.get<ArrayBuffer>(`encrypted:${key}:iv`);
const encryptedIdCards = await this.storageProvider.get<ArrayBuffer>(`encrypted:${key}`);
const decrypted = await crypto.subtle.decrypt({name: 'AES-GCM', iv}, aesKey, encryptedIdCards);
const decompressionStream = new DecompressionStream('gzip');
const writer = decompressionStream.writable.getWriter();
writer.write(decrypted);
writer.close();
const decompressed = await new Response(decompressionStream.readable).arrayBuffer();
return JSON.parse(new TextDecoder().decode(decompressed));
} catch (error) {
console.warn(error);
return undefined;
}
}
/**
* Store a large value in an encrypted storage
* Does nothing if a secure context is not available (i.e. web).
* @param key Unique identifier of the resource in storage
* @param value The value to store
* @returns A promise that resolves when the value is stored
*/
async set<T>(key: string, value: T): Promise<void> {
if (!Capacitor.isNativePlatform()) return undefined;
try {
const compressionStream = new CompressionStream('gzip');
const writer = compressionStream.writable.getWriter();
writer.write(new TextEncoder().encode(JSON.stringify(value)));
writer.close();
const encoded = await new Response(compressionStream.readable).arrayBuffer();
const iv = crypto.getRandomValues(new Uint8Array(16));
const aesKey = await crypto.subtle.generateKey({name: 'AES-GCM', length: 256}, true, [
'encrypt',
'decrypt',
]);
await Promise.all([
SecureStoragePlugin.set({
key: `stapps:key:${key}`,
value: JSON.stringify(await crypto.subtle.exportKey('jwk', aesKey)),
}),
this.storageProvider.put(`encrypted:${key}:iv`, iv),
]);
this.storageProvider.put<ArrayBuffer>(
`encrypted:${key}`,
await crypto.subtle.encrypt({name: 'AES-GCM', iv}, aesKey, encoded),
);
} catch (error) {
alert(error);
}
}
async delete(key: string): Promise<void> {
if (!Capacitor.isNativePlatform()) return;
await Promise.all([
SecureStoragePlugin.remove({key: `stapps:key:${key}`}),
this.storageProvider.delete(`encrypted:${key}:iv`),
this.storageProvider.delete(`encrypted:${key}`),
]);
}
}

View File

@@ -1,7 +1,7 @@
import {Pipe, PipeTransform} from '@angular/core'; import {Pipe, PipeTransform} from '@angular/core';
import {SCRange, isInRange, SCISO8601DateRange} from '@openstapps/core'; import {SCRange, isInRange, SCISO8601DateRange} from '@openstapps/core';
import {merge, Observable, timer} from 'rxjs'; import {NormalizedInterval, differenceInMilliseconds, interval} from 'date-fns';
import {distinctUntilChanged, map, startWith} from 'rxjs/operators'; import {EMPTY, Observable, SchedulerLike, asyncScheduler, concat, defer, map, of, timer} from 'rxjs';
@Pipe({ @Pipe({
name: 'isInRange', name: 'isInRange',
@@ -14,28 +14,51 @@ export class InRangePipe implements PipeTransform {
} }
} }
export const MIN_DATE = new Date(0);
export const MAX_DATE = new Date(1e15);
// Maximum safe delay for JavaScript timers (~24.8 days)
export const MAX_DELAY = 2 ** 31 - 1;
@Pipe({ @Pipe({
name: 'toDateRange', name: 'rangeToDateInterval',
pure: true, pure: true,
standalone: true, standalone: true,
}) })
export class ToDateRangePipe implements PipeTransform { export class ToDateIntervalPipe implements PipeTransform {
transform(value: SCISO8601DateRange): SCRange<Date> { transform(value: SCISO8601DateRange): NormalizedInterval {
return Object.fromEntries(Object.entries(value).map(([key, value]) => [key, new Date(value)])); return interval(new Date(value.gte ?? value.gt ?? MIN_DATE), new Date(value.lte ?? value.lt ?? MAX_DATE));
} }
} }
/**
* Returns an Observable that will change its value when the current date is within the given interval.
*/
export function isWithinIntervalObservable(
value: NormalizedInterval,
scheduler: SchedulerLike = asyncScheduler,
): Observable<boolean> {
return defer(() => {
const now = scheduler.now();
const activate = differenceInMilliseconds(value.start, now);
const deactivate = differenceInMilliseconds(value.end, now);
return concat(
of(activate <= 0 && deactivate > 0),
activate <= 0 ? EMPTY : timer(value.start, scheduler).pipe(map(() => true)),
differenceInMilliseconds(value.end, now) >= MAX_DELAY || deactivate <= 0
? EMPTY
: timer(value.end, scheduler).pipe(map(() => false)),
);
});
}
@Pipe({ @Pipe({
name: 'isInRangeNow', name: 'dfnsIntervalIsNow',
pure: true, pure: true,
standalone: true, standalone: true,
}) })
export class InRangeNowPipe implements PipeTransform { export class IntervalIsNowPipe implements PipeTransform {
transform(value: SCRange<Date>): Observable<boolean> { transform(value: NormalizedInterval): Observable<boolean> {
return merge(timer(value.lte || value.lt || 0), timer(value.gte || value.gt || 0)).pipe( return isWithinIntervalObservable(value);
startWith(0),
map(() => isInRange(new Date(), value)),
distinctUntilChanged(),
);
} }
} }

View File

@@ -0,0 +1,46 @@
import {TestScheduler} from 'rxjs/testing';
import {MAX_DATE, MIN_DATE, isWithinIntervalObservable} from './in-range.pipe';
import {interval} from 'date-fns';
import {MAX_DELAY} from './in-range.pipe';
/**
* Test macro
*/
function test(range: [number | undefined, number | undefined], subscribe: string, expected: string) {
const testScheduler = new TestScheduler((actual, expected) => {
expect(actual).withContext(actual.map(JSON.stringify).join('\n')).toEqual(expected);
});
it(`should emit "${expected}" when "${subscribe}" for range ${range[0] ?? ''}..${range[1] ?? ''}`, () => {
testScheduler.run(({expectObservable}) => {
expectObservable(
isWithinIntervalObservable(
interval(new Date(range[0] ?? MIN_DATE), new Date(range[1] ?? MAX_DATE)),
testScheduler,
),
subscribe,
).toBe(expected, {t: true, f: false});
});
});
}
describe('isWithinIntervalObservable', () => {
test([500, undefined], '1s ^', '1s (t|)');
test([1000, undefined], '500ms ^', '500ms f 499ms (t|)');
test([undefined, 500], '1s ^', '1s (f|)');
test([undefined, 1000], '500ms ^', '500ms t 499ms (f|)');
test([1000, 2000], '500ms ^', '500ms f 499ms t 999ms (f|)');
test([500, 1000], '1500ms ^', '1500ms (f|)');
test([500, 1000], '1s ^', '1000ms (f|)');
test([500, 1000], '999ms ^', '999ms t (f|)');
test([500, 1000], '500ms ^', '500ms t 499ms (f|)');
test([500, 1000], '499ms ^', '499ms f t 499ms (f|)');
test([500, 1000], '^ 750ms !', 'f 499ms t');
// Long interval test case: emit `true` and then complete (EMPTY) because `end` is beyond the delay limit
test([500, 500 + MAX_DELAY + 2000], '1s ^', '1s (t|)');
});

View File

@@ -1,221 +0,0 @@
## [2.1.1](https://gitlab.com/openstapps/app/compare/v2.0.1...v2.1.1) (2023-05-10)
### Bug Fixes
- autofocus searchbar only when no default data is displayed ([e90286f](https://gitlab.com/openstapps/app/commit/e90286fc6814f5c40af3e297be42f23128b991be))
- breadcrumbs are under parallax ([9e160e8](https://gitlab.com/openstapps/app/commit/9e160e8d1ee9409e4fbe518c9dd9748705e680e1))
- browser logout only if endSession url defined ([7f6de94](https://gitlab.com/openstapps/app/commit/7f6de94ab572be66d7f10758c37dcf10af46b4e0)), closes [#395](https://gitlab.com/openstapps/app/issues/395)
- can't select some list elements on safari 16.4 ([3e99d7f](https://gitlab.com/openstapps/app/commit/3e99d7fa8fcae4538e1afe6a017570b0cb6ff45e))
- canteen view removes item select listener on view exit ([05e996a](https://gitlab.com/openstapps/app/commit/05e996ae9052b11c23dc1093ef526f08e3e2e6b6))
- catalog module semester selection ([afbd1fc](https://gitlab.com/openstapps/app/commit/afbd1fc048997acbc0113f8957016f8947b58626))
- catalog semester selection ([a8c7d5a](https://gitlab.com/openstapps/app/commit/a8c7d5ab5934adf853cc40c1df311178df54057e))
- data detail local favorite fallback causing duplicate nested favorite view ([dff4a95](https://gitlab.com/openstapps/app/commit/dff4a95acc55574b4872b0e4e39555cce0b2fd60))
- data-detail favorite button color leaking to list items ([95f2da2](https://gitlab.com/openstapps/app/commit/95f2da2def39ec56f40a660a63e5f7fddb4b3d53))
- location flow on iOS devices ([f207e02](https://gitlab.com/openstapps/app/commit/f207e029f1b30624bf411a57b3c552ef259e13ed))
- parallax broken since safari 16.4 ([0f970fa](https://gitlab.com/openstapps/app/commit/0f970fa1f164a310e24a85140d8bf0da21e5a5f1))
- remove infinite scroll e2e test ([47565e5](https://gitlab.com/openstapps/app/commit/47565e51b0dda5c8227238b6dc19f0d277408429))
- replace breadcrumb popover with simply expanding the breadcrumbs ([1318cbc](https://gitlab.com/openstapps/app/commit/1318cbca7f0cf72e10d96fff1a1116ba073fe8dc))
- schedule tabs navigating to the wrong url ([abf2ab6](https://gitlab.com/openstapps/app/commit/abf2ab6a5a94941d439adf54ec677332823892db))
- translate simple pipe doesn't update on language changes ([f5ca150](https://gitlab.com/openstapps/app/commit/f5ca1508fb9d95693615bfb9e03bc127bd83be00))
- typo in catalog provider query ([f49c44f](https://gitlab.com/openstapps/app/commit/f49c44f5c53780e4794dc1ef4cbacfb20cabbd97))
- workaround for side menu items not being active on page load ([947cab4](https://gitlab.com/openstapps/app/commit/947cab458ca770f116d28a1f22687ae620e71b59))
### Features
- add content to empty catalogs ([982fb16](https://gitlab.com/openstapps/app/commit/982fb1653b3c1253aac9366733f14c22c94d2537))
- add easy way to configure search filtering for nested properties ([2220ab2](https://gitlab.com/openstapps/app/commit/2220ab24b385188515da7c176bf9c1ac72651fd9))
- dark theme ([e75a466](https://gitlab.com/openstapps/app/commit/e75a46633ca3685d6044eb9a8b2fd670f2cd030f))
- dashboard search rework ([8c30a47](https://gitlab.com/openstapps/app/commit/8c30a47706f07eb222fac47ad7fed61f9044328a))
- implement custom cdk virtual scroll behavior ([968cb72](https://gitlab.com/openstapps/app/commit/968cb729575c529fd6d1d35da1b50a8689057c46))
- optional logout from identity provider ([8cd2d77](https://gitlab.com/openstapps/app/commit/8cd2d777ab3a67b1ab24f03aa50a3ff73550c3d2)), closes [#372](https://gitlab.com/openstapps/app/issues/372)
- revamp dashboard mensa section ([33a74d9](https://gitlab.com/openstapps/app/commit/33a74d96ae92137f53a775e90bff99e5f2d41f6a))
- rework settings page design ([2f1298c](https://gitlab.com/openstapps/app/commit/2f1298c9d7df25d2a16576245ea62c1b6094e507))
- show in-place in date series modal, resolves [#385](https://gitlab.com/openstapps/app/issues/385) ([22e70ae](https://gitlab.com/openstapps/app/commit/22e70ae92b35578b559e6644dccb8d4bfd06af1e)), closes [#386](https://gitlab.com/openstapps/app/issues/386) [#388](https://gitlab.com/openstapps/app/issues/388)
- transition to full sidebar at xl instead of lg ([cc939f3](https://gitlab.com/openstapps/app/commit/cc939f38873833b7cc0260525a2ecd536f27bfa5))
## [2.0.1](https://gitlab.com/openstapps/app/compare/v2.0.0...v2.0.1) (2023-02-13)
### Bug Fixes
- assessment segments can become unreadable ([939fb6e](https://gitlab.com/openstapps/app/commit/939fb6ef0f11b40cb71fbe61da90f50b1f75c3f7))
- login button not easily found ([11d1ac3](https://gitlab.com/openstapps/app/commit/11d1ac3f7ce27c2822ea8f839df3f3dffbd6c020))
- remove misleading assessment calculations ([aefae33](https://gitlab.com/openstapps/app/commit/aefae33d5c9fa9ee3efe346e45429aca79ae3c48))
- right-align add event detail chip ([1eee652](https://gitlab.com/openstapps/app/commit/1eee652533c6b8f613ce09df9c3421f89209419a))
### Features
- offline notice ([9b4caf5](https://gitlab.com/openstapps/app/commit/9b4caf526ffb53ec8d8885342323fcc52fd9fc09))
- separate prettier from eslint ([a88d000](https://gitlab.com/openstapps/app/commit/a88d000ccd6cbdeb5fbb07d209f2491023f9d76c))
# [2.0.0](https://gitlab.com/openstapps/app/compare/v0.0.1...v2.0.0) (2023-01-11)
### Bug Fixes
- daia_url missing in environment configs ([863a3ff](https://gitlab.com/openstapps/app/commit/863a3ffd488425e3313ab9b812c4b6d50c68a244))
- 404 on all surge links ([f1b4930](https://gitlab.com/openstapps/app/commit/f1b4930a3068e73aee20b4c3d71dac551ab60c35))
- add areaServed to person detail ([488150f](https://gitlab.com/openstapps/app/commit/488150f7f5558c05c1ec8a71afcb9f9a37e68a37))
- add event popover expanding beyond screen width ([046a95b](https://gitlab.com/openstapps/app/commit/046a95ba1dca3f5ded7e5555d3167f52f95be107))
- add location info to dates from timetable ([92adb9d](https://gitlab.com/openstapps/app/commit/92adb9dd2db18027dcc327433027e28c81ecbd4b)), closes [#344](https://gitlab.com/openstapps/app/issues/344)
- add missing profile translation ([cdb6ac4](https://gitlab.com/openstapps/app/commit/cdb6ac4084f8704d7f2336387a837b86f78c062b))
- add nav button to schedule page ([e628f39](https://gitlab.com/openstapps/app/commit/e628f396e22e51da2c9f2489fe89e42ccf474e2b))
- add openingHours config and catch its errors ([6125d43](https://gitlab.com/openstapps/app/commit/6125d43e8c18f2bf2afda67c0ff72e00d98ab34f))
- add PKCE parameters for PAIA auth ([f3e83bf](https://gitlab.com/openstapps/app/commit/f3e83bfcc88423f0935a060ccd0bf6198da58351))
- address late init from ionic components ([0bce9e5](https://gitlab.com/openstapps/app/commit/0bce9e5452fc5d05123756348dc30308de675bfa))
- adjust code to overcome the breaking changes (ionic 4 to 5) ([f779042](https://gitlab.com/openstapps/app/commit/f7790426cd2da4a6b33e2aa73783943f45b3de02)), closes [#70](https://gitlab.com/openstapps/app/issues/70)
- adjust hebis catalog templates ([83d9a4a](https://gitlab.com/openstapps/app/commit/83d9a4a8b8fe5b8687c72a717b3a2964524006e0))
- adjust library account user info ([bafabb1](https://gitlab.com/openstapps/app/commit/bafabb1d4ec299e2bea43cd4b8442ef33be2329a)), closes [#331](https://gitlab.com/openstapps/app/issues/331)
- adjust npm docker scripts and typos ([82bb15b](https://gitlab.com/openstapps/app/commit/82bb15b863e2d2e4df20244fda2f2e0d049ff43f))
- angular1 ng-if leftover ([25434d5](https://gitlab.com/openstapps/app/commit/25434d54e3800fd72a6c5d9188fb11f441d73aa9))
- assign navigation app name dynamically ([90b9733](https://gitlab.com/openstapps/app/commit/90b97339d3948b0864f634519416fe4a458b459f))
- background fetch crashing android app ([3316ad9](https://gitlab.com/openstapps/app/commit/3316ad9169ed2b29a2755405589213f824aec9d1))
- calculating SCDateSeries for next unit view ([82ba5f8](https://gitlab.com/openstapps/app/commit/82ba5f81211fb10cc5fde04991856567c4ac9680))
- canteen module layout ([b89f5c4](https://gitlab.com/openstapps/app/commit/b89f5c4edd1ed14f7529edc4e4ea54f9d98fda7c))
- canteen module layout ([6f7c680](https://gitlab.com/openstapps/app/commit/6f7c680ed89f027d863ebc02f5b24895d84f32e4))
- cards not having rounded corners in safari ([8a04a43](https://gitlab.com/openstapps/app/commit/8a04a439032920ead799f8b7483f29b896797c37))
- catch error `Setting "language" not provided` ([584878d](https://gitlab.com/openstapps/app/commit/584878d9503b8406b6ee7ec69dde5b8b3c4fd954))
- CI stage/job setup ([ae429ca](https://gitlab.com/openstapps/app/commit/ae429ca5fb3b5f10cad377d37b251806b3dabf6c))
- config.get issue by updating ionic ([9386351](https://gitlab.com/openstapps/app/commit/93863510fac32ed5b887011175a4807df3f522b8))
- **config:** fix catch ConfigFetchError in getValue ([161da63](https://gitlab.com/openstapps/app/commit/161da630ea59f6205ee53dece950972d4f36ada5)), closes [#46](https://gitlab.com/openstapps/app/issues/46)
- correct data path color ([0d755bc](https://gitlab.com/openstapps/app/commit/0d755bcbd3d6fea59a4f7a59981fd28844ff90d5))
- correct html whitespace handling for icon detection ([9bc3642](https://gitlab.com/openstapps/app/commit/9bc3642990b687dd524470fd26df80351aa85f1e))
- daia availability ([13cee2d](https://gitlab.com/openstapps/app/commit/13cee2d4261c7301c1c763446ae44dcdd005172d))
- dashboard next unit structural directive causing animation issues ([c8f6a27](https://gitlab.com/openstapps/app/commit/c8f6a27c571e51bcc0ac0120968e6bc9a20c8dd7))
- **data:** fix and adjust detail test using translation ([478f49a](https://gitlab.com/openstapps/app/commit/478f49a8744211b3c9458b3dadc791d62a46ae46)), closes [#50](https://gitlab.com/openstapps/app/issues/50)
- **data:** fix template and other consistency issues ([c3bc227](https://gitlab.com/openstapps/app/commit/c3bc227a3ca4a295b2b31bfe7fd34830b33c9f05))
- detail page when dish of mensa selected ([e5c2270](https://gitlab.com/openstapps/app/commit/e5c227073a183a1c562e17f3fe14a51048d01637)), closes [#140](https://gitlab.com/openstapps/app/issues/140)
- download events should respect selection ([28fbfef](https://gitlab.com/openstapps/app/commit/28fbfef18cc3b457f7020a70157ea7a4fff345d0))
- enable background fetch on iOS ([a1592f8](https://gitlab.com/openstapps/app/commit/a1592f84cc48f7cae8c55ef806cddbe806034bb5))
- enable overflow for day labels in schedule ([1195c5f](https://gitlab.com/openstapps/app/commit/1195c5ffc8cea07f1e224d92a7fb25aa5858bf0a))
- encode URI parameter to proxy URI ([bc13cc5](https://gitlab.com/openstapps/app/commit/bc13cc5e1fe6144fe0a0e53c0748a154844a5c29)), closes [#326](https://gitlab.com/openstapps/app/issues/326)
- extend landing period button not working on android ([0caa69c](https://gitlab.com/openstapps/app/commit/0caa69c28cbb2f962b70a1da13659739c1c6dd3e)), closes [#333](https://gitlab.com/openstapps/app/issues/333)
- failing native http requests with body ([fed4f20](https://gitlab.com/openstapps/app/commit/fed4f20c3cf43221512f3d2b6dd3c3fe7a4cf43a))
- feedback not allowing valid emails ([cf74c8e](https://gitlab.com/openstapps/app/commit/cf74c8e19f8bd34a31d5af931781e84be2c04dea)), closes [#349](https://gitlab.com/openstapps/app/issues/349)
- fix issues found by ng build for production ([a503811](https://gitlab.com/openstapps/app/commit/a503811c1cfcf909571af766ccd884856aad8ec9)), closes [#48](https://gitlab.com/openstapps/app/issues/48)
- fix various typos ([ad0dae4](https://gitlab.com/openstapps/app/commit/ad0dae46ff04d28551d2ece950d9a4d3442541d2))
- fixate webdriver to match used chrome version ([24dbb42](https://gitlab.com/openstapps/app/commit/24dbb42b345458b7dbdd17b2759824b3b68cb0e4))
- generate library online links properly ([9854541](https://gitlab.com/openstapps/app/commit/9854541a0c062c31bce167673586dccc8af81785)), closes [#340](https://gitlab.com/openstapps/app/issues/340)
- handle prices as an optional property ([9e71efc](https://gitlab.com/openstapps/app/commit/9e71efca9f7b1086db26f580192d6b349bdcb964)), closes [#219](https://gitlab.com/openstapps/app/issues/219)
- header logo changing size on ios navigate ([38f0a30](https://gitlab.com/openstapps/app/commit/38f0a300769a5b7cda35af0927c17099f93981b8))
- ignore null-island location ([d3188f5](https://gitlab.com/openstapps/app/commit/d3188f50905d610097de6c90bc58e6373d30e0dc)), closes [#149](https://gitlab.com/openstapps/app/issues/149)
- item not available in offer template ([7b402d6](https://gitlab.com/openstapps/app/commit/7b402d61c30aed81a5671d778a38c8393a5bc7c8)), closes [#110](https://gitlab.com/openstapps/app/issues/110)
- library account missing ready for pickup ([e504d8c](https://gitlab.com/openstapps/app/commit/e504d8cf6dd1c12fcb8f6a315527337313662385)), closes [#330](https://gitlab.com/openstapps/app/issues/330)
- library fines should load item title only if needed ([cbb949e](https://gitlab.com/openstapps/app/commit/cbb949e3977a5821e6bd1b654dec66a82e4d8c81)), closes [#342](https://gitlab.com/openstapps/app/issues/342)
- links of timetable tabs ([837c69b](https://gitlab.com/openstapps/app/commit/837c69bb21c92a91259051d5680e1073b390fc0e)), closes [#144](https://gitlab.com/openstapps/app/issues/144)
- logged out button not showing on profile ([ebdc14d](https://gitlab.com/openstapps/app/commit/ebdc14d3c398ac7564c077757c564f4e414fe244)), closes [#239](https://gitlab.com/openstapps/app/issues/239)
- make action chips react to changes of their item ([c0d0b1b](https://gitlab.com/openstapps/app/commit/c0d0b1bd9934e8d9e23f47825cae6a5d8ea2f38a))
- make keyboard dismissable on mobile devices ([b2cc1fd](https://gitlab.com/openstapps/app/commit/b2cc1fd91fc5bd66c994dcbe10771a22d91a1b3e))
- map search on iOS ([7d449b4](https://gitlab.com/openstapps/app/commit/7d449b43d0d86825f711848110ac6f044084eba0)), closes [#148](https://gitlab.com/openstapps/app/issues/148)
- map widget not loading tiles properly ([09aa7bb](https://gitlab.com/openstapps/app/commit/09aa7bb5c59e8d167fa91c69745f5f80229094d7)), closes [#127](https://gitlab.com/openstapps/app/issues/127)
- missing init (config setup) in default auth service ([f16e539](https://gitlab.com/openstapps/app/commit/f16e5394cce5a8019f4dfe367e5e0a2f0cca4ce2)), closes [#227](https://gitlab.com/openstapps/app/issues/227)
- missing translations ([30d801a](https://gitlab.com/openstapps/app/commit/30d801a3b419b382d40d96dda995bd35e493523d))
- modals not reacting after several uses ([f39c29f](https://gitlab.com/openstapps/app/commit/f39c29f10bc05ab986b74dfa8a8648df5fb307b4))
- news module not scrollable on large screens ([44b6a4a](https://gitlab.com/openstapps/app/commit/44b6a4aea008ca6c89f6cb289467429fb3f8c1fa))
- ngx-translate defaultLanguage not set ([581a5b2](https://gitlab.com/openstapps/app/commit/581a5b2e55ceda99cf7c41200366c3c5e09f1c63))
- omit sync native calendar when no uuids ([c9720dc](https://gitlab.com/openstapps/app/commit/c9720dc104cce78ae1a422d5efed7b8a58946836)), closes [#177](https://gitlab.com/openstapps/app/issues/177)
- opening hours pipe refreshing too often ([95e1734](https://gitlab.com/openstapps/app/commit/95e1734d26b175d8d1156abb10863155fed89ec0))
- overhaul auth to avoid issues ([99e8d6c](https://gitlab.com/openstapps/app/commit/99e8d6c9bcbc68b639b035af36bc05de0237b1f9)), closes [#336](https://gitlab.com/openstapps/app/issues/336)
- overview search bar scrolling behind header ([a037090](https://gitlab.com/openstapps/app/commit/a037090eec06e867a703b88a43620a74770287fe))
- performance degradation when scrolling ([f0a45d1](https://gitlab.com/openstapps/app/commit/f0a45d1b8eb2b33a6c68b94ed7f96f81db3a728b))
- person list should use long-inline-text ([8b2b853](https://gitlab.com/openstapps/app/commit/8b2b853942ac76904ff49d940dfef625b0397150))
- prevent multiple heavy setting inits ([f772637](https://gitlab.com/openstapps/app/commit/f7726378f443d9809a6174411d25a811e1d0b5e9))
- prevent opening invalid links ([fdee2db](https://gitlab.com/openstapps/app/commit/fdee2db8a42b8f6c99c4a72b3104ae0ba1a41c5a)), closes [#328](https://gitlab.com/openstapps/app/issues/328)
- profile module items show click effect on scroll ([8b2f2c0](https://gitlab.com/openstapps/app/commit/8b2f2c063c65278580d9469b00d9e67f01caaffb))
- recurring schedule offset and event limit ([9c6b513](https://gitlab.com/openstapps/app/commit/9c6b5131cd4cadb2572769d368b346054f19de1c))
- refresh token not used by default auth provider ([15ccbe4](https://gitlab.com/openstapps/app/commit/15ccbe4c1879417f2fc5849204fa9f6bdcce87fc)), closes [#311](https://gitlab.com/openstapps/app/issues/311)
- remove "extend landing" button when renewal not possible ([f60a228](https://gitlab.com/openstapps/app/commit/f60a22839200019a38586a14a0cce32e2c0029e7)), closes [#338](https://gitlab.com/openstapps/app/issues/338)
- remove item before adding it to secure storage ([ec511fb](https://gitlab.com/openstapps/app/commit/ec511fb8f40219e2559b08c62bd915d773d2a36f))
- schedule navigation bar layout ([e7d5f83](https://gitlab.com/openstapps/app/commit/e7d5f83100f43564b55249909a6658e583e3a9b2))
- set android status bar color correctly ([b38a969](https://gitlab.com/openstapps/app/commit/b38a96996a10e4e43ff1b06ecd2235a0e3bdfa1c))
- setting of default language ([ccf8b1a](https://gitlab.com/openstapps/app/commit/ccf8b1a5cc9fe834ba3e04a1ed67b2d082004403))
- show nothing for empty array properties ([1c56c89](https://gitlab.com/openstapps/app/commit/1c56c891e15b034826b5d6b2b35141fc872c922d)), closes [#154](https://gitlab.com/openstapps/app/issues/154)
- single map place width ([88684f0](https://gitlab.com/openstapps/app/commit/88684f068ab130f43e520c87f1d7383e0ae43944)), closes [#147](https://gitlab.com/openstapps/app/issues/147)
- some android devices don't support implied css animation units ([54cc883](https://gitlab.com/openstapps/app/commit/54cc8838aefe8f8c2d25d9228a136ef727a08230))
- status bar being black on Android 13 devices ([feee9e8](https://gitlab.com/openstapps/app/commit/feee9e8db90e66cf2346f7c5cc24f075eb70676c))
- suppress router event logs in console ([28caaf1](https://gitlab.com/openstapps/app/commit/28caaf1d21f7961b678cf339a712abf860ade5e7)), closes [#207](https://gitlab.com/openstapps/app/issues/207)
- swap missing icon after ionic update ([643b6c9](https://gitlab.com/openstapps/app/commit/643b6c967f3268cb305a24d614c3bc91275b0ac3))
- temporary disable flaky ui test ([6b9b1fa](https://gitlab.com/openstapps/app/commit/6b9b1fa8548d5c5fca04b2c1d63e893de39278a2))
- timetable dates cannot be removed ([9242438](https://gitlab.com/openstapps/app/commit/924243813207fa791d3c4938f8653a999b6382ff))
- translate back button title ([b8db0f3](https://gitlab.com/openstapps/app/commit/b8db0f3e70a46f2b493e183a244cb29d1954c257))
- translations ([5e1a902](https://gitlab.com/openstapps/app/commit/5e1a902d4c0d2345f21500fba5394c1907e04eb8))
- typo in translation ([7928534](https://gitlab.com/openstapps/app/commit/7928534d88a26db28b098bbceb47bc1103022a57))
- update core and apply stricter tslint rules ([911492d](https://gitlab.com/openstapps/app/commit/911492d064ff0280dd6626244cd8038cbfc0f408))
- use HashLocationStrategy for routes ([9d68212](https://gitlab.com/openstapps/app/commit/9d682125db55c87cab2b33c7633bfa133d2fc5a9)), closes [#54](https://gitlab.com/openstapps/app/issues/54)
- use localized date and time ([6ca0b97](https://gitlab.com/openstapps/app/commit/6ca0b9763761c5204a757a243056a087c5f35fd9))
- use stapps core version to compare with backends' core version ([66b8720](https://gitlab.com/openstapps/app/commit/66b8720da0f264824a396f2d9e598b0e48c8e3d1)), closes [#77](https://gitlab.com/openstapps/app/issues/77)
- user info card ([998edcb](https://gitlab.com/openstapps/app/commit/998edcb5cdfb588c2986f466f4a2951f172a8bb4)), closes [#305](https://gitlab.com/openstapps/app/issues/305)
### Features
- add "no results" screen to search ([c75ca68](https://gitlab.com/openstapps/app/commit/c75ca68c440a20e197213ecbb47d05fc293afd9c))
- add about module ([d420008](https://gitlab.com/openstapps/app/commit/d42000892694f4a3b29fa623c43fb45f8ba54687))
- add action chips to search results ([67fb4a4](https://gitlab.com/openstapps/app/commit/67fb4a43c95043caba50d43ace2ab276f3319b81))
- add auth support (default and paia) ([b5f239e](https://gitlab.com/openstapps/app/commit/b5f239ea4edebd0d27b1cdaad4a830998ce2f681))
- add backend toggle ([c1d3330](https://gitlab.com/openstapps/app/commit/c1d33303aa11da3b3e150c6717d77ef484a16ac1))
- add basic templates for data list items ([3e697b1](https://gitlab.com/openstapps/app/commit/3e697b17b4448c15781d0f6dd55577b638e9d974))
- add catalog module ([03084b1](https://gitlab.com/openstapps/app/commit/03084b1c966de98b3723d0bee2b2475589393c59))
- add ConfigModule and FakeBackendInterceptor ([406f400](https://gitlab.com/openstapps/app/commit/406f40055567bfde4ec5edf26cff942411bd073e)), closes [#34](https://gitlab.com/openstapps/app/issues/34) [#37](https://gitlab.com/openstapps/app/issues/37)
- add detail view for news ([2566a71](https://gitlab.com/openstapps/app/commit/2566a71a81a3408dbb16b97d3a453d95e25d1f00))
- add duration pipe with frequency capabilites ([49a1758](https://gitlab.com/openstapps/app/commit/49a1758da358958ffe590700c19aaf90ec748ee5))
- add favorites support ([e9452d6](https://gitlab.com/openstapps/app/commit/e9452d6520c34f6513623c16e291dc23d6ea9757))
- add feedback module ([867f9e9](https://gitlab.com/openstapps/app/commit/867f9e9b832e3bd54c04801fef63a11543e8f3dd))
- add filter chips for news ([5435f85](https://gitlab.com/openstapps/app/commit/5435f85cc43dc3baa774a5008d2920ac7b3783f6))
- add formatting pipes basted on Intl ([4b932af](https://gitlab.com/openstapps/app/commit/4b932af1c07e1af4369414667a987d31181c073c))
- add HeBIS HDS search ([9a3241c](https://gitlab.com/openstapps/app/commit/9a3241c42ab59e15c0084178f76dc4a2450a2bb8))
- add library account screens ([080e6fa](https://gitlab.com/openstapps/app/commit/080e6fa3e8c18e9608d7fa2efc95e4fd65c43a15))
- add library action confirmations ([42b860e](https://gitlab.com/openstapps/app/commit/42b860e41793fc3983a39237a4f7128416485fae)), closes [#334](https://gitlab.com/openstapps/app/issues/334)
- add logger ([a0c798f](https://gitlab.com/openstapps/app/commit/a0c798f765d87c5eebcbed65b70f3b05f285d0ce))
- add map module ([c1c9a92](https://gitlab.com/openstapps/app/commit/c1c9a92ec900403218b887fdebfe5132b232e1e0))
- add new font and new icons ([915fd72](https://gitlab.com/openstapps/app/commit/915fd72bd4bfed16e15fcc3c57879db0ec0379e2))
- add not found screen ([e3d9ef4](https://gitlab.com/openstapps/app/commit/e3d9ef40ccd626c81c67ea2d790eecbe6e025780))
- add permission check for geoLocation setting ([d5fa2fd](https://gitlab.com/openstapps/app/commit/d5fa2fd9a578d48cd2513eeb1380f1d2bc4d3754))
- add service and pipe for core translator ([4565600](https://gitlab.com/openstapps/app/commit/456560026cc9550a10a9f42657d942122be34d53))
- apply new layout overhaul ([7bbdba5](https://gitlab.com/openstapps/app/commit/7bbdba5c0b886e2789d2a603c4be627dfd16b60e))
- assessment tree view ([0b037f9](https://gitlab.com/openstapps/app/commit/0b037f96e634b412fbaaee24747df08afdc0e565))
- assessments module ([e68d1b7](https://gitlab.com/openstapps/app/commit/e68d1b73f94b36abcefe9b2bf42e98de00b69188))
- calendar plugin ([a57c302](https://gitlab.com/openstapps/app/commit/a57c3029df61ac3157c856744380a85dc001abc6))
- dashboard ui tests ([9f8ab5c](https://gitlab.com/openstapps/app/commit/9f8ab5c7a15a918f7bd05423f0a43f22a33d9228))
- **data:** add basic methods of data provider ([ffe05e4](https://gitlab.com/openstapps/app/commit/ffe05e4548fc399183ef651047cb02a3cdc80c67)), closes [#1](https://gitlab.com/openstapps/app/issues/1)
- **data:** add data detail templates ([5855acc](https://gitlab.com/openstapps/app/commit/5855accc169579d87f5779fd602d4f00f2b479a1))
- **data:** add method that checks if data item has been saved ([017fc67](https://gitlab.com/openstapps/app/commit/017fc67765bdb75542045830fb85f8eb4b899485))
- **data:** implement basic facets handling ([b6f92a7](https://gitlab.com/openstapps/app/commit/b6f92a74494e1a39d5c828d593eb70c7002bb0f6)), closes [#1](https://gitlab.com/openstapps/app/issues/1)
- **data:** show skeleton screens before data is loaded ([e1039aa](https://gitlab.com/openstapps/app/commit/e1039aa2260a0e9d8ccca5a18ded480bf2f12530)), closes [#4](https://gitlab.com/openstapps/app/issues/4)
- **data:** use data provider for list and detail view ([7caaa18](https://gitlab.com/openstapps/app/commit/7caaa18b7eb94ed8e4ff4e54d760ba1dba3ae3be))
- **data:** use general template for all offers ([58960a2](https://gitlab.com/openstapps/app/commit/58960a29ea0cd73f26a43186b41f5b53864810e0))
- display availability and item data for library items ([d571b1d](https://gitlab.com/openstapps/app/commit/d571b1dbe59f8e2270d88dd050b94283bf0a7056))
- dynamic news page rows ([848d257](https://gitlab.com/openstapps/app/commit/848d2574c7046d6f84b91e32aa83ca57fca8ad2e))
- expandable accordion in grades module ([3f81afd](https://gitlab.com/openstapps/app/commit/3f81afda82cf87bb393fbb3b71d27eee77ae42d9))
- get tab navigation items from config ([c3130a3](https://gitlab.com/openstapps/app/commit/c3130a392a53c9ec3657e24a53ed0b3333ba162b))
- Implement variable for styling ([8ecf347](https://gitlab.com/openstapps/app/commit/8ecf347c9a8abac9347dca45e99ebe35eca8f252))
- include font licenses in the licenses section ([82479f4](https://gitlab.com/openstapps/app/commit/82479f463cbee834507a0c8faf895a729631eb06))
- Ionic v6 breadcrumbs in catalog module ([7b491ed](https://gitlab.com/openstapps/app/commit/7b491ed3bb5466a845493bd8ea0bbb836a4f03d2))
- lazy load all news ([e48134e](https://gitlab.com/openstapps/app/commit/e48134eddcd1ca4d5ec5dbf910571e33a3311ba1))
- **menu:** add context menu ([1dbf451](https://gitlab.com/openstapps/app/commit/1dbf4515fe57cc8250a7fa2213ced682e3e5e0fc)), closes [#3](https://gitlab.com/openstapps/app/issues/3)
- navigation rail ([6b08af6](https://gitlab.com/openstapps/app/commit/6b08af6a746bf12005d3297ec97c130e84477615))
- news module ([22cd0af](https://gitlab.com/openstapps/app/commit/22cd0af1bf92a4576316f5510c22f012e085a237))
- profile page sections ([e395e9d](https://gitlab.com/openstapps/app/commit/e395e9d270f41bd4f6e5ecd88e438a28dde92465)), closes [#233](https://gitlab.com/openstapps/app/issues/233) [#261](https://gitlab.com/openstapps/app/issues/261) [#267](https://gitlab.com/openstapps/app/issues/267)
- refresh on pull for news module ([1f3d9ad](https://gitlab.com/openstapps/app/commit/1f3d9ad5f0c0557add2dcf242cf0b7dd7685bb1b))
- resume at origin path after login ([a5e5a5b](https://gitlab.com/openstapps/app/commit/a5e5a5b40799e7505557f7ebd764b9c563be0f4b)), closes [#168](https://gitlab.com/openstapps/app/issues/168)
- schedule layout ([e416590](https://gitlab.com/openstapps/app/commit/e4165901bb5efa6b38e397cdf5d66138e396d7f2))
- scroll schedule cursor into view ([bc4c3d7](https://gitlab.com/openstapps/app/commit/bc4c3d78dbd906243dcddac4db9ac8ccaca79056))
- search url query param handling ([f349bd7](https://gitlab.com/openstapps/app/commit/f349bd72335c47d292d0a007b1a4ce7f5c694a61))
- seperate dishes by menu sections ([400c6b8](https://gitlab.com/openstapps/app/commit/400c6b8d8c5300035862186096e38883f781d297))
- show availability in offers ([5fdef95](https://gitlab.com/openstapps/app/commit/5fdef95c065e7b467a13045adbb56a641cb2dc12))
- show feedback when map search yields no results ([c54ea86](https://gitlab.com/openstapps/app/commit/c54ea867bd64af187446dbf8dc03967aae600961))
- show menu for multiple days for canteens and cafes ([3c079cd](https://gitlab.com/openstapps/app/commit/3c079cd189e3b75d3b203bd46ab57378f99e88cc)), closes [#19](https://gitlab.com/openstapps/app/issues/19) [#79](https://gitlab.com/openstapps/app/issues/79)
- **storage:** add search using regex ([86b9bff](https://gitlab.com/openstapps/app/commit/86b9bff09a51b17151efa5ec322cddcc50a54cb2))
- **storage:** support deletion of multiple entries ([63266f5](https://gitlab.com/openstapps/app/commit/63266f588f6ddb2476e2cea4bab42a3864f77470))
- **storage:** support search using a string ([4334cad](https://gitlab.com/openstapps/app/commit/4334cad68c7d0abb7443e245b6eb983804547925))
- support deep links ([2e3f668](https://gitlab.com/openstapps/app/commit/2e3f6684ef5fbac8e4fb127c536b2b438399ce37))
- support overlapping timetable dates ([93c37b2](https://gitlab.com/openstapps/app/commit/93c37b26cca7764dea66fb12c0e53acc8fd78d2b))
- tab navigation bar animations and state ([7ecba0b](https://gitlab.com/openstapps/app/commit/7ecba0b7819ae5a7ab32d86f6049de0ab6c68e55))
- timetable module - schedule and calendar ([d8ede00](https://gitlab.com/openstapps/app/commit/d8ede006dfbd613dfbc752eb6de80db2e7e84531))
- turn on oauth2 state check for PAIA ([5bd0b50](https://gitlab.com/openstapps/app/commit/5bd0b50816973548a0a4aa5dbed3f2d0902590dd)), closes [#172](https://gitlab.com/openstapps/app/issues/172)
- use http interceptor for backendless development ([2558163](https://gitlab.com/openstapps/app/commit/2558163ad6c3038445a79e6338f1827cb1e6510e)), closes [#37](https://gitlab.com/openstapps/app/issues/37)
- use school-neutral news image fallback via css ([bb94c71](https://gitlab.com/openstapps/app/commit/bb94c71761ca5d3bf6639476ba0c143678cfabbd))
- webpack bundle analyzer ([552911c](https://gitlab.com/openstapps/app/commit/552911cfcfecec8d9be8b8bbb155b597d1f70fa2))
## [0.0.1](https://gitlab.com/openstapps/app/compare/8b23159e678773b08252bc5826510de2a302fa1d...v0.0.1) (2018-12-11)
### Features
- add the app ([8b23159](https://gitlab.com/openstapps/app/commit/8b23159e678773b08252bc5826510de2a302fa1d))

View File

@@ -29,7 +29,10 @@
"TITLE_COPIED": "In die Zwischenablage kopiert" "TITLE_COPIED": "In die Zwischenablage kopiert"
}, },
"about": { "about": {
"VERSION_INFO": "{{name}} {{version}} basierend auf StApps {{stappsVersion}}" "VERSION_INFO": "{{name}} {{version}} basierend auf StApps {{stappsVersion}}",
"CHANGELOG": "Versionshistorie",
"TECHNICAL_CHANGELOG": "StApps Basis Versionshistorie",
"TECHNICAL_CHANGELOG_DISCLAIMER": "Die StApps Basis ist anders versioniert als die App. Versionsnummern zwischen StApps und App weichen daher ab."
}, },
"releaseNotes": { "releaseNotes": {
"TITLE_UPDATED": "Deine App wurde aktualisiert!" "TITLE_UPDATED": "Deine App wurde aktualisiert!"
@@ -336,11 +339,15 @@
"title": "Titel", "title": "Titel",
"about": "Mehr Informationen", "about": "Mehr Informationen",
"label": "Signatur", "label": "Signatur",
"starttime": "Vorgemerkt am",
"endtime": "Abzuholen bis", "endtime": "Abzuholen bis",
"storage": "Abholbereit" "storage": "Abholtheke",
"queue": "Position in der Warteschlange"
}, },
"holds": "Bestellungen", "holds": "Bestellungen",
"reservations": "Vormerkungen" "reservations": "Vormerkungen",
"ordered": "Bestellt",
"ready": "Abholbereit"
}, },
"checked_out": { "checked_out": {
"title": "Deine Ausleihen", "title": "Deine Ausleihen",
@@ -348,7 +355,7 @@
"title": "Titel", "title": "Titel",
"about": "Mehr Informationen", "about": "Mehr Informationen",
"label": "Signatur", "label": "Signatur",
"endtime": "Leihfristende", "duedate": "Leihfristende",
"renewals": "Verlängerungen" "renewals": "Verlängerungen"
} }
}, },

View File

@@ -29,7 +29,10 @@
"TITLE_COPIED": "Copied to clipboard" "TITLE_COPIED": "Copied to clipboard"
}, },
"about": { "about": {
"VERSION_INFO": "{{name}} {{version}} based on StApps {{stappsVersion}}" "VERSION_INFO": "{{name}} {{version}} based on StApps {{stappsVersion}}",
"CHANGELOG": "Changelog",
"TECHNICAL_CHANGELOG": "StApps Base Changelog",
"TECHNICAL_CHANGELOG_DISCLAIMER": "The StApps base is versioned separately from the app. Version numbers will not match those of the app itself."
}, },
"releaseNotes": { "releaseNotes": {
"TITLE_UPDATED": "Your app was updated!" "TITLE_UPDATED": "Your app was updated!"
@@ -336,11 +339,15 @@
"title": "Title", "title": "Title",
"about": "More information", "about": "More information",
"label": "Shelfmark", "label": "Shelfmark",
"starttime": "Reserved on",
"endtime": "Available for pickup until", "endtime": "Available for pickup until",
"storage": "Available for pickup" "storage": "Pickup counter",
"queue": "Position in the queue"
}, },
"holds": "orders", "holds": "orders",
"reservations": "reservations" "reservations": "reservations",
"ordered": "Ordered",
"ready": "Ready for pickup"
}, },
"checked_out": { "checked_out": {
"title": "checked out items", "title": "checked out items",
@@ -348,7 +355,7 @@
"title": "Title", "title": "Title",
"about": "More information", "about": "More information",
"label": "Label", "label": "Label",
"endtime": "Due date", "duedate": "Due date",
"renewals": "Renewals" "renewals": "Renewals"
} }
}, },

Some files were not shown because too many files have changed in this diff Show More