merge main

This commit is contained in:
martabal 2023-11-30 22:42:16 +01:00
commit 089fa7a550
No known key found for this signature in database
GPG key ID: C00196E3148A52BD
275 changed files with 7348 additions and 2186 deletions

View file

@ -35,7 +35,7 @@ jobs:
with: with:
ref: ${{ steps.get-ref.outputs.ref }} ref: ${{ steps.get-ref.outputs.ref }}
- uses: actions/setup-java@v3 - uses: actions/setup-java@v4
with: with:
distribution: "zulu" distribution: "zulu"
java-version: "12.x" java-version: "12.x"

View file

@ -1,4 +1,4 @@
name: Clean up actions cache on PR close name: Cache Cleanup
on: on:
pull_request: pull_request:
types: types:
@ -10,6 +10,7 @@ concurrency:
jobs: jobs:
cleanup: cleanup:
name: Cleanup
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out code - name: Check out code

View file

@ -1,9 +1,10 @@
name: Publish Package to npmjs name: CLI Release
on: on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
publish: publish:
name: Publish
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:

View file

@ -5,7 +5,7 @@
# #
# This workflow will not trigger runs on forked repos. # This workflow will not trigger runs on forked repos.
name: Cleanup Old Docker Images name: Docker Cleanup
on: on:
pull_request: pull_request:

View file

@ -1,4 +1,4 @@
name: Build and Push Docker Images name: Docker
on: on:
workflow_dispatch: workflow_dispatch:
@ -18,6 +18,7 @@ permissions:
jobs: jobs:
build_and_push: build_and_push:
name: Build and Push
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
# Prevent a failure in one image from stopping the other builds # Prevent a failure in one image from stopping the other builds

View file

@ -11,7 +11,7 @@ concurrency:
jobs: jobs:
e2e-tests: e2e-tests:
name: Run end-to-end test suites name: Server (e2e)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -24,7 +24,7 @@ jobs:
run: docker compose -f ./docker/docker-compose.test.yml up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server --remove-orphans --build run: docker compose -f ./docker/docker-compose.test.yml up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server --remove-orphans --build
doc-tests: doc-tests:
name: Run documentation checks name: Docs
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@ -45,8 +45,12 @@ jobs:
run: npm run check run: npm run check
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Run build
run: npm run build
if: ${{ !cancelled() }}
server-unit-tests: server-unit-tests:
name: Run server unit test suites and checks name: Server
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@ -76,7 +80,7 @@ jobs:
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
cli-unit-tests: cli-unit-tests:
name: Run cli test suites name: CLI
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@ -106,7 +110,7 @@ jobs:
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
web-unit-tests: web-unit-tests:
name: Run web unit test suites and checks name: Web
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@ -140,7 +144,7 @@ jobs:
# if: ${{ !cancelled() }} # if: ${{ !cancelled() }}
mobile-unit-tests: mobile-unit-tests:
name: Run mobile unit tests name: Mobile
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -154,7 +158,7 @@ jobs:
run: flutter test -j 1 run: flutter test -j 1
ml-unit-tests: ml-unit-tests:
name: Run ML unit tests and checks name: Machine Learning
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@ -184,7 +188,7 @@ jobs:
poetry run pytest --cov app poetry run pytest --cov app
generated-api-up-to-date: generated-api-up-to-date:
name: Check generated files are up-to-date name: OpenAPI Clients
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -205,11 +209,11 @@ jobs:
exit 1 exit 1
generated-typeorm-migrations-up-to-date: generated-typeorm-migrations-up-to-date:
name: Check generated TypeORM migrations are up-to-date name: TypeORM Checks
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
postgres: postgres:
image: postgres image: postgres@sha256:71da05df8c4f1e1bac9b92ebfba2a0eeb183f6ac6a972fd5e55e8146e29efe9c
env: env:
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres POSTGRES_USER: postgres
@ -232,7 +236,7 @@ jobs:
- name: Install server dependencies - name: Install server dependencies
run: npm ci run: npm ci
- name: Build the - name: Build the app
run: npm run build run: npm run build
- name: Run existing migrations - name: Run existing migrations
@ -248,13 +252,30 @@ jobs:
with: with:
files: | files: |
server/src/infra/migrations/ server/src/infra/migrations/
- name: Verify files have not changed - name: Verify migration files have not changed
if: steps.verify-changed-files.outputs.files_changed == 'true' if: steps.verify-changed-files.outputs.files_changed == 'true'
run: | run: |
echo "ERROR: Generated files not up to date!" echo "ERROR: Generated migration files not up to date!"
echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}" echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}"
exit 1 exit 1
- name: Run SQL generation
run: npm run sql:generate
- name: Find file changes
uses: tj-actions/verify-changed-files@v13.1
id: verify-changed-sql-files
with:
files: |
server/src/infra/sql
- name: Verify SQL files have not changed
if: steps.verify-changed-sql-files.outputs.files_changed == 'true'
run: |
echo "ERROR: Generated SQL files not up to date!"
echo "Changed files: ${{ steps.verify-changed-sql-files.outputs.changed_files }}"
exit 1
# mobile-integration-tests: # mobile-integration-tests:
# name: Run mobile end-to-end integration tests # name: Run mobile end-to-end integration tests
# runs-on: macos-latest # runs-on: macos-latest

View file

@ -26,7 +26,10 @@ prod-scale:
docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
api: api:
cd ./server && npm run api:generate npm --prefix server run api:generate
sql:
npm --prefix server run sql:generate
attach-server: attach-server:
docker exec -it docker_immich-server_1 sh docker exec -it docker_immich-server_1 sh

604
cli/package-lock.json generated
View file

@ -29,8 +29,8 @@
"@types/mime-types": "^2.1.1", "@types/mime-types": "^2.1.1",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/node": "^20.3.1", "@types/node": "^20.3.1",
"@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^5.48.1", "@typescript-eslint/parser": "^6.0.0",
"chai": "^4.3.7", "chai": "^4.3.7",
"eslint": "^8.43.0", "eslint": "^8.43.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
@ -46,7 +46,7 @@
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tslib": "^2.5.3", "tslib": "^2.5.3",
"typescript": "^4.9.4" "typescript": "^5.0.0"
} }
}, },
"node_modules/@aashutoshrathi/word-wrap": { "node_modules/@aashutoshrathi/word-wrap": {
@ -1604,9 +1604,9 @@
} }
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.9.4", "version": "20.10.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.4.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz",
"integrity": "sha512-wmyg8HUhcn6ACjsn8oKYjkN/zUzQeNtMy44weTJSM6p4MMzEOuKbA3OjJ267uPCOW7Xex9dyrNTful8XTQYoDA==", "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
@ -1646,32 +1646,33 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.62.0", "version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz",
"integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.4.0", "@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/scope-manager": "6.13.1",
"@typescript-eslint/type-utils": "5.62.0", "@typescript-eslint/type-utils": "6.13.1",
"@typescript-eslint/utils": "5.62.0", "@typescript-eslint/utils": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.2.0", "ignore": "^5.2.4",
"natural-compare-lite": "^1.4.0", "natural-compare": "^1.4.0",
"semver": "^7.3.7", "semver": "^7.5.4",
"tsutils": "^3.21.0" "ts-api-utils": "^1.0.1"
}, },
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^16.0.0 || >=18.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" "eslint": "^7.0.0 || ^8.0.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"typescript": { "typescript": {
@ -1679,26 +1680,126 @@
} }
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz",
"integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz",
"integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz",
"integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz",
"integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.13.1",
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/typescript-estree": "6.13.1",
"semver": "^7.5.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz",
"integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.1",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "5.62.0", "version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz",
"integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/scope-manager": "6.13.1",
"@typescript-eslint/types": "5.62.0", "@typescript-eslint/types": "6.13.1",
"@typescript-eslint/typescript-estree": "5.62.0", "@typescript-eslint/typescript-estree": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^16.0.0 || >=18.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" "eslint": "^7.0.0 || ^8.0.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"typescript": { "typescript": {
@ -1706,6 +1807,80 @@
} }
} }
}, },
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz",
"integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz",
"integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz",
"integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz",
"integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.1",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "5.62.0", "version": "5.62.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz",
@ -1724,25 +1899,25 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "5.62.0", "version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz",
"integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "5.62.0", "@typescript-eslint/typescript-estree": "6.13.1",
"@typescript-eslint/utils": "5.62.0", "@typescript-eslint/utils": "6.13.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"tsutils": "^3.21.0" "ts-api-utils": "^1.0.1"
}, },
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^16.0.0 || >=18.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "*" "eslint": "^7.0.0 || ^8.0.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"typescript": { "typescript": {
@ -1750,6 +1925,105 @@
} }
} }
}, },
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz",
"integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz",
"integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz",
"integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz",
"integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.13.1",
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/typescript-estree": "6.13.1",
"semver": "^7.5.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz",
"integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.1",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "5.62.0", "version": "5.62.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz",
@ -4864,12 +5138,6 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true "dev": true
}, },
"node_modules/natural-compare-lite": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
"dev": true
},
"node_modules/node-int64": { "node_modules/node-int64": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@ -6023,6 +6291,18 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/ts-api-utils": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
"integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==",
"dev": true,
"engines": {
"node": ">=16.13.0"
},
"peerDependencies": {
"typescript": ">=4.2.0"
}
},
"node_modules/ts-jest": { "node_modules/ts-jest": {
"version": "29.1.1", "version": "29.1.1",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz",
@ -6170,16 +6450,16 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "4.9.5", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
"dev": true, "dev": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
}, },
"engines": { "engines": {
"node": ">=4.2.0" "node": ">=14.17"
} }
}, },
"node_modules/undici-types": { "node_modules/undici-types": {
@ -7710,9 +7990,9 @@
} }
}, },
"@types/node": { "@types/node": {
"version": "20.9.4", "version": "20.10.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.4.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz",
"integrity": "sha512-wmyg8HUhcn6ACjsn8oKYjkN/zUzQeNtMy44weTJSM6p4MMzEOuKbA3OjJ267uPCOW7Xex9dyrNTful8XTQYoDA==", "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
@ -7752,33 +8032,136 @@
"dev": true "dev": true
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "5.62.0", "version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz",
"integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@eslint-community/regexpp": "^4.4.0", "@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/scope-manager": "6.13.1",
"@typescript-eslint/type-utils": "5.62.0", "@typescript-eslint/type-utils": "6.13.1",
"@typescript-eslint/utils": "5.62.0", "@typescript-eslint/utils": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.2.0", "ignore": "^5.2.4",
"natural-compare-lite": "^1.4.0", "natural-compare": "^1.4.0",
"semver": "^7.3.7", "semver": "^7.5.4",
"tsutils": "^3.21.0" "ts-api-utils": "^1.0.1"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz",
"integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1"
}
},
"@typescript-eslint/types": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz",
"integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz",
"integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
}
},
"@typescript-eslint/utils": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz",
"integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.13.1",
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/typescript-estree": "6.13.1",
"semver": "^7.5.4"
}
},
"@typescript-eslint/visitor-keys": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz",
"integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.13.1",
"eslint-visitor-keys": "^3.4.1"
}
}
} }
}, },
"@typescript-eslint/parser": { "@typescript-eslint/parser": {
"version": "5.62.0", "version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz",
"integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/scope-manager": "6.13.1",
"@typescript-eslint/types": "5.62.0", "@typescript-eslint/types": "6.13.1",
"@typescript-eslint/typescript-estree": "5.62.0", "@typescript-eslint/typescript-estree": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4" "debug": "^4.3.4"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz",
"integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1"
}
},
"@typescript-eslint/types": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz",
"integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz",
"integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
}
},
"@typescript-eslint/visitor-keys": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz",
"integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.13.1",
"eslint-visitor-keys": "^3.4.1"
}
}
} }
}, },
"@typescript-eslint/scope-manager": { "@typescript-eslint/scope-manager": {
@ -7792,15 +8175,73 @@
} }
}, },
"@typescript-eslint/type-utils": { "@typescript-eslint/type-utils": {
"version": "5.62.0", "version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz",
"integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/typescript-estree": "5.62.0", "@typescript-eslint/typescript-estree": "6.13.1",
"@typescript-eslint/utils": "5.62.0", "@typescript-eslint/utils": "6.13.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"tsutils": "^3.21.0" "ts-api-utils": "^1.0.1"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz",
"integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1"
}
},
"@typescript-eslint/types": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz",
"integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz",
"integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
}
},
"@typescript-eslint/utils": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz",
"integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.13.1",
"@typescript-eslint/types": "6.13.1",
"@typescript-eslint/typescript-estree": "6.13.1",
"semver": "^7.5.4"
}
},
"@typescript-eslint/visitor-keys": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz",
"integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.13.1",
"eslint-visitor-keys": "^3.4.1"
}
}
} }
}, },
"@typescript-eslint/types": { "@typescript-eslint/types": {
@ -10035,12 +10476,6 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true "dev": true
}, },
"natural-compare-lite": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
"dev": true
},
"node-int64": { "node-int64": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@ -10865,6 +11300,13 @@
"is-number": "^7.0.0" "is-number": "^7.0.0"
} }
}, },
"ts-api-utils": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
"integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==",
"dev": true,
"requires": {}
},
"ts-jest": { "ts-jest": {
"version": "29.1.1", "version": "29.1.1",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz",
@ -10947,9 +11389,9 @@
"dev": true "dev": true
}, },
"typescript": { "typescript": {
"version": "4.9.5", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
"dev": true "dev": true
}, },
"undici-types": { "undici-types": {

View file

@ -29,8 +29,8 @@
"@types/mime-types": "^2.1.1", "@types/mime-types": "^2.1.1",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/node": "^20.3.1", "@types/node": "^20.3.1",
"@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^5.48.1", "@typescript-eslint/parser": "^6.0.0",
"chai": "^4.3.7", "chai": "^4.3.7",
"eslint": "^8.43.0", "eslint": "^8.43.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
@ -46,7 +46,7 @@
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tslib": "^2.5.3", "tslib": "^2.5.3",
"typescript": "^4.9.4" "typescript": "^5.0.0"
}, },
"scripts": { "scripts": {
"build": "tsc --project tsconfig.build.json", "build": "tsc --project tsconfig.build.json",

View file

@ -4,7 +4,7 @@
* Immich * Immich
* Immich API * Immich API
* *
* The version of the OpenAPI document: 1.88.2 * The version of the OpenAPI document: 1.89.0
* *
* *
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@ -447,6 +447,12 @@ export interface AssetBulkDeleteDto {
* @interface AssetBulkUpdateDto * @interface AssetBulkUpdateDto
*/ */
export interface AssetBulkUpdateDto { export interface AssetBulkUpdateDto {
/**
*
* @type {string}
* @memberof AssetBulkUpdateDto
*/
'dateTimeOriginal'?: string;
/** /**
* *
* @type {Array<string>} * @type {Array<string>}
@ -465,6 +471,18 @@ export interface AssetBulkUpdateDto {
* @memberof AssetBulkUpdateDto * @memberof AssetBulkUpdateDto
*/ */
'isFavorite'?: boolean; 'isFavorite'?: boolean;
/**
*
* @type {number}
* @memberof AssetBulkUpdateDto
*/
'latitude'?: number;
/**
*
* @type {number}
* @memberof AssetBulkUpdateDto
*/
'longitude'?: number;
/** /**
* *
* @type {boolean} * @type {boolean}
@ -1164,22 +1182,6 @@ export interface CheckExistingAssetsResponseDto {
*/ */
'existingIds': Array<string>; 'existingIds': Array<string>;
} }
/**
*
* @export
* @enum {string}
*/
export const CitiesFile = {
Cities15000: 'cities15000',
Cities5000: 'cities5000',
Cities1000: 'cities1000',
Cities500: 'cities500'
} as const;
export type CitiesFile = typeof CitiesFile[keyof typeof CitiesFile];
/** /**
* *
* @export * @export
@ -3832,12 +3834,6 @@ export interface SystemConfigPasswordLoginDto {
* @interface SystemConfigReverseGeocodingDto * @interface SystemConfigReverseGeocodingDto
*/ */
export interface SystemConfigReverseGeocodingDto { export interface SystemConfigReverseGeocodingDto {
/**
*
* @type {CitiesFile}
* @memberof SystemConfigReverseGeocodingDto
*/
'citiesFileOverride': CitiesFile;
/** /**
* *
* @type {boolean} * @type {boolean}
@ -3845,8 +3841,6 @@ export interface SystemConfigReverseGeocodingDto {
*/ */
'enabled': boolean; 'enabled': boolean;
} }
/** /**
* *
* @export * @export
@ -4161,6 +4155,12 @@ export interface UpdateAlbumDto {
* @interface UpdateAssetDto * @interface UpdateAssetDto
*/ */
export interface UpdateAssetDto { export interface UpdateAssetDto {
/**
*
* @type {string}
* @memberof UpdateAssetDto
*/
'dateTimeOriginal'?: string;
/** /**
* *
* @type {string} * @type {string}
@ -4179,6 +4179,18 @@ export interface UpdateAssetDto {
* @memberof UpdateAssetDto * @memberof UpdateAssetDto
*/ */
'isFavorite'?: boolean; 'isFavorite'?: boolean;
/**
*
* @type {number}
* @memberof UpdateAssetDto
*/
'latitude'?: number;
/**
*
* @type {number}
* @memberof UpdateAssetDto
*/
'longitude'?: number;
} }
/** /**
* *
@ -6808,6 +6820,48 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Get all asset of a device that are in the database, ID only.
* @param {string} deviceId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getAllUserAssetsByDeviceId: async (deviceId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'deviceId' is not null or undefined
assertParamExists('getAllUserAssetsByDeviceId', 'deviceId', deviceId)
const localVarPath = `/asset/device/{deviceId}`
.replace(`{${"deviceId"}}`, encodeURIComponent(String(deviceId)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
setSearchParams(localVarUrlObj, localVarQueryParameter); setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
@ -7477,9 +7531,11 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
}; };
}, },
/** /**
* Get all asset of a device that are in the database, ID only. *
* @summary Use /asset/device/:deviceId instead - Remove in 1.92 release
* @param {string} deviceId * @param {string} deviceId
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @deprecated
* @throws {RequiredError} * @throws {RequiredError}
*/ */
getUserAssetsByDeviceId: async (deviceId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { getUserAssetsByDeviceId: async (deviceId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
@ -8311,6 +8367,16 @@ export const AssetApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(skip, take, userId, isFavorite, isArchived, updatedAfter, updatedBefore, ifNoneMatch, options); const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(skip, take, userId, isFavorite, isArchived, updatedAfter, updatedBefore, ifNoneMatch, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/**
* Get all asset of a device that are in the database, ID only.
* @param {string} deviceId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getAllUserAssetsByDeviceId(deviceId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getAllUserAssetsByDeviceId(deviceId, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/** /**
* Get a single asset\'s information * Get a single asset\'s information
* @param {string} id * @param {string} id
@ -8458,9 +8524,11 @@ export const AssetApiFp = function(configuration?: Configuration) {
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/** /**
* Get all asset of a device that are in the database, ID only. *
* @summary Use /asset/device/:deviceId instead - Remove in 1.92 release
* @param {string} deviceId * @param {string} deviceId
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @deprecated
* @throws {RequiredError} * @throws {RequiredError}
*/ */
async getUserAssetsByDeviceId(deviceId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> { async getUserAssetsByDeviceId(deviceId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> {
@ -8686,6 +8754,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig): AxiosPromise<Array<AssetResponseDto>> { getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig): AxiosPromise<Array<AssetResponseDto>> {
return localVarFp.getAllAssets(requestParameters.skip, requestParameters.take, requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.ifNoneMatch, options).then((request) => request(axios, basePath)); return localVarFp.getAllAssets(requestParameters.skip, requestParameters.take, requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.ifNoneMatch, options).then((request) => request(axios, basePath));
}, },
/**
* Get all asset of a device that are in the database, ID only.
* @param {AssetApiGetAllUserAssetsByDeviceIdRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getAllUserAssetsByDeviceId(requestParameters: AssetApiGetAllUserAssetsByDeviceIdRequest, options?: AxiosRequestConfig): AxiosPromise<Array<string>> {
return localVarFp.getAllUserAssetsByDeviceId(requestParameters.deviceId, options).then((request) => request(axios, basePath));
},
/** /**
* Get a single asset\'s information * Get a single asset\'s information
* @param {AssetApiGetAssetByIdRequest} requestParameters Request parameters. * @param {AssetApiGetAssetByIdRequest} requestParameters Request parameters.
@ -8792,9 +8869,11 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(axios, basePath)); return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(axios, basePath));
}, },
/** /**
* Get all asset of a device that are in the database, ID only. *
* @summary Use /asset/device/:deviceId instead - Remove in 1.92 release
* @param {AssetApiGetUserAssetsByDeviceIdRequest} requestParameters Request parameters. * @param {AssetApiGetUserAssetsByDeviceIdRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @deprecated
* @throws {RequiredError} * @throws {RequiredError}
*/ */
getUserAssetsByDeviceId(requestParameters: AssetApiGetUserAssetsByDeviceIdRequest, options?: AxiosRequestConfig): AxiosPromise<Array<string>> { getUserAssetsByDeviceId(requestParameters: AssetApiGetUserAssetsByDeviceIdRequest, options?: AxiosRequestConfig): AxiosPromise<Array<string>> {
@ -9030,6 +9109,20 @@ export interface AssetApiGetAllAssetsRequest {
readonly ifNoneMatch?: string readonly ifNoneMatch?: string
} }
/**
* Request parameters for getAllUserAssetsByDeviceId operation in AssetApi.
* @export
* @interface AssetApiGetAllUserAssetsByDeviceIdRequest
*/
export interface AssetApiGetAllUserAssetsByDeviceIdRequest {
/**
*
* @type {string}
* @memberof AssetApiGetAllUserAssetsByDeviceId
*/
readonly deviceId: string
}
/** /**
* Request parameters for getAssetById operation in AssetApi. * Request parameters for getAssetById operation in AssetApi.
* @export * @export
@ -9974,6 +10067,17 @@ export class AssetApi extends BaseAPI {
return AssetApiFp(this.configuration).getAllAssets(requestParameters.skip, requestParameters.take, requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); return AssetApiFp(this.configuration).getAllAssets(requestParameters.skip, requestParameters.take, requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath));
} }
/**
* Get all asset of a device that are in the database, ID only.
* @param {AssetApiGetAllUserAssetsByDeviceIdRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof AssetApi
*/
public getAllUserAssetsByDeviceId(requestParameters: AssetApiGetAllUserAssetsByDeviceIdRequest, options?: AxiosRequestConfig) {
return AssetApiFp(this.configuration).getAllUserAssetsByDeviceId(requestParameters.deviceId, options).then((request) => request(this.axios, this.basePath));
}
/** /**
* Get a single asset\'s information * Get a single asset\'s information
* @param {AssetApiGetAssetByIdRequest} requestParameters Request parameters. * @param {AssetApiGetAssetByIdRequest} requestParameters Request parameters.
@ -10104,9 +10208,11 @@ export class AssetApi extends BaseAPI {
} }
/** /**
* Get all asset of a device that are in the database, ID only. *
* @summary Use /asset/device/:deviceId instead - Remove in 1.92 release
* @param {AssetApiGetUserAssetsByDeviceIdRequest} requestParameters Request parameters. * @param {AssetApiGetUserAssetsByDeviceIdRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @deprecated
* @throws {RequiredError} * @throws {RequiredError}
* @memberof AssetApi * @memberof AssetApi
*/ */

View file

@ -4,7 +4,7 @@
* Immich * Immich
* Immich API * Immich API
* *
* The version of the OpenAPI document: 1.88.2 * The version of the OpenAPI document: 1.89.0
* *
* *
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View file

@ -4,7 +4,7 @@
* Immich * Immich
* Immich API * Immich API
* *
* The version of the OpenAPI document: 1.88.2 * The version of the OpenAPI document: 1.89.0
* *
* *
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View file

@ -4,7 +4,7 @@
* Immich * Immich
* Immich API * Immich API
* *
* The version of the OpenAPI document: 1.88.2 * The version of the OpenAPI document: 1.89.0
* *
* *
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View file

@ -4,7 +4,7 @@
* Immich * Immich
* Immich API * Immich API
* *
* The version of the OpenAPI document: 1.88.2 * The version of the OpenAPI document: 1.89.0
* *
* *
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View file

@ -59,7 +59,7 @@ services:
build: build:
context: ../web context: ../web
dockerfile: Dockerfile dockerfile: Dockerfile
command: npm run dev --host command: "node ./node_modules/.bin/vite dev --host 0.0.0.0 --port 3000"
env_file: env_file:
- .env - .env
ports: ports:

View file

@ -14,8 +14,6 @@ docker exec -it <id or name> <command> # attach to a container with a c
docker exec -it immich_server sh docker exec -it immich_server sh
docker exec -it immich_microservices sh docker exec -it immich_microservices sh
docker exec -it immich_machine_learning sh docker exec -it immich_machine_learning sh
docker exec -it immich_web sh
docker exec -it immich_proxy sh
``` ```
## Logs ## Logs
@ -26,8 +24,6 @@ docker logs <id or name> # see the logs for a specific container (by id
docker logs immich_server docker logs immich_server
docker logs immich_microservices docker logs immich_microservices
docker logs immich_machine_learning docker logs immich_machine_learning
docker logs immich_web
docker logs immich_proxy
``` ```
:::tip Follow a log :::tip Follow a log

View file

@ -0,0 +1,58 @@
# Remote Access
This page gives a few pointers on how to access your Immich instance from outside your LAN.
:::danger
Never forward port 2283 directly to the internet without additional configuration. This will expose the web interface via http to the internet, making you succeptible to [man in the middle](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) attacks.
:::
## Option 1: VPN to home network
You may use a VPN service to open an encrypted connection to your Immich instance. OpenVPN and Wireguard are two popular VPN solutions. Here is a guide on setting up VPN access to your server - [Pihole documentation](https://docs.pi-hole.net/guides/vpn/wireguard/overview/)
### Pros:
- Simple to set up and very secure.
- Single point of potential failure, i.e., the VPN software itself. Even if there is a zero-day vulnerability on Immich, you will not be at risk.
- Both Wireguard and OpenVPN are independently security-audited, so the risk of serious zero-day exploits are minimal.
### Cons:
- If you don't have a static IP address, you would need to set up a [Dynamic DNS](https://www.cloudflare.com/learning/dns/glossary/dynamic-dns/). [DuckDNS](https://www.duckdns.org/) is a free DDNS provider.
- VPN software needs to be installed and active on both server-side and client-side.
- Requires you to open a port on your router to your server.
## Option 2: Tailscale
If you are unable to open a port on your router for Wireguard or OpenVPN to your server, [Tailscale](https://tailscale.com/) is a good option. Tailscale mediates a peer-to-peer wireguard tunnel between your server and remote device, even if one or both of them are behind a [NAT firewall](https://en.wikipedia.org/wiki/Network_address_translation).
### Pros
- Minimal configuration needed on server and client sides.
- You are protected against zero-day vulnerabilities on Immich.
### Cons
- The Tailscale client usually needs to run as root on your devices and it increases the attack surface slightly compared to a minimal Wireguard server. e.g., an [RCE vulnerability](https://github.com/tailscale/tailscale/security/advisories/GHSA-vqp6-rc3h-83cp) was discovered in the Windows Tailscale client in November 2022.
- Tailscale is a paid service. However, there is a generous [free tier](https://tailscale.com/pricing/) that permits up to 3 users and up to 100 devices.
- Tailscale needs to be installed and running on both server-side and client-side.
## Option 3: Reverse Proxy
A reverse proxy is a service that sits between web servers and clients. A reverse proxy can either be hosted on the server itself or remotely. Clients can connect to the reverse proxy via https, and the proxy relays data to Immich. This setup makes most sense if you have your own domain and want to access your Immich instance just like any other website, from outside your LAN. You can also use a DDNS provider like DuckDNS or no-ip if you don't have a domain. This configuration allows the Immich Android and iphone apps to connect to your server without a VPN or tailscale app on the client side.
If you're hosting your own reverse proxy, [Nginx](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) is a great option. An example configuration for Nginx is provided [here](https://immich.app/docs/administration/reverse-proxy).
You'll also need your own certificate to authenticate https connections. If you're making Immich publicly accesible, [Let's Encrypt](https://letsencrypt.org/) can provide a free certificate for your domain and is the recommended option. Alternatively, a [self-signed certificate](https://en.wikipedia.org/wiki/Self-signed_certificate) allows you to encrypt your connection to Immich, but it raises a security warning on the client's browser.
A remote reverse proxy like [Cloudflare](https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/) increases security by hiding the server IP address, which makes targeted attacks like [DDoS](https://www.cloudflare.com/learning/ddos/what-is-a-ddos-attack/) harder.
### Pros
- No additional software needs to be installed client-side
- If you only need access to the web interface remotely, it is possible to set up access controls that shield you from zero-day vulnerabilities on Immich. [Cloudflare Access](https://www.cloudflare.com/zero-trust/products/access/) has a generous free tier.
### Cons
- Complex configuration
- Depending on your configuration, both the Immich web interface and API may be exposed to the internet. Immich is under very active developement and the existence of severe security vulnerabilities cannot be ruled out.

120
docs/package-lock.json generated
View file

@ -15,7 +15,7 @@
"@mdx-js/react": "^1.6.22", "@mdx-js/react": "^1.6.22",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"clsx": "^1.2.1", "clsx": "^2.0.0",
"docusaurus-lunr-search": "^2.3.2", "docusaurus-lunr-search": "^2.3.2",
"docusaurus-preset-openapi": "^0.6.3", "docusaurus-preset-openapi": "^0.6.3",
"postcss": "^8.4.25", "postcss": "^8.4.25",
@ -28,7 +28,7 @@
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "^2.4.1", "@docusaurus/module-type-aliases": "^2.4.1",
"@tsconfig/docusaurus": "^1.0.5", "@tsconfig/docusaurus": "^1.0.5",
"prettier": "^2.8.8", "prettier": "^3.0.0",
"typescript": "^5.1.6" "typescript": "^5.1.6"
}, },
"engines": { "engines": {
@ -2603,6 +2603,14 @@
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"
} }
}, },
"node_modules/@docusaurus/theme-classic/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/@docusaurus/theme-common": { "node_modules/@docusaurus/theme-common": {
"version": "2.4.3", "version": "2.4.3",
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.4.3.tgz", "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.4.3.tgz",
@ -2633,6 +2641,14 @@
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"
} }
}, },
"node_modules/@docusaurus/theme-common/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/@docusaurus/theme-search-algolia": { "node_modules/@docusaurus/theme-search-algolia": {
"version": "2.4.3", "version": "2.4.3",
"resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.4.3.tgz", "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.4.3.tgz",
@ -2663,6 +2679,14 @@
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"
} }
}, },
"node_modules/@docusaurus/theme-search-algolia/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/@docusaurus/theme-translations": { "node_modules/@docusaurus/theme-translations": {
"version": "2.4.3", "version": "2.4.3",
"resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.4.3.tgz", "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.4.3.tgz",
@ -4948,9 +4972,9 @@
} }
}, },
"node_modules/clsx": { "node_modules/clsx": {
"version": "1.2.1", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@ -5995,6 +6019,14 @@
"react-dom": "^16.8.4 || ^17" "react-dom": "^16.8.4 || ^17"
} }
}, },
"node_modules/docusaurus-lunr-search/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/docusaurus-plugin-openapi": { "node_modules/docusaurus-plugin-openapi": {
"version": "0.6.4", "version": "0.6.4",
"resolved": "https://registry.npmjs.org/docusaurus-plugin-openapi/-/docusaurus-plugin-openapi-0.6.4.tgz", "resolved": "https://registry.npmjs.org/docusaurus-plugin-openapi/-/docusaurus-plugin-openapi-0.6.4.tgz",
@ -6025,6 +6057,14 @@
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"
} }
}, },
"node_modules/docusaurus-plugin-openapi/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/docusaurus-plugin-openapi/node_modules/fs-extra": { "node_modules/docusaurus-plugin-openapi/node_modules/fs-extra": {
"version": "9.1.0", "version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@ -6098,6 +6138,14 @@
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"
} }
}, },
"node_modules/docusaurus-theme-openapi/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/dom-converter": { "node_modules/dom-converter": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
@ -10801,15 +10849,15 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "2.8.8", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
"dev": true, "dev": true,
"bin": { "bin": {
"prettier": "bin-prettier.js" "prettier": "bin/prettier.cjs"
}, },
"engines": { "engines": {
"node": ">=10.13.0" "node": ">=14"
}, },
"funding": { "funding": {
"url": "https://github.com/prettier/prettier?sponsor=1" "url": "https://github.com/prettier/prettier?sponsor=1"
@ -16745,6 +16793,13 @@
"rtlcss": "^3.5.0", "rtlcss": "^3.5.0",
"tslib": "^2.4.0", "tslib": "^2.4.0",
"utility-types": "^3.10.0" "utility-types": "^3.10.0"
},
"dependencies": {
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
}
} }
}, },
"@docusaurus/theme-common": { "@docusaurus/theme-common": {
@ -16768,6 +16823,13 @@
"tslib": "^2.4.0", "tslib": "^2.4.0",
"use-sync-external-store": "^1.2.0", "use-sync-external-store": "^1.2.0",
"utility-types": "^3.10.0" "utility-types": "^3.10.0"
},
"dependencies": {
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
}
} }
}, },
"@docusaurus/theme-search-algolia": { "@docusaurus/theme-search-algolia": {
@ -16791,6 +16853,13 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"tslib": "^2.4.0", "tslib": "^2.4.0",
"utility-types": "^3.10.0" "utility-types": "^3.10.0"
},
"dependencies": {
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
}
} }
}, },
"@docusaurus/theme-translations": { "@docusaurus/theme-translations": {
@ -18515,9 +18584,9 @@
} }
}, },
"clsx": { "clsx": {
"version": "1.2.1", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q=="
}, },
"collapse-white-space": { "collapse-white-space": {
"version": "1.0.6", "version": "1.0.6",
@ -19243,6 +19312,13 @@
"to-vfile": "^6.1.0", "to-vfile": "^6.1.0",
"unified": "^9.0.0", "unified": "^9.0.0",
"unist-util-is": "^4.0.2" "unist-util-is": "^4.0.2"
},
"dependencies": {
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
}
} }
}, },
"docusaurus-plugin-openapi": { "docusaurus-plugin-openapi": {
@ -19268,6 +19344,11 @@
"webpack": "^5.73.0" "webpack": "^5.73.0"
}, },
"dependencies": { "dependencies": {
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
},
"fs-extra": { "fs-extra": {
"version": "9.1.0", "version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@ -19321,6 +19402,13 @@
"react-redux": "^7.2.0", "react-redux": "^7.2.0",
"redux-devtools-extension": "^2.13.8", "redux-devtools-extension": "^2.13.8",
"webpack": "^5.73.0" "webpack": "^5.73.0"
},
"dependencies": {
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
}
} }
}, },
"dom-converter": { "dom-converter": {
@ -22663,9 +22751,9 @@
"integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==" "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA=="
}, },
"prettier": { "prettier": {
"version": "2.8.8", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
"dev": true "dev": true
}, },
"pretty-error": { "pretty-error": {

View file

@ -24,7 +24,7 @@
"@mdx-js/react": "^1.6.22", "@mdx-js/react": "^1.6.22",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"clsx": "^1.2.1", "clsx": "^2.0.0",
"docusaurus-lunr-search": "^2.3.2", "docusaurus-lunr-search": "^2.3.2",
"docusaurus-preset-openapi": "^0.6.3", "docusaurus-preset-openapi": "^0.6.3",
"postcss": "^8.4.25", "postcss": "^8.4.25",
@ -37,7 +37,7 @@
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "^2.4.1", "@docusaurus/module-type-aliases": "^2.4.1",
"@tsconfig/docusaurus": "^1.0.5", "@tsconfig/docusaurus": "^1.0.5",
"prettier": "^2.8.8", "prettier": "^3.0.0",
"typescript": "^5.1.6" "typescript": "^5.1.6"
}, },
"browserslist": { "browserslist": {

View file

@ -61,8 +61,12 @@
.searchbox__input { .searchbox__input {
display: inline-block; display: inline-block;
box-sizing: border-box; box-sizing: border-box;
-webkit-transition: box-shadow 0.4s ease, background 0.4s ease; -webkit-transition:
transition: box-shadow 0.4s ease, background 0.4s ease; box-shadow 0.4s ease,
background 0.4s ease;
transition:
box-shadow 0.4s ease,
background 0.4s ease;
border: 0; border: 0;
border-radius: 16px; border-radius: 16px;
box-shadow: inset 0 0 0 1px #cccccc; box-shadow: inset 0 0 0 1px #cccccc;
@ -243,7 +247,9 @@
} }
.algolia-autocomplete .ds-dropdown-menu { .algolia-autocomplete .ds-dropdown-menu {
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2), 0 2px 3px 0 rgba(0, 0, 0, 0.1); box-shadow:
0 1px 0 0 rgba(0, 0, 0, 0.2),
0 2px 3px 0 rgba(0, 0, 0, 0.1);
} }
@media (min-width: 601px) { @media (min-width: 601px) {

View file

@ -1,4 +1,4 @@
FROM python:3.11-bookworm as builder FROM python:3.11-bookworm@sha256:e5a1b0a194a5fbf94f6e350b31c9a508723f9eeb2f9e9e32c3b65df8520a40cc as builder
ENV PYTHONDONTWRITEBYTECODE=1 \ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
@ -13,7 +13,7 @@ ENV VIRTUAL_ENV="/opt/venv" PATH="/opt/venv/bin:${PATH}"
COPY poetry.lock pyproject.toml ./ COPY poetry.lock pyproject.toml ./
RUN poetry install --sync --no-interaction --no-ansi --no-root --only main RUN poetry install --sync --no-interaction --no-ansi --no-root --only main
FROM python:3.11-slim-bookworm FROM python:3.11-slim-bookworm@sha256:1bc6a3e9356d64ea632791653bc71a56340e8741dab66434ab2739ebf6aed29d
RUN apt-get update && apt-get install -y --no-install-recommends tini libmimalloc2.0 && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y --no-install-recommends tini libmimalloc2.0 && rm -rf /var/lib/apt/lists/*

View file

@ -1,4 +1,4 @@
FROM mambaorg/micromamba:bookworm-slim as builder FROM mambaorg/micromamba:bookworm-slim@sha256:d20c621f3ae42f50f380166b15b6c88b14fa62ab6ea188f2cef33451d64057c7 as builder
ENV NODE_ENV=production \ ENV NODE_ENV=production \
TRANSFORMERS_CACHE=/cache \ TRANSFORMERS_CACHE=/cache \

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "machine-learning" name = "machine-learning"
version = "1.88.2" version = "1.89.0"
description = "" description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"] authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md" readme = "README.md"

View file

@ -49,7 +49,6 @@ dart_code_metrics:
# Common # Common
- avoid-accessing-collections-by-constant-index - avoid-accessing-collections-by-constant-index
- avoid-accessing-other-classes-private-members - avoid-accessing-other-classes-private-members
- avoid-async-call-in-sync-function
- avoid-cascade-after-if-null - avoid-cascade-after-if-null
- avoid-collapsible-if - avoid-collapsible-if
- avoid-collection-methods-with-unrelated-types - avoid-collection-methods-with-unrelated-types

View file

@ -35,8 +35,8 @@ platform :android do
task: 'bundle', task: 'bundle',
build_type: 'Release', build_type: 'Release',
properties: { properties: {
"android.injected.version.code" => 112, "android.injected.version.code" => 113,
"android.injected.version.name" => "1.88.2", "android.injected.version.name" => "1.89.0",
} }
) )
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')

View file

@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Afegeix usuaris", "album_viewer_page_share_add_users": "Afegeix usuaris",
"all_people_page_title": "Persones", "all_people_page_title": "Persones",
"all_videos_page_title": "Vídeos", "all_videos_page_title": "Vídeos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No s'ha trobat res arxivat", "archive_page_no_archived_assets": "No s'ha trobat res arxivat",
@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Configuració de la memòria cau", "cache_settings_title": "Configuració de la memòria cau",
"change_password_form_confirm_password": "Confirma la contrasenya", "change_password_form_confirm_password": "Confirma la contrasenya",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "Místní úložiště", "cache_settings_tile_title": "Místní úložiště",
"cache_settings_title": "Nastavení vyrovnávací paměti", "cache_settings_title": "Nastavení vyrovnávací paměti",
"change_password_form_confirm_password": "Potvrďte heslo", "change_password_form_confirm_password": "Potvrďte heslo",
"change_password_form_description": "Dobrý den, {firstName} {lastName},\n\nje to buď poprvé, co se přihlašujete do systému, nebo byl vytvořen požadavek na změnu hesla. Níže zadejte nové heslo.", "change_password_form_description": "Dobrý den, {name},\n\nje to buď poprvé, co se přihlašujete do systému, nebo byl vytvořen požadavek na změnu hesla. Níže zadejte nové heslo.",
"change_password_form_new_password": "Nové heslo", "change_password_form_new_password": "Nové heslo",
"change_password_form_password_mismatch": "Hesla se neshodují", "change_password_form_password_mismatch": "Hesla se neshodují",
"change_password_form_reenter_new_password": "Znovu zadejte nové heslo", "change_password_form_reenter_new_password": "Znovu zadejte nové heslo",

View file

@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Tilføj brugere", "album_viewer_page_share_add_users": "Tilføj brugere",
"all_people_page_title": "Personer", "all_people_page_title": "Personer",
"all_videos_page_title": "Videoer", "all_videos_page_title": "Videoer",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "Ingen arkiverede elementer blev fundet", "archive_page_no_archived_assets": "Ingen arkiverede elementer blev fundet",
@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Cache-indstillinger", "cache_settings_title": "Cache-indstillinger",
"change_password_form_confirm_password": "Bekræft kodeord", "change_password_form_confirm_password": "Bekræft kodeord",
"change_password_form_description": "Hej {firstName} {lastName},\n\nDette er enten første gang du logger ind eller også er der lavet en anmodning om at ændre dit kodeord. Indtast venligst et nyt kodeord nedenfor.", "change_password_form_description": "Hej {name},\n\nDette er enten første gang du logger ind eller også er der lavet en anmodning om at ændre dit kodeord. Indtast venligst et nyt kodeord nedenfor.",
"change_password_form_new_password": "Nyt kodeord", "change_password_form_new_password": "Nyt kodeord",
"change_password_form_password_mismatch": "Kodeord er ikke ens", "change_password_form_password_mismatch": "Kodeord er ikke ens",
"change_password_form_reenter_new_password": "Gentag nyt kodeord", "change_password_form_reenter_new_password": "Gentag nyt kodeord",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "Lokaler Speicher", "cache_settings_tile_title": "Lokaler Speicher",
"cache_settings_title": "Zwischenspeicher Einstellungen", "cache_settings_title": "Zwischenspeicher Einstellungen",
"change_password_form_confirm_password": "Passwort bestätigen", "change_password_form_confirm_password": "Passwort bestätigen",
"change_password_form_description": "Hallo {firstName} {lastName}\n\nDas ist entweder das erste Mal dass du dich einloggst oder eine Anfrage zur Änderung deines Passwortes wurde gestellt. Bitte gebe das neue Passwort ein.", "change_password_form_description": "Hallo {name}\n\nDas ist entweder das erste Mal dass du dich einloggst oder eine Anfrage zur Änderung deines Passwortes wurde gestellt. Bitte gebe das neue Passwort ein.",
"change_password_form_new_password": "Neues Passwort", "change_password_form_new_password": "Neues Passwort",
"change_password_form_password_mismatch": "Passwörter stimmen nicht überein", "change_password_form_password_mismatch": "Passwörter stimmen nicht überein",
"change_password_form_reenter_new_password": "Passwort erneut eingeben", "change_password_form_reenter_new_password": "Passwort erneut eingeben",

View file

@ -28,7 +28,7 @@
"album_viewer_page_share_add_users": "Add users", "album_viewer_page_share_add_users": "Add users",
"all_people_page_title": "People", "all_people_page_title": "People",
"all_videos_page_title": "Videos", "all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",
@ -123,7 +123,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Caching Settings", "cache_settings_title": "Caching Settings",
"change_password_form_confirm_password": "Confirm Password", "change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",
@ -390,6 +390,28 @@
"shared_link_edit_show_meta": "Show metadata", "shared_link_edit_show_meta": "Show metadata",
"shared_link_edit_submit_button": "Update link", "shared_link_edit_submit_button": "Update link",
"shared_link_empty": "You don't have any shared links", "shared_link_empty": "You don't have any shared links",
"shared_link_error_server_url_fetch": "Cannot fetch the server url",
"shared_link_expired": "Expired",
"shared_link_expires_days": {
"one": "Expires in {} day",
"other": "Expires in {} days"
},
"shared_link_expires_hours": {
"one": "Expires in {} hour",
"other": "Expires in {} hours"
},
"shared_link_expires_minutes": {
"one": "Expires in {} minute",
"other": "Expires in {} minutes"
},
"shared_link_expires_seconds": {
"one": "Expires in {} second",
"other": "Expires in {} seconds"
},
"shared_link_expires_never": "Expires ∞",
"shared_link_info_chip_download": "Download",
"shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Upload",
"shared_link_manage_links": "Manage Shared links", "shared_link_manage_links": "Manage Shared links",
"share_done": "Done", "share_done": "Done",
"share_invite": "Invite to album", "share_invite": "Invite to album",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "Almacenamiento local", "cache_settings_tile_title": "Almacenamiento local",
"cache_settings_title": "Configuración de la caché", "cache_settings_title": "Configuración de la caché",
"change_password_form_confirm_password": "Confirmar Contraseña", "change_password_form_confirm_password": "Confirmar Contraseña",
"change_password_form_description": "Hola {firstName} {lastName},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", "change_password_form_description": "Hola {name},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.",
"change_password_form_new_password": "Nueva Contraseña", "change_password_form_new_password": "Nueva Contraseña",
"change_password_form_password_mismatch": "Las contraseñas no coinciden", "change_password_form_password_mismatch": "Las contraseñas no coinciden",
"change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña", "change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "Almacenamiento local", "cache_settings_tile_title": "Almacenamiento local",
"cache_settings_title": "Configuración de la caché", "cache_settings_title": "Configuración de la caché",
"change_password_form_confirm_password": "Confirmar Contraseña", "change_password_form_confirm_password": "Confirmar Contraseña",
"change_password_form_description": "Hola {firstName} {lastName},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", "change_password_form_description": "Hola {name},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.",
"change_password_form_new_password": "Nueva Contraseña", "change_password_form_new_password": "Nueva Contraseña",
"change_password_form_password_mismatch": "Las contraseñas no coinciden", "change_password_form_password_mismatch": "Las contraseñas no coinciden",
"change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña", "change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "Almacenamiento local", "cache_settings_tile_title": "Almacenamiento local",
"cache_settings_title": "Configuración de la caché", "cache_settings_title": "Configuración de la caché",
"change_password_form_confirm_password": "Confirmar Contraseña", "change_password_form_confirm_password": "Confirmar Contraseña",
"change_password_form_description": "Hola {firstName} {lastName},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", "change_password_form_description": "Hola {name},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.",
"change_password_form_new_password": "Nueva Contraseña", "change_password_form_new_password": "Nueva Contraseña",
"change_password_form_password_mismatch": "Las contraseñas no coinciden", "change_password_form_password_mismatch": "Las contraseñas no coinciden",
"change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña", "change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña",

View file

@ -390,6 +390,28 @@
"shared_link_edit_show_meta": "Mostrar metadatos", "shared_link_edit_show_meta": "Mostrar metadatos",
"shared_link_edit_submit_button": "Actualizar enlace", "shared_link_edit_submit_button": "Actualizar enlace",
"shared_link_empty": "No tienes ningún enlace compartido", "shared_link_empty": "No tienes ningún enlace compartido",
"shared_link_error_server_url_fetch": "No se puede obtener la URL del servidor",
"shared_link_expired": "Expirado",
"shared_link_expires_days": {
"one": "Expira en {} día",
"other": "Expira en {} días"
},
"shared_link_expires_hours": {
"one": "Expira en {} hora",
"other": "Expira en {} horas"
},
"shared_link_expires_minutes": {
"one": "Expira en {} minuto",
"other": "Expira en {} minutos"
},
"shared_link_expires_seconds": {
"one": "Expira en {} segundo",
"other": "Expira en {} segundos"
},
"shared_link_expires_never": "Sin expiración",
"shared_link_info_chip_download": "Descargar",
"shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Subir",
"shared_link_manage_links": "Administrar enlaces compartidos", "shared_link_manage_links": "Administrar enlaces compartidos",
"share_done": "Hecho", "share_done": "Hecho",
"share_invite": "Invitar al álbum", "share_invite": "Invitar al álbum",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "Paikallinen tallennustila", "cache_settings_tile_title": "Paikallinen tallennustila",
"cache_settings_title": "Välimuistin asetukset", "cache_settings_title": "Välimuistin asetukset",
"change_password_form_confirm_password": "Vahvista salasana", "change_password_form_confirm_password": "Vahvista salasana",
"change_password_form_description": "Hei {firstName} {lastName},\n\nTämä on joko ensimmäinen kirjautumisesi järjestelmään tai salasanan vaihtaminen vaihtaminen on pakotettu. Ole hyvä ja syötä uusi salasana alle.", "change_password_form_description": "Hei {name},\n\nTämä on joko ensimmäinen kirjautumisesi järjestelmään tai salasanan vaihtaminen vaihtaminen on pakotettu. Ole hyvä ja syötä uusi salasana alle.",
"change_password_form_new_password": "Uusi salasana", "change_password_form_new_password": "Uusi salasana",
"change_password_form_password_mismatch": "Salasanat eivät täsmää", "change_password_form_password_mismatch": "Salasanat eivät täsmää",
"change_password_form_reenter_new_password": "Uusi salasana uudelleen", "change_password_form_reenter_new_password": "Uusi salasana uudelleen",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "Stockage local", "cache_settings_tile_title": "Stockage local",
"cache_settings_title": "Paramètres de mise en cache", "cache_settings_title": "Paramètres de mise en cache",
"change_password_form_confirm_password": "Confirmez le mot de passe", "change_password_form_confirm_password": "Confirmez le mot de passe",
"change_password_form_description": "Bonjour {firstName} {lastName},\n\nC'est la première fois que vous vous connectez au système ou vous avez demandé de changer votre mot de passe. Veuillez saisir le nouveau mot de passe ci-dessous.", "change_password_form_description": "Bonjour {name},\n\nC'est la première fois que vous vous connectez au système ou vous avez demandé de changer votre mot de passe. Veuillez saisir le nouveau mot de passe ci-dessous.",
"change_password_form_new_password": "Nouveau mot de passe", "change_password_form_new_password": "Nouveau mot de passe",
"change_password_form_password_mismatch": "Les mots de passe ne correspondent pas", "change_password_form_password_mismatch": "Les mots de passe ne correspondent pas",
"change_password_form_reenter_new_password": "Saisissez à nouveau le nouveau mot de passe", "change_password_form_reenter_new_password": "Saisissez à nouveau le nouveau mot de passe",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "Stockage local", "cache_settings_tile_title": "Stockage local",
"cache_settings_title": "Paramètres de mise en cache", "cache_settings_title": "Paramètres de mise en cache",
"change_password_form_confirm_password": "Confirmez le mot de passe", "change_password_form_confirm_password": "Confirmez le mot de passe",
"change_password_form_description": "Bonjour {firstName} {lastName},\n\nC'est la première fois que vous vous connectez au système ou vous avez demandé à changer votre mot de passe. Veuillez saisir le nouveau mot de passe ci-dessous.", "change_password_form_description": "Bonjour {name},\n\nC'est la première fois que vous vous connectez au système ou vous avez demandé à changer votre mot de passe. Veuillez saisir le nouveau mot de passe ci-dessous.",
"change_password_form_new_password": "Nouveau mot de passe", "change_password_form_new_password": "Nouveau mot de passe",
"change_password_form_password_mismatch": "Les mots de passe ne correspondent pas", "change_password_form_password_mismatch": "Les mots de passe ne correspondent pas",
"change_password_form_reenter_new_password": "Saisissez à nouveau le nouveau mot de passe", "change_password_form_reenter_new_password": "Saisissez à nouveau le nouveau mot de passe",

View file

@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Add users", "album_viewer_page_share_add_users": "Add users",
"all_people_page_title": "People", "all_people_page_title": "People",
"all_videos_page_title": "Videos", "all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",
@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Caching Settings", "cache_settings_title": "Caching Settings",
"change_password_form_confirm_password": "Confirm Password", "change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",

View file

@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Felhasználók hozzáadása", "album_viewer_page_share_add_users": "Felhasználók hozzáadása",
"all_people_page_title": "Emberek", "all_people_page_title": "Emberek",
"all_videos_page_title": "Videók", "all_videos_page_title": "Videók",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "Nem található archivált média", "archive_page_no_archived_assets": "Nem található archivált média",

View file

@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Aggiungi utenti", "album_viewer_page_share_add_users": "Aggiungi utenti",
"all_people_page_title": "Persone", "all_people_page_title": "Persone",
"all_videos_page_title": "Video", "all_videos_page_title": "Video",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "Nessuna oggetto archiviato", "archive_page_no_archived_assets": "Nessuna oggetto archiviato",
@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Impostazioni della Cache", "cache_settings_title": "Impostazioni della Cache",
"change_password_form_confirm_password": "Conferma Password ", "change_password_form_confirm_password": "Conferma Password ",
"change_password_form_description": "Ciao {firstName} {lastName},\n\nQuesto è la prima volta che accedi al sistema oppure è stato fatto una richiesta di cambiare la password. Per favore inserisca la nuova password qui sotto", "change_password_form_description": "Ciao {name},\n\nQuesto è la prima volta che accedi al sistema oppure è stato fatto una richiesta di cambiare la password. Per favore inserisca la nuova password qui sotto",
"change_password_form_new_password": "Nuova Password", "change_password_form_new_password": "Nuova Password",
"change_password_form_password_mismatch": "Le password non coincidono", "change_password_form_password_mismatch": "Le password non coincidono",
"change_password_form_reenter_new_password": "Inserisci ancora la nuova password ", "change_password_form_reenter_new_password": "Inserisci ancora la nuova password ",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "로컬 저장소", "cache_settings_tile_title": "로컬 저장소",
"cache_settings_title": "캐시 설정", "cache_settings_title": "캐시 설정",
"change_password_form_confirm_password": "비밀번호 확인", "change_password_form_confirm_password": "비밀번호 확인",
"change_password_form_description": "{firstName} {lastName} 님, 안녕하세요.\n\n시스템에 처음 로그인했거나 비밀번호 변경 요청이 있었습니다. 아래에 새 비밀번호를 입력하세요.", "change_password_form_description": "{name} 님, 안녕하세요.\n\n시스템에 처음 로그인했거나 비밀번호 변경 요청이 있었습니다. 아래에 새 비밀번호를 입력하세요.",
"change_password_form_new_password": "새 비밀번호", "change_password_form_new_password": "새 비밀번호",
"change_password_form_password_mismatch": "비밀번호가 일치하지 않습니다", "change_password_form_password_mismatch": "비밀번호가 일치하지 않습니다",
"change_password_form_reenter_new_password": "새 비밀번호 재입력", "change_password_form_reenter_new_password": "새 비밀번호 재입력",

View file

@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Pievienot lietotājus", "album_viewer_page_share_add_users": "Pievienot lietotājus",
"all_people_page_title": "Cilvēki", "all_people_page_title": "Cilvēki",
"all_videos_page_title": "Videoklipi", "all_videos_page_title": "Videoklipi",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "Nav atrasts neviens arhivēts aktīvs", "archive_page_no_archived_assets": "Nav atrasts neviens arhivēts aktīvs",
@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Kešdarbes iestatījumi", "cache_settings_title": "Kešdarbes iestatījumi",
"change_password_form_confirm_password": "Apstiprināt Paroli", "change_password_form_confirm_password": "Apstiprināt Paroli",
"change_password_form_description": "Sveiki {FirstName} {LastName},\n\nŠī ir pirmā reize, kad pierakstāties sistēmā, vai arī ir iesniegts pieprasījums mainīt paroli. Lūdzu, zemāk ievadiet jauno paroli.", "change_password_form_description": "Sveiki {name},\n\nŠī ir pirmā reize, kad pierakstāties sistēmā, vai arī ir iesniegts pieprasījums mainīt paroli. Lūdzu, zemāk ievadiet jauno paroli.",
"change_password_form_new_password": "Jauna Parole", "change_password_form_new_password": "Jauna Parole",
"change_password_form_password_mismatch": "Paroles nesakrīt", "change_password_form_password_mismatch": "Paroles nesakrīt",
"change_password_form_reenter_new_password": "Atkārtoti ievadīt jaunu paroli", "change_password_form_reenter_new_password": "Atkārtoti ievadīt jaunu paroli",

View file

@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Add users", "album_viewer_page_share_add_users": "Add users",
"all_people_page_title": "People", "all_people_page_title": "People",
"all_videos_page_title": "Videos", "all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",
@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Caching Settings", "cache_settings_title": "Caching Settings",
"change_password_form_confirm_password": "Confirm Password", "change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "Lokal lagring", "cache_settings_tile_title": "Lokal lagring",
"cache_settings_title": "Bufringsinnstillinger", "cache_settings_title": "Bufringsinnstillinger",
"change_password_form_confirm_password": "Bekreft passord", "change_password_form_confirm_password": "Bekreft passord",
"change_password_form_description": "Hei {firstName} {lastName}!\n\nDette er enten første gang du logger på systemet, eller det er sendt en forespørsel om å endre passordet ditt. Vennligst skriv inn det nye passordet nedenfor.", "change_password_form_description": "Hei {name}!\n\nDette er enten første gang du logger på systemet, eller det er sendt en forespørsel om å endre passordet ditt. Vennligst skriv inn det nye passordet nedenfor.",
"change_password_form_new_password": "Nytt passord", "change_password_form_new_password": "Nytt passord",
"change_password_form_password_mismatch": "Passordene stemmer ikke", "change_password_form_password_mismatch": "Passordene stemmer ikke",
"change_password_form_reenter_new_password": "Skriv nytt passord igjen", "change_password_form_reenter_new_password": "Skriv nytt passord igjen",

View file

@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Gebruikers toevoegen", "album_viewer_page_share_add_users": "Gebruikers toevoegen",
"all_people_page_title": "Personen", "all_people_page_title": "Personen",
"all_videos_page_title": "Video's", "all_videos_page_title": "Video's",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "Geen gearchiveerde items gevonden", "archive_page_no_archived_assets": "Geen gearchiveerde items gevonden",
@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Cache-instellingen", "cache_settings_title": "Cache-instellingen",
"change_password_form_confirm_password": "Bevestig wachtwoord", "change_password_form_confirm_password": "Bevestig wachtwoord",
"change_password_form_description": "Hallo {firstName} {lastName},\n\nDit is ofwel de eerste keer dat je inlogt, of er is een verzoek gedaan om je wachtwoord te wijzigen. Vul hieronder een nieuw wachtwoord in.", "change_password_form_description": "Hallo {name},\n\nDit is ofwel de eerste keer dat je inlogt, of er is een verzoek gedaan om je wachtwoord te wijzigen. Vul hieronder een nieuw wachtwoord in.",
"change_password_form_new_password": "Nieuw wachtwoord", "change_password_form_new_password": "Nieuw wachtwoord",
"change_password_form_password_mismatch": "Wachtwoorden komen niet overeen", "change_password_form_password_mismatch": "Wachtwoorden komen niet overeen",
"change_password_form_reenter_new_password": "Vul het wachtwoord opnieuw in", "change_password_form_reenter_new_password": "Vul het wachtwoord opnieuw in",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "Lokalny magazyn", "cache_settings_tile_title": "Lokalny magazyn",
"cache_settings_title": "Ustawienia Buforowania", "cache_settings_title": "Ustawienia Buforowania",
"change_password_form_confirm_password": "Potwierdź Hasło", "change_password_form_confirm_password": "Potwierdź Hasło",
"change_password_form_description": "Cześć {firstName} {lastName},\n\nPierwszy raz logujesz się do systemu, albo złożono prośbę o zmianę hasła. Wpisz poniżej nowe hasło.", "change_password_form_description": "Cześć {name},\n\nPierwszy raz logujesz się do systemu, albo złożono prośbę o zmianę hasła. Wpisz poniżej nowe hasło.",
"change_password_form_new_password": "Nowe Hasło", "change_password_form_new_password": "Nowe Hasło",
"change_password_form_password_mismatch": "Hasła nie są zgodne", "change_password_form_password_mismatch": "Hasła nie są zgodne",
"change_password_form_reenter_new_password": "Wprowadź ponownie Nowe Hasło", "change_password_form_reenter_new_password": "Wprowadź ponownie Nowe Hasło",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "Локальное хранилище", "cache_settings_tile_title": "Локальное хранилище",
"cache_settings_title": "Настройки кэширования", "cache_settings_title": "Настройки кэширования",
"change_password_form_confirm_password": "Подтвердите пароль", "change_password_form_confirm_password": "Подтвердите пароль",
"change_password_form_description": "Привет {firstName} {lastName},\n\nЭто либо ваш первый вход в систему, либо был сделан запрос на смену пароля. Пожалуйста, введите новый пароль ниже.", "change_password_form_description": "Привет {name},\n\nЭто либо ваш первый вход в систему, либо был сделан запрос на смену пароля. Пожалуйста, введите новый пароль ниже.",
"change_password_form_new_password": "Новый пароль", "change_password_form_new_password": "Новый пароль",
"change_password_form_password_mismatch": "Пароли не совпадают", "change_password_form_password_mismatch": "Пароли не совпадают",
"change_password_form_reenter_new_password": "Повторно введите новый пароль", "change_password_form_reenter_new_password": "Повторно введите новый пароль",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "Lokálne úložisko", "cache_settings_tile_title": "Lokálne úložisko",
"cache_settings_title": "Nastavenia vyrovnávacej pamäte", "cache_settings_title": "Nastavenia vyrovnávacej pamäte",
"change_password_form_confirm_password": "Potvrďte heslo", "change_password_form_confirm_password": "Potvrďte heslo",
"change_password_form_description": "Dobrý deň, {firstName} {lastName},\n\nBuď sa do systému prihlasujete prvýkrát, alebo bola podaná žiadosť o zmenu hesla. Prosím, zadajte nové heslo nižšie.", "change_password_form_description": "Dobrý deň, {name},\n\nBuď sa do systému prihlasujete prvýkrát, alebo bola podaná žiadosť o zmenu hesla. Prosím, zadajte nové heslo nižšie.",
"change_password_form_new_password": "Nové heslo", "change_password_form_new_password": "Nové heslo",
"change_password_form_password_mismatch": "Heslá sa nezhodujú", "change_password_form_password_mismatch": "Heslá sa nezhodujú",
"change_password_form_reenter_new_password": "Znova zadajte nové heslo", "change_password_form_reenter_new_password": "Znova zadajte nové heslo",

View file

@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Add users", "album_viewer_page_share_add_users": "Add users",
"all_people_page_title": "People", "all_people_page_title": "People",
"all_videos_page_title": "Videos", "all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",
@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Caching Settings", "cache_settings_title": "Caching Settings",
"change_password_form_confirm_password": "Confirm Password", "change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",

View file

@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Dodaj korisnike", "album_viewer_page_share_add_users": "Dodaj korisnike",
"all_people_page_title": "People", "all_people_page_title": "People",
"all_videos_page_title": "Videos", "all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",

View file

@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Add users", "album_viewer_page_share_add_users": "Add users",
"all_people_page_title": "People", "all_people_page_title": "People",
"all_videos_page_title": "Videos", "all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",
@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Caching Settings", "cache_settings_title": "Caching Settings",
"change_password_form_confirm_password": "Confirm Password", "change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",

View file

@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Lägg till användare", "album_viewer_page_share_add_users": "Lägg till användare",
"all_people_page_title": "People", "all_people_page_title": "People",
"all_videos_page_title": "Videos", "all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",
@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Cache Inställningar", "cache_settings_title": "Cache Inställningar",
"change_password_form_confirm_password": "Bekräfta lösenord", "change_password_form_confirm_password": "Bekräfta lösenord",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "Nytt lösenord", "change_password_form_new_password": "Nytt lösenord",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",

View file

@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "เพิ่มผู้ใช้งาน", "album_viewer_page_share_add_users": "เพิ่มผู้ใช้งาน",
"all_people_page_title": "ผู้คน", "all_people_page_title": "ผู้คน",
"all_videos_page_title": "วิดีโอ", "all_videos_page_title": "วิดีโอ",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "ไม่พบทรัพยากรในที่เก็บถาวร", "archive_page_no_archived_assets": "ไม่พบทรัพยากรในที่เก็บถาวร",
@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "ตั้งค่าแคช", "cache_settings_title": "ตั้งค่าแคช",
"change_password_form_confirm_password": "ยืนยันรหัสผ่าน", "change_password_form_confirm_password": "ยืนยันรหัสผ่าน",
"change_password_form_description": "สวัสดี {firstName} {lastName},\n\nครั้งนี้อาจจะเป็นครั้งแรกที่คุณเข้าสู่ระบบ หรือมีคำขอเพื่อที่จะเปลี่ยนรหัสผ่านของคุI กรุณาเพิ่มรหัสผ่านใหม่ข้างล่าง", "change_password_form_description": "สวัสดี {name},\n\nครั้งนี้อาจจะเป็นครั้งแรกที่คุณเข้าสู่ระบบ หรือมีคำขอเพื่อที่จะเปลี่ยนรหัสผ่านของคุI กรุณาเพิ่มรหัสผ่านใหม่ข้างล่าง",
"change_password_form_new_password": "รหัสผ่านใหม่", "change_password_form_new_password": "รหัสผ่านใหม่",
"change_password_form_password_mismatch": "รหัสผ่านไม่ตรงกัน", "change_password_form_password_mismatch": "รหัสผ่านไม่ตรงกัน",
"change_password_form_reenter_new_password": "กรอกรหัสผ่านใหม่", "change_password_form_reenter_new_password": "กรอกรหัสผ่านใหม่",

View file

@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Додати користувачів", "album_viewer_page_share_add_users": "Додати користувачів",
"all_people_page_title": "Люди", "all_people_page_title": "Люди",
"all_videos_page_title": "Відео", "all_videos_page_title": "Відео",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "Немає архівних елементів", "archive_page_no_archived_assets": "Немає архівних елементів",
@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Налаштування Кешування", "cache_settings_title": "Налаштування Кешування",
"change_password_form_confirm_password": "Підтвердити пароль", "change_password_form_confirm_password": "Підтвердити пароль",
"change_password_form_description": "Привіт {firstName} {lastName},\n\nВи або або вперше входите у систему, або було зроблено запит на зміну вашого пароля. \nВведіть ваш новий пароль.", "change_password_form_description": "Привіт {name},\n\nВи або або вперше входите у систему, або було зроблено запит на зміну вашого пароля. \nВведіть ваш новий пароль.",
"change_password_form_new_password": "Новий Пароль", "change_password_form_new_password": "Новий Пароль",
"change_password_form_password_mismatch": "Паролі не співпадають", "change_password_form_password_mismatch": "Паролі не співпадають",
"change_password_form_reenter_new_password": "Повторіть Новий Пароль", "change_password_form_reenter_new_password": "Повторіть Новий Пароль",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "Lưu trữ cục bộ", "cache_settings_tile_title": "Lưu trữ cục bộ",
"cache_settings_title": "Caching Settings", "cache_settings_title": "Caching Settings",
"change_password_form_confirm_password": "Confirm Password", "change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "本地存储", "cache_settings_tile_title": "本地存储",
"cache_settings_title": "缓存设置", "cache_settings_title": "缓存设置",
"change_password_form_confirm_password": "确认密码", "change_password_form_confirm_password": "确认密码",
"change_password_form_description": "{firstName} {lastName} 您好,\n\n这是您首次登录系统或被管理员要求更改密码。\n请在下方输入新密码。", "change_password_form_description": "{name} 您好,\n\n这是您首次登录系统或被管理员要求更改密码。\n请在下方输入新密码。",
"change_password_form_new_password": "新密码", "change_password_form_new_password": "新密码",
"change_password_form_password_mismatch": "密码不匹配", "change_password_form_password_mismatch": "密码不匹配",
"change_password_form_reenter_new_password": "重新输入新的密码", "change_password_form_reenter_new_password": "重新输入新的密码",

View file

@ -119,7 +119,7 @@
"cache_settings_tile_title": "本地存储", "cache_settings_tile_title": "本地存储",
"cache_settings_title": "缓存设置", "cache_settings_title": "缓存设置",
"change_password_form_confirm_password": "确认密码", "change_password_form_confirm_password": "确认密码",
"change_password_form_description": "{firstName} {lastName} 您好,\n\n这是您首次登录系统或被管理员要求更改密码。\n请在下方输入新密码。", "change_password_form_description": "{name} 您好,\n\n这是您首次登录系统或被管理员要求更改密码。\n请在下方输入新密码。",
"change_password_form_new_password": "新密码", "change_password_form_new_password": "新密码",
"change_password_form_password_mismatch": "密码不匹配", "change_password_form_password_mismatch": "密码不匹配",
"change_password_form_reenter_new_password": "重新输入新的密码", "change_password_form_reenter_new_password": "重新输入新的密码",

View file

@ -45,7 +45,7 @@ class ImmichTestHelper {
await tester.pumpWidget( await tester.pumpWidget(
ProviderScope( ProviderScope(
overrides: [dbProvider.overrideWithValue(db)], overrides: [dbProvider.overrideWithValue(db)],
child: app.getMainWidget(), child: const app.MainWidget(),
), ),
); );
// Post run tasks // Post run tasks

View file

@ -169,4 +169,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382 PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382
COCOAPODS: 1.12.1 COCOAPODS: 1.11.3

View file

@ -19,7 +19,7 @@ platform :ios do
desc "iOS Beta" desc "iOS Beta"
lane :beta do lane :beta do
increment_version_number( increment_version_number(
version_number: "1.88.2" version_number: "1.89.0"
) )
increment_build_number( increment_build_number(
build_number: latest_testflight_build_number + 1, build_number: latest_testflight_build_number + 1,

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
Color immichBackgroundColor = const Color(0xFFf6f8fe); const Color immichBackgroundColor = Color(0xFFf6f8fe);
Color immichDarkBackgroundColor = const Color.fromARGB(255, 0, 0, 0); const Color immichDarkBackgroundColor = Color.fromARGB(255, 0, 0, 0);
Color immichDarkThemePrimaryColor = const Color.fromARGB(255, 173, 203, 250); const Color immichDarkThemePrimaryColor = Color.fromARGB(255, 173, 203, 250);

View file

@ -4,22 +4,32 @@ import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/scaffold_error_body.dart'; import 'package:immich_mobile/shared/ui/scaffold_error_body.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
extension ScaffoldBody<T> on AsyncValue<T> { extension LogOnError<T> on AsyncValue<T> {
static final Logger _scaffoldBodyLog = Logger("ScaffoldBody"); static final Logger _asyncErrorLogger = Logger("AsyncValue");
Widget scaffoldBodyWhen({ Widget widgetWhen({
bool skipLoadingOnRefresh = true,
Widget Function()? onLoading,
Widget Function(Object? error, StackTrace? stack)? onError,
required Widget Function(T data) onData, required Widget Function(T data) onData,
Widget? onError,
}) { }) {
if (isLoading) { if (isLoading) {
return const Center( bool skip = false;
if (isRefreshing) {
skip = skipLoadingOnRefresh;
}
if (!skip) {
return onLoading?.call() ??
const Center(
child: ImmichLoadingIndicator(), child: ImmichLoadingIndicator(),
); );
} }
}
if (hasError && !hasValue) { if (hasError && !hasValue) {
_scaffoldBodyLog.severe("Error occured in AsyncValue", error, stackTrace); _asyncErrorLogger.severe("Error occured", error, stackTrace);
return onError ?? const ScaffoldErrorBody(); return onError?.call(error, stackTrace) ?? const ScaffoldErrorBody();
} }
return onData(requireValue); return onData(requireValue);

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
@ -7,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:timezone/data/latest.dart'; import 'package:timezone/data/latest.dart';
import 'package:immich_mobile/constants/locales.dart'; import 'package:immich_mobile/constants/locales.dart';
import 'package:immich_mobile/modules/backup/background_service/background.service.dart'; import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
@ -28,7 +30,6 @@ import 'package:immich_mobile/shared/providers/app_state.provider.dart';
import 'package:immich_mobile/shared/providers/db.provider.dart'; import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:immich_mobile/shared/services/immich_logger.service.dart'; import 'package:immich_mobile/shared/services/immich_logger.service.dart';
import 'package:immich_mobile/shared/services/local_notification.service.dart'; import 'package:immich_mobile/shared/services/local_notification.service.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
import 'package:immich_mobile/utils/http_ssl_cert_override.dart'; import 'package:immich_mobile/utils/http_ssl_cert_override.dart';
import 'package:immich_mobile/utils/immich_app_theme.dart'; import 'package:immich_mobile/utils/immich_app_theme.dart';
import 'package:immich_mobile/utils/migration.dart'; import 'package:immich_mobile/utils/migration.dart';
@ -43,10 +44,11 @@ void main() async {
await initApp(); await initApp();
await migrateDatabaseIfNeeded(db); await migrateDatabaseIfNeeded(db);
HttpOverrides.global = HttpSSLCertOverride(); HttpOverrides.global = HttpSSLCertOverride();
runApp( runApp(
ProviderScope( ProviderScope(
overrides: [dbProvider.overrideWithValue(db)], overrides: [dbProvider.overrideWithValue(db)],
child: getMainWidget(), child: const MainWidget(),
), ),
); );
} }
@ -108,16 +110,6 @@ Future<Isar> loadDb() async {
return db; return db;
} }
Widget getMainWidget() {
return EasyLocalization(
supportedLocales: locales,
path: translationsPath,
useFallbackTranslations: true,
fallbackLocale: locales.first,
child: const ImmichApp(),
);
}
class ImmichApp extends ConsumerStatefulWidget { class ImmichApp extends ConsumerStatefulWidget {
const ImmichApp({super.key}); const ImmichApp({super.key});
@ -167,10 +159,9 @@ class ImmichAppState extends ConsumerState<ImmichApp>
// Android 8 does not support transparent app bars // Android 8 does not support transparent app bars
final info = await DeviceInfoPlugin().androidInfo; final info = await DeviceInfoPlugin().androidInfo;
if (info.version.sdkInt <= 26) { if (info.version.sdkInt <= 26) {
overlayStyle = overlayStyle = context.isDarkTheme
MediaQuery.of(context).platformBrightness == Brightness.light ? SystemUiOverlayStyle.dark
? SystemUiOverlayStyle.light : SystemUiOverlayStyle.light;
: SystemUiOverlayStyle.dark;
} }
} }
SystemChrome.setSystemUIOverlayStyle(overlayStyle); SystemChrome.setSystemUIOverlayStyle(overlayStyle);
@ -202,9 +193,7 @@ class ImmichAppState extends ConsumerState<ImmichApp>
supportedLocales: context.supportedLocales, supportedLocales: context.supportedLocales,
locale: context.locale, locale: context.locale,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
home: Stack( home: MaterialApp.router(
children: [
MaterialApp.router(
title: 'Immich', title: 'Immich',
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
themeMode: ref.watch(immichThemeProvider), themeMode: ref.watch(immichThemeProvider),
@ -215,9 +204,22 @@ class ImmichAppState extends ConsumerState<ImmichApp>
navigatorObservers: () => [TabNavigationObserver(ref: ref)], navigatorObservers: () => [TabNavigationObserver(ref: ref)],
), ),
), ),
const ImmichLoadingOverlay(), );
], }
), }
// ignore: prefer-single-widget-per-file
class MainWidget extends StatelessWidget {
const MainWidget({super.key});
@override
Widget build(BuildContext context) {
return EasyLocalization(
supportedLocales: locales,
path: translationsPath,
useFallbackTranslations: true,
fallbackLocale: locales.first,
child: const ImmichApp(),
); );
} }
} }

View file

@ -4,12 +4,12 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/activities/models/activity.model.dart'; import 'package:immich_mobile/modules/activities/models/activity.model.dart';
import 'package:immich_mobile/modules/activities/providers/activity.provider.dart'; import 'package:immich_mobile/modules/activities/providers/activity.provider.dart';
import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/ui/confirm_dialog.dart'; import 'package:immich_mobile/shared/ui/confirm_dialog.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
import 'package:immich_mobile/extensions/datetime_extensions.dart'; import 'package:immich_mobile/extensions/datetime_extensions.dart';
import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/image_url_builder.dart';
@ -88,7 +88,7 @@ class ActivitiesPage extends HookConsumerWidget {
width: 40, width: 40,
height: 30, height: 30,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4), borderRadius: const BorderRadius.all(Radius.circular(4)),
image: DecorationImage( image: DecorationImage(
image: CachedNetworkImageProvider( image: CachedNetworkImageProvider(
getThumbnailUrlForRemoteId( getThumbnailUrlForRemoteId(
@ -231,11 +231,8 @@ class ActivitiesPage extends HookConsumerWidget {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(appBarTitle)), appBar: AppBar(title: Text(appBarTitle)),
body: activities.maybeWhen( body: activities.widgetWhen(
orElse: () { onData: (data) {
return const Center(child: ImmichLoadingIndicator());
},
data: (data) {
final liked = data.firstWhereOrNull( final liked = data.firstWhereOrNull(
(a) => (a) =>
a.type == ActivityType.like && a.type == ActivityType.like &&

View file

@ -65,7 +65,7 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
} }
ref.invalidate(albumDetailProvider(album.id)); ref.invalidate(albumDetailProvider(album.id));
Navigator.pop(context); context.pop();
} }
return Card( return Card(

View file

@ -43,6 +43,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText; final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText;
final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum; final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum;
final isProcessing = useProcessingOverlay();
final comments = album.shared final comments = album.shared
? ref.watch( ? ref.watch(
activityStatisticsStateProvider( activityStatisticsStateProvider(
@ -52,7 +53,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
: 0; : 0;
deleteAlbum() async { deleteAlbum() async {
ImmichLoadingOverlayController.appLoader.show(); isProcessing.value = true;
final bool success; final bool success;
if (album.shared) { if (album.shared) {
@ -74,7 +75,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
); );
} }
ImmichLoadingOverlayController.appLoader.hide(); isProcessing.value = false;
} }
Future<void> showConfirmationDialog() async { Future<void> showConfirmationDialog() async {
@ -89,7 +90,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
), ),
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'), onPressed: () => context.pop('Cancel'),
child: Text( child: Text(
'Cancel', 'Cancel',
style: TextStyle( style: TextStyle(
@ -100,7 +101,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.pop(context, 'Confirm'); context.pop('Confirm');
deleteAlbum(); deleteAlbum();
}, },
child: Text( child: Text(
@ -122,7 +123,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
} }
void onLeaveAlbumPressed() async { void onLeaveAlbumPressed() async {
ImmichLoadingOverlayController.appLoader.show(); isProcessing.value = true;
bool isSuccess = bool isSuccess =
await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(album); await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(album);
@ -131,7 +132,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
context context
.autoNavigate(const TabControllerRoute(children: [SharingRoute()])); .autoNavigate(const TabControllerRoute(children: [SharingRoute()]));
} else { } else {
Navigator.pop(context); context.pop();
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: "album_viewer_appbar_share_err_leave".tr(), msg: "album_viewer_appbar_share_err_leave".tr(),
@ -140,11 +141,11 @@ class AlbumViewerAppbar extends HookConsumerWidget
); );
} }
ImmichLoadingOverlayController.appLoader.hide(); isProcessing.value = false;
} }
void onRemoveFromAlbumPressed() async { void onRemoveFromAlbumPressed() async {
ImmichLoadingOverlayController.appLoader.show(); isProcessing.value = true;
bool isSuccess = bool isSuccess =
await ref.watch(sharedAlbumProvider.notifier).removeAssetFromAlbum( await ref.watch(sharedAlbumProvider.notifier).removeAssetFromAlbum(
@ -153,12 +154,12 @@ class AlbumViewerAppbar extends HookConsumerWidget
); );
if (isSuccess) { if (isSuccess) {
Navigator.pop(context); context.pop();
selectionDisabled(); selectionDisabled();
ref.watch(albumProvider.notifier).getAllAlbums(); ref.watch(albumProvider.notifier).getAllAlbums();
ref.invalidate(albumDetailProvider(album.id)); ref.invalidate(albumDetailProvider(album.id));
} else { } else {
Navigator.pop(context); context.pop();
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: "album_viewer_appbar_share_err_remove".tr(), msg: "album_viewer_appbar_share_err_remove".tr(),
@ -167,7 +168,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
); );
} }
ImmichLoadingOverlayController.appLoader.hide(); isProcessing.value = false;
} }
void handleShareAssets( void handleShareAssets(
@ -198,9 +199,9 @@ class AlbumViewerAppbar extends HookConsumerWidget
} }
void onShareAssetsTo() async { void onShareAssetsTo() async {
ImmichLoadingOverlayController.appLoader.show(); isProcessing.value = true;
handleShareAssets(ref, context, selected); handleShareAssets(ref, context, selected);
ImmichLoadingOverlayController.appLoader.hide(); isProcessing.value = false;
} }
buildBottomSheetActions() { buildBottomSheetActions() {
@ -253,7 +254,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
ListTile( ListTile(
leading: const Icon(Icons.person_add_alt_rounded), leading: const Icon(Icons.person_add_alt_rounded),
onTap: () { onTap: () {
Navigator.pop(context); context.pop();
onAddUsers!(album); onAddUsers!(album);
}, },
title: const Text( title: const Text(
@ -265,7 +266,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
leading: const Icon(Icons.share_rounded), leading: const Icon(Icons.share_rounded),
onTap: () { onTap: () {
context.autoPush(SharedLinkEditRoute(albumId: album.remoteId)); context.autoPush(SharedLinkEditRoute(albumId: album.remoteId));
Navigator.pop(context); context.pop();
}, },
title: const Text( title: const Text(
"control_bottom_app_bar_share", "control_bottom_app_bar_share",
@ -286,7 +287,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
ListTile( ListTile(
leading: const Icon(Icons.add_photo_alternate_outlined), leading: const Icon(Icons.add_photo_alternate_outlined),
onTap: () { onTap: () {
Navigator.pop(context); context.pop();
onAddPhotos!(album); onAddPhotos!(album);
}, },
title: const Text( title: const Text(

View file

@ -24,10 +24,11 @@ class AlbumOptionsPage extends HookConsumerWidget {
final owner = album.owner.value; final owner = album.owner.value;
final userId = ref.watch(authenticationProvider).userId; final userId = ref.watch(authenticationProvider).userId;
final activityEnabled = useState(album.activityEnabled); final activityEnabled = useState(album.activityEnabled);
final isProcessing = useProcessingOverlay();
final isOwner = owner?.id == userId; final isOwner = owner?.id == userId;
void showErrorMessage() { void showErrorMessage() {
Navigator.pop(context); context.pop();
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: "shared_album_section_people_action_error".tr(), msg: "shared_album_section_people_action_error".tr(),
@ -37,7 +38,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
} }
void leaveAlbum() async { void leaveAlbum() async {
ImmichLoadingOverlayController.appLoader.show(); isProcessing.value = true;
try { try {
final isSuccess = final isSuccess =
@ -54,11 +55,11 @@ class AlbumOptionsPage extends HookConsumerWidget {
showErrorMessage(); showErrorMessage();
} }
ImmichLoadingOverlayController.appLoader.hide(); isProcessing.value = false;
} }
void removeUserFromAlbum(User user) async { void removeUserFromAlbum(User user) async {
ImmichLoadingOverlayController.appLoader.show(); isProcessing.value = true;
try { try {
await ref await ref
@ -70,8 +71,8 @@ class AlbumOptionsPage extends HookConsumerWidget {
showErrorMessage(); showErrorMessage();
} }
Navigator.pop(context); context.pop();
ImmichLoadingOverlayController.appLoader.hide(); isProcessing.value = false;
} }
void handleUserClick(User user) { void handleUserClick(User user) {
@ -180,9 +181,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded), icon: const Icon(Icons.arrow_back_ios_new_rounded),
onPressed: () { onPressed: () => context.autoPop(null),
context.autoPop(null);
},
), ),
centerTitle: true, centerTitle: true,
title: Text("translated_text_options".tr()), title: Text("translated_text_options".tr()),

View file

@ -4,6 +4,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart'; import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart';
import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart'; import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart';
@ -17,7 +18,6 @@ import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart'; import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
@ -33,6 +33,7 @@ class AlbumViewerPage extends HookConsumerWidget {
final userId = ref.watch(authenticationProvider).userId; final userId = ref.watch(authenticationProvider).userId;
final selection = useState<Set<Asset>>({}); final selection = useState<Set<Asset>>({});
final multiSelectEnabled = useState(false); final multiSelectEnabled = useState(false);
final isProcessing = useProcessingOverlay();
useEffect( useEffect(
() { () {
@ -75,10 +76,9 @@ class AlbumViewerPage extends HookConsumerWidget {
), ),
); );
if (returnPayload != null) { if (returnPayload != null && returnPayload.selectedAssets.isNotEmpty) {
// Check if there is new assets add // Check if there is new assets add
if (returnPayload.selectedAssets.isNotEmpty) { isProcessing.value = true;
ImmichLoadingOverlayController.appLoader.show();
var addAssetsResult = var addAssetsResult =
await ref.watch(albumServiceProvider).addAdditionalAssetToAlbum( await ref.watch(albumServiceProvider).addAdditionalAssetToAlbum(
@ -86,13 +86,11 @@ class AlbumViewerPage extends HookConsumerWidget {
albumInfo, albumInfo,
); );
if (addAssetsResult != null && if (addAssetsResult != null && addAssetsResult.successfullyAdded > 0) {
addAssetsResult.successfullyAdded > 0) {
ref.invalidate(albumDetailProvider(albumId)); ref.invalidate(albumDetailProvider(albumId));
} }
ImmichLoadingOverlayController.appLoader.hide(); isProcessing.value = false;
}
} }
} }
@ -102,7 +100,7 @@ class AlbumViewerPage extends HookConsumerWidget {
); );
if (sharedUserIds != null) { if (sharedUserIds != null) {
ImmichLoadingOverlayController.appLoader.show(); isProcessing.value = true;
var isSuccess = await ref var isSuccess = await ref
.watch(albumServiceProvider) .watch(albumServiceProvider)
@ -112,7 +110,7 @@ class AlbumViewerPage extends HookConsumerWidget {
ref.invalidate(albumDetailProvider(album.id)); ref.invalidate(albumDetailProvider(album.id));
} }
ImmichLoadingOverlayController.appLoader.hide(); isProcessing.value = false;
} }
} }
@ -260,13 +258,11 @@ class AlbumViewerPage extends HookConsumerWidget {
error: (error, stackTrace) => AppBar(title: const Text("Error")), error: (error, stackTrace) => AppBar(title: const Text("Error")),
loading: () => AppBar(), loading: () => AppBar(),
), ),
body: album.when( body: album.widgetWhen(
data: (data) => WillPopScope( onData: (data) => WillPopScope(
onWillPop: onWillPop, onWillPop: onWillPop,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () => titleFocusNode.unfocus(),
titleFocusNode.unfocus();
},
child: ImmichAssetGrid( child: ImmichAssetGrid(
renderList: data.renderList, renderList: data.renderList,
listener: selectionListener, listener: selectionListener,
@ -285,10 +281,6 @@ class AlbumViewerPage extends HookConsumerWidget {
), ),
), ),
), ),
error: (e, _) => Center(child: Text("Error loading album info!\n$e")),
loading: () => const Center(
child: ImmichLoadingIndicator(),
),
), ),
); );
} }

View file

@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart'; import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/render_list.provider.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/render_list.provider.dart';
@ -85,12 +86,8 @@ class AssetSelectionPage extends HookConsumerWidget {
), ),
], ],
), ),
body: renderList.when( body: renderList.widgetWhen(
data: (data) => buildBody(data), onData: (data) => buildBody(data),
error: (error, stackTrace) => Center(
child: Text(error.toString()),
),
loading: () => const Center(child: CircularProgressIndicator()),
), ),
); );
} }

View file

@ -2,11 +2,11 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.dart'; import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.dart';
import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
class SelectAdditionalUserForSharingPage extends HookConsumerWidget { class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
@ -137,8 +137,8 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
), ),
], ],
), ),
body: suggestedShareUsers.when( body: suggestedShareUsers.widgetWhen(
data: (users) { onData: (users) {
for (var sharedUsers in album.sharedUsers) { for (var sharedUsers in album.sharedUsers) {
users.removeWhere( users.removeWhere(
(u) => u.id == sharedUsers.id || u.id == album.ownerId, (u) => u.id == sharedUsers.id || u.id == album.ownerId,
@ -147,10 +147,6 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
return buildUserList(users); return buildUserList(users);
}, },
error: (e, _) => Text("Error loading suggested users $e"),
loading: () => const Center(
child: ImmichLoadingIndicator(),
),
), ),
); );
} }

View file

@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/album/providers/album_title.provider.dart'; import 'package:immich_mobile/modules/album/providers/album_title.provider.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
@ -9,7 +10,6 @@ import 'package:immich_mobile/modules/album/providers/suggested_shared_users.pro
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
class SelectUserForSharingPage extends HookConsumerWidget { class SelectUserForSharingPage extends HookConsumerWidget {
@ -42,7 +42,12 @@ class SelectUserForSharingPage extends HookConsumerWidget {
ScaffoldMessenger( ScaffoldMessenger(
child: SnackBar( child: SnackBar(
content: const Text('select_user_for_sharing_page_err_album').tr(), content: Text(
'select_user_for_sharing_page_err_album',
style: context.textTheme.bodyLarge?.copyWith(
color: context.primaryColor,
),
).tr(),
), ),
); );
} }
@ -166,14 +171,10 @@ class SelectUserForSharingPage extends HookConsumerWidget {
), ),
], ],
), ),
body: suggestedShareUsers.when( body: suggestedShareUsers.widgetWhen(
data: (users) { onData: (users) {
return buildUserList(users); return buildUserList(users);
}, },
error: (e, _) => Text("Error loading suggested users $e"),
loading: () => const Center(
child: ImmichLoadingIndicator(),
),
), ),
); );
} }

View file

@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/archive/providers/archive_asset_provider.dart'; import 'package:immich_mobile/modules/archive/providers/archive_asset_provider.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
@ -48,11 +49,9 @@ class ArchivePage extends HookConsumerWidget {
child: SizedBox( child: SizedBox(
height: 64, height: 64,
child: Card( child: Card(
child: Column( child: ListTile(
children: [ shape: const RoundedRectangleBorder(
ListTile( borderRadius: BorderRadius.all(Radius.circular(10)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
), ),
leading: const Icon( leading: const Icon(
Icons.unarchive_rounded, Icons.unarchive_rounded,
@ -78,26 +77,19 @@ class ArchivePage extends HookConsumerWidget {
} }
}, },
), ),
],
),
), ),
), ),
), ),
); );
} }
return archivedAssets.when( return Scaffold(
loading: () => Scaffold( appBar: archivedAssets.maybeWhen(
appBar: buildAppBar("?"), data: (data) => buildAppBar(data.totalAssets.toString()),
body: const Center(child: CircularProgressIndicator()), orElse: () => buildAppBar("?"),
), ),
error: (error, stackTrace) => Scaffold( body: archivedAssets.widgetWhen(
appBar: buildAppBar("Error"), onData: (data) => data.isEmpty
body: Center(child: Text(error.toString())),
),
data: (data) => Scaffold(
appBar: buildAppBar(data.totalAssets.toString()),
body: data.isEmpty
? Center( ? Center(
child: Text('archive_page_no_archived_assets'.tr()), child: Text('archive_page_no_archived_assets'.tr()),
) )

View file

@ -62,8 +62,14 @@ class AdvancedBottomSheet extends HookConsumerWidget {
ClipboardData(text: assetDetail.toString()), ClipboardData(text: assetDetail.toString()),
).then((_) { ).then((_) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( SnackBar(
content: Text("Copied to clipboard"), content: Text(
"Copied to clipboard",
style: context.textTheme.bodyLarge
?.copyWith(
color: context.primaryColor,
),
),
), ),
); );
}); });

View file

@ -514,7 +514,7 @@ class GalleryViewerPage extends HookConsumerWidget {
currentAsset, currentAsset,
stackElements.elementAt(stackIndex.value), stackElements.elementAt(stackIndex.value),
); );
Navigator.pop(ctx); ctx.pop();
context.autoPop(); context.autoPop();
}, },
title: const Text( title: const Text(
@ -541,7 +541,7 @@ class GalleryViewerPage extends HookConsumerWidget {
stackElements.elementAt(1), stackElements.elementAt(1),
childrenToRemove: [currentAsset], childrenToRemove: [currentAsset],
); );
Navigator.pop(ctx); ctx.pop();
context.autoPop(); context.autoPop();
} else { } else {
await ref.read(assetStackServiceProvider).updateStack( await ref.read(assetStackServiceProvider).updateStack(
@ -551,7 +551,7 @@ class GalleryViewerPage extends HookConsumerWidget {
], ],
); );
removeAssetFromStack(); removeAssetFromStack();
Navigator.pop(ctx); ctx.pop();
} }
}, },
title: const Text( title: const Text(
@ -569,7 +569,7 @@ class GalleryViewerPage extends HookConsumerWidget {
currentAsset, currentAsset,
childrenToRemove: stack, childrenToRemove: stack,
); );
Navigator.pop(ctx); ctx.pop();
context.autoPop(); context.autoPop();
}, },
title: const Text( title: const Text(

View file

@ -42,6 +42,9 @@ class BackupService {
try { try {
return await _apiService.assetApi.getUserAssetsByDeviceId(deviceId); return await _apiService.assetApi.getUserAssetsByDeviceId(deviceId);
// TODO! Start using this in 1.92.0
// return await _apiService.assetApi.getAllUserAssetsByDeviceId(deviceId);
} catch (e) { } catch (e) {
debugPrint('Error [getDeviceBackupAsset] ${e.toString()}'); debugPrint('Error [getDeviceBackupAsset] ${e.toString()}');
return null; return null;
@ -275,13 +278,6 @@ class BackupService {
req.files.add(assetRawUploadData); req.files.add(assetRawUploadData);
if (entity.isLivePhoto) {
var livePhotoRawUploadData = await _getLivePhotoFile(entity);
if (livePhotoRawUploadData != null) {
req.files.add(livePhotoRawUploadData);
}
}
setCurrentUploadAssetCb( setCurrentUploadAssetCb(
CurrentUploadAsset( CurrentUploadAsset(
id: entity.id, id: entity.id,
@ -296,6 +292,29 @@ class BackupService {
var response = var response =
await httpClient.send(req, cancellationToken: cancelToken); await httpClient.send(req, cancellationToken: cancelToken);
// Send live photo separately
if (entity.isLivePhoto) {
var livePhotoRawUploadData = await _getLivePhotoFile(entity);
if (livePhotoRawUploadData != null) {
var livePhotoReq = MultipartRequest(
req.method,
req.url,
onProgress: req.onProgress,
)
..headers.addAll(req.headers)
..fields.addAll(req.fields);
livePhotoReq.files.add(livePhotoRawUploadData);
// Send live photo only if the non-motion part is successful
if (response.statusCode == 200 || response.statusCode == 201) {
response = await httpClient.send(
livePhotoReq,
cancellationToken: cancelToken,
);
}
}
}
if (response.statusCode == 200) { if (response.statusCode == 200) {
// asset is a duplicate (already exists on the server) // asset is a duplicate (already exists on the server)
duplicatedAssetIds.add(entity.id); duplicatedAssetIds.add(entity.id);
@ -353,7 +372,7 @@ class BackupService {
var fileStream = motionFile.openRead(); var fileStream = motionFile.openRead();
String fileName = p.basename(motionFile.path); String fileName = p.basename(motionFile.path);
return http.MultipartFile( return http.MultipartFile(
"livePhotoData", "assetData",
fileStream, fileStream,
motionFile.lengthSync(), motionFile.lengthSync(),
filename: fileName, filename: fileName,

View file

@ -229,6 +229,9 @@ class BackupControllerPage extends HookConsumerWidget {
final snackBar = SnackBar( final snackBar = SnackBar(
content: Text( content: Text(
msg.tr(), msg.tr(),
style: context.textTheme.bodyLarge?.copyWith(
color: context.primaryColor,
),
), ),
backgroundColor: Colors.red, backgroundColor: Colors.red,
); );

View file

@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart'; import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
@ -62,11 +63,9 @@ class FavoritesPage extends HookConsumerWidget {
child: SizedBox( child: SizedBox(
height: 64, height: 64,
child: Card( child: Card(
child: Column( child: ListTile(
children: [ shape: const RoundedRectangleBorder(
ListTile( borderRadius: BorderRadius.all(Radius.circular(10)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
), ),
leading: const Icon( leading: const Icon(
Icons.star_border, Icons.star_border,
@ -77,8 +76,6 @@ class FavoritesPage extends HookConsumerWidget {
), ),
onTap: processing.value ? null : unfavorite, onTap: processing.value ? null : unfavorite,
), ),
],
),
), ),
), ),
), ),
@ -87,10 +84,8 @@ class FavoritesPage extends HookConsumerWidget {
return Scaffold( return Scaffold(
appBar: buildAppBar(), appBar: buildAppBar(),
body: ref.watch(favoriteAssetsProvider).when( body: ref.watch(favoriteAssetsProvider).widgetWhen(
loading: () => const Center(child: CircularProgressIndicator()), onData: (data) => data.isEmpty
error: (error, stackTrace) => Center(child: Text(error.toString())),
data: (data) => data.isEmpty
? Center( ? Center(
child: Text('favorites_page_no_favorites'.tr()), child: Text('favorites_page_no_favorites'.tr()),
) )

View file

@ -5,13 +5,13 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/render_list.provider.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/render_list.provider.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid_view.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid_view.dart';
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
class ImmichAssetGrid extends HookConsumerWidget { class ImmichAssetGrid extends HookConsumerWidget {
@ -130,12 +130,8 @@ class ImmichAssetGrid extends HookConsumerWidget {
if (renderList != null) return buildAssetGridView(renderList!); if (renderList != null) return buildAssetGridView(renderList!);
final renderListFuture = ref.watch(renderListProvider(assets!)); final renderListFuture = ref.watch(renderListProvider(assets!));
return renderListFuture.when( return renderListFuture.widgetWhen(
data: (renderList) => buildAssetGridView(renderList), onData: (renderList) => buildAssetGridView(renderList),
error: (err, stack) => Center(child: Text("$err")),
loading: () => const Center(
child: ImmichLoadingIndicator(),
),
); );
} }
} }

View file

@ -197,7 +197,9 @@ class ThumbnailImage extends StatelessWidget {
}, },
child: Stack( child: Stack(
children: [ children: [
Container( AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.decelerate,
decoration: BoxDecoration( decoration: BoxDecoration(
border: multiselectEnabled && isSelected border: multiselectEnabled && isSelected
? Border.all( ? Border.all(

View file

@ -28,6 +28,7 @@ import 'package:immich_mobile/shared/providers/websocket.provider.dart';
import 'package:immich_mobile/shared/ui/immich_app_bar.dart'; import 'package:immich_mobile/shared/ui/immich_app_bar.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
import 'package:immich_mobile/utils/selection_handlers.dart'; import 'package:immich_mobile/utils/selection_handlers.dart';
class HomePage extends HookConsumerWidget { class HomePage extends HookConsumerWidget {
@ -50,7 +51,7 @@ class HomePage extends HookConsumerWidget {
final tipOneOpacity = useState(0.0); final tipOneOpacity = useState(0.0);
final refreshCount = useState(0); final refreshCount = useState(0);
final processing = useState(false); final processing = useProcessingOverlay();
useEffect( useEffect(
() { () {
@ -323,16 +324,12 @@ class HomePage extends HookConsumerWidget {
} else { } else {
refreshCount.value++; refreshCount.value++;
// set counter back to 0 if user does not request refresh again // set counter back to 0 if user does not request refresh again
Timer(const Duration(seconds: 4), () { Timer(const Duration(seconds: 4), () => refreshCount.value = 0);
refreshCount.value = 0;
});
} }
} }
buildLoadingIndicator() { buildLoadingIndicator() {
Timer(const Duration(seconds: 2), () { Timer(const Duration(seconds: 2), () => tipOneOpacity.value = 1);
tipOneOpacity.value = 1;
});
return Center( return Center(
child: Column( child: Column(
@ -415,7 +412,6 @@ class HomePage extends HookConsumerWidget {
selectionAssetState: selectionAssetState.value, selectionAssetState: selectionAssetState.value,
onStack: onStack, onStack: onStack,
), ),
if (processing.value) const Center(child: ImmichLoadingIndicator()),
], ],
), ),
); );

View file

@ -48,7 +48,7 @@ class LoginForm extends HookConsumerWidget {
/// Fetch the server login credential and enables oAuth login if necessary /// Fetch the server login credential and enables oAuth login if necessary
/// Returns true if successful, false otherwise /// Returns true if successful, false otherwise
Future<bool> getServerLoginCredential() async { Future<bool> getServerLoginCredential() async {
final serverUrl = serverEndpointController.text.trim(); final serverUrl = sanitizeUrl(serverEndpointController.text);
// Guard empty URL // Guard empty URL
if (serverUrl.isEmpty) { if (serverUrl.isEmpty) {
@ -127,6 +127,12 @@ class LoginForm extends HookConsumerWidget {
); );
populateTestLoginInfo() { populateTestLoginInfo() {
usernameController.text = 'demo@immich.app';
passwordController.text = 'demo';
serverEndpointController.text = 'https://demo.immich.app';
}
populateTestLoginInfo1() {
usernameController.text = 'testuser@email.com'; usernameController.text = 'testuser@email.com';
passwordController.text = 'password'; passwordController.text = 'password';
serverEndpointController.text = 'http://10.1.15.216:2283/api'; serverEndpointController.text = 'http://10.1.15.216:2283/api';
@ -144,7 +150,7 @@ class LoginForm extends HookConsumerWidget {
await ref.read(authenticationProvider.notifier).login( await ref.read(authenticationProvider.notifier).login(
usernameController.text, usernameController.text,
passwordController.text, passwordController.text,
serverEndpointController.text.trim(), sanitizeUrl(serverEndpointController.text),
); );
if (isAuthenticated) { if (isAuthenticated) {
// Resume backup (if enable) then navigate // Resume backup (if enable) then navigate
@ -181,7 +187,7 @@ class LoginForm extends HookConsumerWidget {
try { try {
oAuthServerConfig = await oAuthService oAuthServerConfig = await oAuthService
.getOAuthServerConfig(serverEndpointController.text); .getOAuthServerConfig(sanitizeUrl(serverEndpointController.text));
isLoading.value = true; isLoading.value = true;
} catch (e) { } catch (e) {
@ -203,7 +209,7 @@ class LoginForm extends HookConsumerWidget {
.watch(authenticationProvider.notifier) .watch(authenticationProvider.notifier)
.setSuccessLoginInfo( .setSuccessLoginInfo(
accessToken: loginResponseDto.accessToken, accessToken: loginResponseDto.accessToken,
serverUrl: serverEndpointController.text, serverUrl: sanitizeUrl(serverEndpointController.text),
); );
if (isSuccess) { if (isSuccess) {
@ -299,7 +305,7 @@ class LoginForm extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Text( Text(
serverEndpointController.text, sanitizeUrl(serverEndpointController.text),
style: context.textTheme.displaySmall, style: context.textTheme.displaySmall,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@ -387,6 +393,7 @@ class LoginForm extends HookConsumerWidget {
children: [ children: [
GestureDetector( GestureDetector(
onDoubleTap: () => populateTestLoginInfo(), onDoubleTap: () => populateTestLoginInfo(),
onLongPress: () => populateTestLoginInfo1(),
child: RotationTransition( child: RotationTransition(
turns: logoAnimationController, turns: logoAnimationController,
child: const ImmichLogo( child: const ImmichLogo(

View file

@ -17,7 +17,7 @@ class MemoryLane extends HookConsumerWidget {
.whenData( .whenData(
(memories) => memories != null (memories) => memories != null
? Container( ? Container(
margin: const EdgeInsets.only(top: 10), margin: const EdgeInsets.only(top: 10, left: 10),
height: 200, height: 200,
child: ListView.builder( child: ListView.builder(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,

View file

@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/partner/providers/partner.provider.dart'; import 'package:immich_mobile/modules/partner/providers/partner.provider.dart';
import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart';
class PartnerDetailPage extends HookConsumerWidget { class PartnerDetailPage extends HookConsumerWidget {
@ -71,8 +71,8 @@ class PartnerDetailPage extends HookConsumerWidget {
), ),
], ],
), ),
body: assets.when( body: assets.widgetWhen(
data: (renderList) => renderList.isEmpty onData: (renderList) => renderList.isEmpty
? Padding( ? Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Text( child: Text(
@ -84,8 +84,6 @@ class PartnerDetailPage extends HookConsumerWidget {
onRefresh: () => onRefresh: () =>
ref.read(assetProvider.notifier).getPartnerAssets(partner), ref.read(assetProvider.notifier).getPartnerAssets(partner),
), ),
error: (e, _) => Text("Error loading partners:\n$e"),
loading: () => const Center(child: ImmichLoadingIndicator()),
), ),
); );
} }

View file

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/partner/providers/partner.provider.dart'; import 'package:immich_mobile/modules/partner/providers/partner.provider.dart';
import 'package:immich_mobile/modules/partner/services/partner.service.dart'; import 'package:immich_mobile/modules/partner/services/partner.service.dart';
import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/models/user.dart';
@ -34,7 +35,7 @@ class PartnerPage extends HookConsumerWidget {
children: [ children: [
for (User u in users) for (User u in users)
SimpleDialogOption( SimpleDialogOption(
onPressed: () => Navigator.pop(context, u), onPressed: () => context.pop(u),
child: Row( child: Row(
children: [ children: [
Padding( Padding(
@ -70,8 +71,7 @@ class PartnerPage extends HookConsumerWidget {
builder: (BuildContext context) { builder: (BuildContext context) {
return ConfirmDialog( return ConfirmDialog(
title: "partner_page_stop_sharing_title", title: "partner_page_stop_sharing_title",
content: content: "partner_page_stop_sharing_content".tr(args: [u.name]),
"partner_page_stop_sharing_content".tr(args: [u.name]),
onOk: () => ref.read(partnerServiceProvider).removePartner(u), onOk: () => ref.read(partnerServiceProvider).removePartner(u),
); );
}, },
@ -118,6 +118,7 @@ class PartnerPage extends HookConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0), padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
@ -126,13 +127,16 @@ class PartnerPage extends HookConsumerWidget {
style: TextStyle(fontSize: 14), style: TextStyle(fontSize: 14),
).tr(), ).tr(),
), ),
ElevatedButton.icon( Align(
alignment: Alignment.center,
child: ElevatedButton.icon(
onPressed: availableUsers.whenOrNull( onPressed: availableUsers.whenOrNull(
data: (data) => addNewUsersHandler, data: (data) => addNewUsersHandler,
), ),
icon: const Icon(Icons.person_add), icon: const Icon(Icons.person_add),
label: const Text("partner_page_add_partner").tr(), label: const Text("partner_page_add_partner").tr(),
), ),
),
], ],
), ),
), ),

View file

@ -1,10 +1,10 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/search/providers/all_motion_photos.provider.dart'; import 'package:immich_mobile/modules/search/providers/all_motion_photos.provider.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
class AllMotionPhotosPage extends HookConsumerWidget { class AllMotionPhotosPage extends HookConsumerWidget {
const AllMotionPhotosPage({super.key}); const AllMotionPhotosPage({super.key});
@ -21,14 +21,10 @@ class AllMotionPhotosPage extends HookConsumerWidget {
icon: const Icon(Icons.arrow_back_ios_rounded), icon: const Icon(Icons.arrow_back_ios_rounded),
), ),
), ),
body: motionPhotos.when( body: motionPhotos.widgetWhen(
data: (assets) => ImmichAssetGrid( onData: (assets) => ImmichAssetGrid(
assets: assets, assets: assets,
), ),
error: (e, s) => Text(e.toString()),
loading: () => const Center(
child: ImmichLoadingIndicator(),
),
), ),
); );
} }

View file

@ -1,10 +1,10 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/search/providers/people.provider.dart'; import 'package:immich_mobile/modules/search/providers/people.provider.dart';
import 'package:immich_mobile/modules/search/ui/explore_grid.dart'; import 'package:immich_mobile/modules/search/ui/explore_grid.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
class AllPeoplePage extends HookConsumerWidget { class AllPeoplePage extends HookConsumerWidget {
const AllPeoplePage({super.key}); const AllPeoplePage({super.key});
@ -23,12 +23,8 @@ class AllPeoplePage extends HookConsumerWidget {
icon: const Icon(Icons.arrow_back_ios_rounded), icon: const Icon(Icons.arrow_back_ios_rounded),
), ),
), ),
body: curatedPeople.when( body: curatedPeople.widgetWhen(
loading: () => const Center(child: ImmichLoadingIndicator()), onData: (people) => ExploreGrid(
error: (err, stack) => Center(
child: Text('Error: $err'),
),
data: (people) => ExploreGrid(
isPeople: true, isPeople: true,
curatedContent: people, curatedContent: people,
), ),

View file

@ -1,10 +1,10 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/search/providers/all_video_assets.provider.dart'; import 'package:immich_mobile/modules/search/providers/all_video_assets.provider.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
class AllVideosPage extends HookConsumerWidget { class AllVideosPage extends HookConsumerWidget {
const AllVideosPage({super.key}); const AllVideosPage({super.key});
@ -21,14 +21,10 @@ class AllVideosPage extends HookConsumerWidget {
icon: const Icon(Icons.arrow_back_ios_rounded), icon: const Icon(Icons.arrow_back_ios_rounded),
), ),
), ),
body: videos.when( body: videos.widgetWhen(
data: (assets) => ImmichAssetGrid( onData: (assets) => ImmichAssetGrid(
assets: assets, assets: assets,
), ),
error: (e, s) => Text(e.toString()),
loading: () => const Center(
child: ImmichLoadingIndicator(),
),
), ),
); );
} }

View file

@ -1,11 +1,11 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/search/models/curated_content.dart'; import 'package:immich_mobile/modules/search/models/curated_content.dart';
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart'; import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
import 'package:immich_mobile/modules/search/ui/explore_grid.dart'; import 'package:immich_mobile/modules/search/ui/explore_grid.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class CuratedLocationPage extends HookConsumerWidget { class CuratedLocationPage extends HookConsumerWidget {
@ -26,12 +26,8 @@ class CuratedLocationPage extends HookConsumerWidget {
icon: const Icon(Icons.arrow_back_ios_rounded), icon: const Icon(Icons.arrow_back_ios_rounded),
), ),
), ),
body: curatedLocation.when( body: curatedLocation.widgetWhen(
loading: () => const Center(child: ImmichLoadingIndicator()), onData: (curatedLocations) => ExploreGrid(
error: (err, stack) => Center(
child: Text('Error: $err'),
),
data: (curatedLocations) => ExploreGrid(
curatedContent: curatedLocations curatedContent: curatedLocations
.map( .map(
(l) => CuratedContent( (l) => CuratedContent(

View file

@ -8,7 +8,6 @@ import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'
import 'package:immich_mobile/modules/search/providers/people.provider.dart'; import 'package:immich_mobile/modules/search/providers/people.provider.dart';
import 'package:immich_mobile/modules/search/ui/person_name_edit_form.dart'; import 'package:immich_mobile/modules/search/ui/person_name_edit_form.dart';
import 'package:immich_mobile/shared/models/store.dart' as isar_store; import 'package:immich_mobile/shared/models/store.dart' as isar_store;
import 'package:immich_mobile/shared/ui/scaffold_error_body.dart';
import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/image_url_builder.dart';
class PersonResultPage extends HookConsumerWidget { class PersonResultPage extends HookConsumerWidget {
@ -112,7 +111,7 @@ class PersonResultPage extends HookConsumerWidget {
), ),
], ],
), ),
body: ref.watch(personAssetsProvider(personId)).scaffoldBodyWhen( body: ref.watch(personAssetsProvider(personId)).widgetWhen(
onData: (renderList) => ImmichAssetGrid( onData: (renderList) => ImmichAssetGrid(
renderList: renderList, renderList: renderList,
topWidget: Padding( topWidget: Padding(
@ -137,7 +136,6 @@ class PersonResultPage extends HookConsumerWidget {
), ),
), ),
), ),
onError: const ScaffoldErrorBody(icon: Icons.person_off_outlined),
), ),
); );
} }

View file

@ -1,10 +1,10 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/search/providers/recently_added.provider.dart'; import 'package:immich_mobile/modules/search/providers/recently_added.provider.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
class RecentlyAddedPage extends HookConsumerWidget { class RecentlyAddedPage extends HookConsumerWidget {
const RecentlyAddedPage({super.key}); const RecentlyAddedPage({super.key});
@ -21,14 +21,10 @@ class RecentlyAddedPage extends HookConsumerWidget {
icon: const Icon(Icons.arrow_back_ios_rounded), icon: const Icon(Icons.arrow_back_ios_rounded),
), ),
), ),
body: recents.when( body: recents.widgetWhen(
data: (searchResponse) => ImmichAssetGrid( onData: (searchResponse) => ImmichAssetGrid(
assets: searchResponse, assets: searchResponse,
), ),
error: (e, s) => Text(e.toString()),
loading: () => const Center(
child: ImmichLoadingIndicator(),
),
), ),
); );
} }

View file

@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/search/models/curated_content.dart'; import 'package:immich_mobile/modules/search/models/curated_content.dart';
import 'package:immich_mobile/modules/search/providers/people.provider.dart'; import 'package:immich_mobile/modules/search/providers/people.provider.dart';
@ -15,7 +16,7 @@ import 'package:immich_mobile/modules/search/ui/search_row_title.dart';
import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart'; import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart'; import 'package:immich_mobile/shared/providers/server_info.provider.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/scaffold_error_body.dart';
// ignore: must_be_immutable // ignore: must_be_immutable
class SearchPage extends HookConsumerWidget { class SearchPage extends HookConsumerWidget {
@ -73,10 +74,9 @@ class SearchPage extends HookConsumerWidget {
buildPeople() { buildPeople() {
return SizedBox( return SizedBox(
height: imageSize, height: imageSize,
child: curatedPeople.when( child: curatedPeople.widgetWhen(
loading: () => const Center(child: ImmichLoadingIndicator()), onError: (error, stack) => const ScaffoldErrorBody(withIcon: false),
error: (err, stack) => Center(child: Text('Error: $err')), onData: (people) => CuratedPeopleRow(
data: (people) => CuratedPeopleRow(
content: people.take(12).toList(), content: people.take(12).toList(),
onTap: (content, index) { onTap: (content, index) {
context.autoPush( context.autoPush(
@ -97,10 +97,9 @@ class SearchPage extends HookConsumerWidget {
buildPlaces() { buildPlaces() {
return SizedBox( return SizedBox(
height: imageSize, height: imageSize,
child: curatedLocation.when( child: curatedLocation.widgetWhen(
loading: () => const Center(child: ImmichLoadingIndicator()), onError: (error, stack) => const ScaffoldErrorBody(withIcon: false),
error: (err, stack) => Center(child: Text('Error: $err')), onData: (locations) => CuratedPlacesRow(
data: (locations) => CuratedPlacesRow(
isMapEnabled: isMapEnabled, isMapEnabled: isMapEnabled,
content: locations content: locations
.map( .map(

View file

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/settings/ui/advanced_settings/advanced_settings.dart'; import 'package:immich_mobile/modules/settings/ui/advanced_settings/advanced_settings.dart';
import 'package:immich_mobile/modules/settings/ui/asset_list_settings/asset_list_settings.dart'; import 'package:immich_mobile/modules/settings/ui/asset_list_settings/asset_list_settings.dart';
import 'package:immich_mobile/modules/settings/ui/local_storage_settings/local_storage_settings.dart'; import 'package:immich_mobile/modules/settings/ui/local_storage_settings/local_storage_settings.dart';
@ -18,9 +19,7 @@ class SettingsPage extends HookConsumerWidget {
leading: IconButton( leading: IconButton(
iconSize: 20, iconSize: 20,
splashRadius: 24, splashRadius: 24,
onPressed: () { onPressed: () => context.pop(),
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back_ios_new_rounded), icon: const Icon(Icons.arrow_back_ios_new_rounded),
), ),
automaticallyImplyLeading: false, automaticallyImplyLeading: false,

View file

@ -1,4 +1,5 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
@ -26,13 +27,13 @@ class SharedLinkItem extends ConsumerWidget {
} }
Widget getExpiryDuration(bool isDarkMode) { Widget getExpiryDuration(bool isDarkMode) {
var expiresText = "Expires ∞"; var expiresText = "shared_link_expires_never".tr();
if (sharedLink.expiresAt != null) { if (sharedLink.expiresAt != null) {
if (isExpired()) { if (isExpired()) {
return Text( return Text(
"Expired", "shared_link_expired",
style: TextStyle(color: Colors.red[300]), style: TextStyle(color: Colors.red[300]),
); ).tr();
} }
final difference = sharedLink.expiresAt!.difference(DateTime.now()); final difference = sharedLink.expiresAt!.difference(DateTime.now());
debugPrint("Difference: $difference"); debugPrint("Difference: $difference");
@ -41,13 +42,15 @@ class SharedLinkItem extends ConsumerWidget {
if (difference.inHours % 24 > 12) { if (difference.inHours % 24 > 12) {
dayDifference += 1; dayDifference += 1;
} }
expiresText = "in $dayDifference days"; expiresText = "shared_link_expires_days".plural(dayDifference);
} else if (difference.inHours > 0) { } else if (difference.inHours > 0) {
expiresText = "in ${difference.inHours} hours"; expiresText = "shared_link_expires_hours".plural(difference.inHours);
} else if (difference.inMinutes > 0) { } else if (difference.inMinutes > 0) {
expiresText = "in ${difference.inMinutes} minutes"; expiresText =
"shared_link_expires_minutes".plural(difference.inMinutes);
} else if (difference.inSeconds > 0) { } else if (difference.inSeconds > 0) {
expiresText = "in ${difference.inSeconds} seconds"; expiresText =
"shared_link_expires_seconds".plural(difference.inSeconds);
} }
} }
return Text( return Text(
@ -72,7 +75,7 @@ class SharedLinkItem extends ConsumerWidget {
context: context, context: context,
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
toastType: ToastType.error, toastType: ToastType.error,
msg: 'Cannot fetch the server url', msg: "shared_link_error_server_url_fetch".tr(),
); );
return; return;
} }
@ -83,11 +86,14 @@ class SharedLinkItem extends ConsumerWidget {
), ),
).then((_) { ).then((_) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( SnackBar(
content: Text( content: Text(
"Copied to clipboard", "shared_link_clipboard_copied_massage",
style: context.textTheme.bodyLarge?.copyWith(
color: context.primaryColor,
), ),
duration: Duration(seconds: 2), ).tr(),
duration: const Duration(seconds: 2),
), ),
); );
}); });
@ -163,9 +169,12 @@ class SharedLinkItem extends ConsumerWidget {
Widget buildBottomInfo() { Widget buildBottomInfo() {
return Row( return Row(
children: [ children: [
if (sharedLink.allowUpload) buildInfoChip("Upload"), if (sharedLink.allowUpload)
if (sharedLink.allowDownload) buildInfoChip("Download"), buildInfoChip("shared_link_info_chip_upload".tr()),
if (sharedLink.showMetadata) buildInfoChip("EXIF"), if (sharedLink.allowDownload)
buildInfoChip("shared_link_info_chip_download".tr()),
if (sharedLink.showMetadata)
buildInfoChip("shared_link_info_chip_metadata".tr()),
], ],
); );
} }

View file

@ -275,7 +275,12 @@ class SharedLinkEditPage extends HookConsumerWidget {
).then((_) { ).then((_) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: const Text("shared_link_clipboard_copied_massage").tr(), content: Text(
"shared_link_clipboard_copied_massage",
style: context.textTheme.bodyLarge?.copyWith(
color: context.primaryColor,
),
).tr(),
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
), ),
); );

View file

@ -2,11 +2,11 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/shared_link/models/shared_link.dart'; import 'package:immich_mobile/modules/shared_link/models/shared_link.dart';
import 'package:immich_mobile/modules/shared_link/providers/shared_link.provider.dart'; import 'package:immich_mobile/modules/shared_link/providers/shared_link.provider.dart';
import 'package:immich_mobile/modules/shared_link/ui/shared_link_item.dart'; import 'package:immich_mobile/modules/shared_link/ui/shared_link_item.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
class SharedLinkPage extends HookConsumerWidget { class SharedLinkPage extends HookConsumerWidget {
const SharedLinkPage({Key? key}) : super(key: key); const SharedLinkPage({Key? key}) : super(key: key);
@ -18,7 +18,10 @@ class SharedLinkPage extends HookConsumerWidget {
useEffect( useEffect(
() { () {
ref.read(sharedLinksStateProvider.notifier).fetchLinks(); ref.read(sharedLinksStateProvider.notifier).fetchLinks();
return () => ref.invalidate(sharedLinksStateProvider); return () {
if (!context.mounted) return;
ref.invalidate(sharedLinksStateProvider);
};
}, },
[], [],
); );
@ -113,11 +116,10 @@ class SharedLinkPage extends HookConsumerWidget {
centerTitle: false, centerTitle: false,
), ),
body: SafeArea( body: SafeArea(
child: sharedLinks.when( child: sharedLinks.widgetWhen(
data: (links) => onError: (error, stackTrace) => buildNoShares(),
onData: (links) =>
links.isNotEmpty ? buildSharesList(links) : buildNoShares(), links.isNotEmpty ? buildSharesList(links) : buildNoShares(),
error: (error, stackTrace) => buildNoShares(),
loading: () => const Center(child: ImmichLoadingIndicator()),
), ),
), ),
); );

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart'; import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
@ -11,8 +12,8 @@ import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart'; import 'package:immich_mobile/shared/providers/server_info.provider.dart';
import 'package:immich_mobile/shared/ui/confirm_dialog.dart'; import 'package:immich_mobile/shared/ui/confirm_dialog.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
class TrashPage extends HookConsumerWidget { class TrashPage extends HookConsumerWidget {
const TrashPage({super.key}); const TrashPage({super.key});
@ -24,7 +25,7 @@ class TrashPage extends HookConsumerWidget {
ref.watch(serverInfoProvider.select((v) => v.serverConfig.trashDays)); ref.watch(serverInfoProvider.select((v) => v.serverConfig.trashDays));
final selectionEnabledHook = useState(false); final selectionEnabledHook = useState(false);
final selection = useState(<Asset>{}); final selection = useState(<Asset>{});
final processing = useState(false); final processing = useProcessingOverlay();
void selectionListener( void selectionListener(
bool multiselect, bool multiselect,
@ -229,18 +230,13 @@ class TrashPage extends HookConsumerWidget {
); );
} }
return trashedAssets.when( return Scaffold(
loading: () => Scaffold( appBar: trashedAssets.maybeWhen(
appBar: buildAppBar("?"), orElse: () => buildAppBar("?"),
body: const Center(child: CircularProgressIndicator()), data: (data) => buildAppBar(data.totalAssets.toString()),
), ),
error: (error, stackTrace) => Scaffold( body: trashedAssets.widgetWhen(
appBar: buildAppBar("!"), onData: (data) => data.isEmpty
body: Center(child: Text(error.toString())),
),
data: (data) => Scaffold(
appBar: buildAppBar(data.totalAssets.toString()),
body: data.isEmpty
? Center( ? Center(
child: Text('trash_page_no_assets'.tr()), child: Text('trash_page_no_assets'.tr()),
) )
@ -254,11 +250,9 @@ class TrashPage extends HookConsumerWidget {
showMultiSelectIndicator: false, showMultiSelectIndicator: false,
showStack: true, showStack: true,
topWidget: Padding( topWidget: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.symmetric(
top: 24, horizontal: 12,
bottom: 24, vertical: 24,
left: 12,
right: 12,
), ),
child: const Text( child: const Text(
"trash_page_info", "trash_page_info",
@ -267,8 +261,6 @@ class TrashPage extends HookConsumerWidget {
), ),
), ),
if (selectionEnabledHook.value) buildBottomBar(), if (selectionEnabledHook.value) buildBottomBar(),
if (processing.value)
const Center(child: ImmichLoadingIndicator()),
], ],
), ),
), ),

View file

@ -21,7 +21,7 @@ class ImmichLoadingIndicator extends StatelessWidget {
padding: const EdgeInsets.all(15), padding: const EdgeInsets.all(15),
child: const CircularProgressIndicator( child: const CircularProgressIndicator(
color: Colors.white, color: Colors.white,
strokeWidth: 2, strokeWidth: 3,
), ),
); );
} }

View file

@ -4,9 +4,9 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
// Error widget to be used in Scaffold when an AsyncError is received // Error widget to be used in Scaffold when an AsyncError is received
class ScaffoldErrorBody extends StatelessWidget { class ScaffoldErrorBody extends StatelessWidget {
final IconData icon; final bool withIcon;
const ScaffoldErrorBody({this.icon = Icons.error_outline, super.key}); const ScaffoldErrorBody({super.key, this.withIcon = true});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -14,19 +14,22 @@ class ScaffoldErrorBody extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Text( Text(
"scaffold_body_error_occured", "scaffold_body_error_occured",
style: style: context.textTheme.displayMedium,
TextStyle(fontSize: 14, fontWeight: FontWeight.bold, height: 3),
textAlign: TextAlign.center, textAlign: TextAlign.center,
).tr(), ).tr(),
if (withIcon)
Center( Center(
child: Padding(
padding: const EdgeInsets.only(top: 15),
child: Icon( child: Icon(
icon, Icons.error_outline,
size: 100, size: 100,
color: context.themeData.iconTheme.color?.withOpacity(0.5), color: context.themeData.iconTheme.color?.withOpacity(0.5),
), ),
), ),
),
], ],
); );
} }

View file

@ -39,7 +39,14 @@ class AppLogDetailPage extends HookConsumerWidget {
Clipboard.setData(ClipboardData(text: stackTrace)) Clipboard.setData(ClipboardData(text: stackTrace))
.then((_) { .then((_) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Copied to clipboard")), SnackBar(
content: Text(
"Copied to clipboard",
style: context.textTheme.bodyLarge?.copyWith(
color: context.primaryColor,
),
),
),
); );
}); });
}, },
@ -98,7 +105,14 @@ class AppLogDetailPage extends HookConsumerWidget {
onPressed: () { onPressed: () {
Clipboard.setData(ClipboardData(text: message)).then((_) { Clipboard.setData(ClipboardData(text: message)).then((_) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Copied to clipboard")), SnackBar(
content: Text(
"Copied to clipboard",
style: context.textTheme.bodyLarge?.copyWith(
color: context.primaryColor,
),
),
),
); );
}); });
}, },

View file

@ -1,41 +1,64 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
class ImmichLoadingOverlay extends StatelessWidget { final _loadingEntry = OverlayEntry(
const ImmichLoadingOverlay({ builder: (context) => SizedBox.square(
Key? key, dimension: double.infinity,
}) : super(key: key); child: DecoratedBox(
decoration:
BoxDecoration(color: context.colorScheme.surface.withAlpha(200)),
child: const Center(child: ImmichLoadingIndicator()),
),
),
);
ValueNotifier<bool> useProcessingOverlay() {
return use(const _LoadingOverlay());
}
class _LoadingOverlay extends Hook<ValueNotifier<bool>> {
const _LoadingOverlay();
@override @override
Widget build(BuildContext context) { _LoadingOverlayState createState() => _LoadingOverlayState();
return ValueListenableBuilder<bool>(
valueListenable:
ImmichLoadingOverlayController.appLoader.loaderShowingNotifier,
builder: (context, shouldShow, child) {
return shouldShow
? const Scaffold(
backgroundColor: Colors.black54,
body: Center(
child: ImmichLoadingIndicator(),
),
)
: const SizedBox();
},
);
}
} }
class ImmichLoadingOverlayController { class _LoadingOverlayState
static final ImmichLoadingOverlayController appLoader = extends HookState<ValueNotifier<bool>, _LoadingOverlay> {
ImmichLoadingOverlayController(); late final _isProcessing = ValueNotifier(false)..addListener(_listener);
ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false); OverlayEntry? overlayEntry;
ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message');
void show() { void _listener() {
loaderShowingNotifier.value = true; setState(() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_isProcessing.value) {
overlayEntry?.remove();
overlayEntry = _loadingEntry;
Overlay.of(context).insert(_loadingEntry);
} else {
overlayEntry?.remove();
overlayEntry = null;
}
});
});
} }
void hide() { @override
loaderShowingNotifier.value = false; ValueNotifier<bool> build(BuildContext context) {
return _isProcessing;
} }
@override
void dispose() {
_isProcessing.dispose();
super.dispose();
}
@override
Object? get debugValue => _isProcessing.value;
@override
String get debugLabel => 'useProcessingOverlay<>';
} }

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