Compare commits

..

10 Commits

Author SHA1 Message Date
mertalev
2c7e6071f5 split up search page 2025-03-05 19:06:04 -05:00
Alex
c110c9b00e chore(mobile): post release task (#16623) 2025-03-05 14:54:56 -06:00
Yaros
b241a80339 feat(mobile): Navigate back on memories (#16545)
* Navigate back on memories

* Fixes crash on navigating back
2025-03-05 14:42:43 -06:00
github-actions
31dd15ce8a chore: version v1.129.0 2025-03-05 19:47:50 +00:00
Alex
6108587c8b fix(web): show tags timeline (#16617)
* fix(web): show tags timeline

* fix(web): show tags timeline
2025-03-05 13:36:56 -06:00
Alex
3e50f668d9 feat(mobile): add catalan i18n (#16616)
* feat(mobile): Add Catalan

* refactor

* fix: load correct file

* chore: remove unused language files
2025-03-05 11:47:31 -06:00
Daniel Dietzler
9b82617e22 docs: 60k stars! (#16618)
60k stars! 
2025-03-05 11:40:45 -06:00
Alex
76cb32d8d0 chore(mobile): translations update (#16615)
chore(mobile): translation update
2025-03-05 16:33:41 +00:00
Yaros
e8f3348833 fix(mobile): Fixed zh-Hans not persisting (#16608)
Fixed zh-Hans not persisting
2025-03-05 09:56:00 -06:00
Zack Pollard
9922c8de59 fix: storage template failure after re-upload and previous fail (#16611)
fix: storage template breaks when files are re-uploaded after a move failure
2025-03-05 15:00:37 +00:00
68 changed files with 1664 additions and 1135 deletions

View File

@@ -49,23 +49,23 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn'] suffix: ["", "-cuda", "-openvino", "-armnn"]
steps: steps:
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Re-tag image - name: Re-tag image
run: | run: |
REGISTRY_NAME="ghcr.io" REGISTRY_NAME="ghcr.io"
REPOSITORY=${{ github.repository_owner }}/immich-machine-learning REPOSITORY=${{ github.repository_owner }}/immich-machine-learning
TAG_OLD=main${{ matrix.suffix }} TAG_OLD=main${{ matrix.suffix }}
TAG_PR=${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffix }} TAG_PR=${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffix }}
TAG_COMMIT=commit-${{ github.event_name != 'pull_request' && github.sha || github.event.pull_request.head.sha }}${{ matrix.suffix }} TAG_COMMIT=commit-${{ github.event_name != 'pull_request' && github.sha || github.event.pull_request.head.sha }}${{ matrix.suffix }}
docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_PR $REGISTRY_NAME/$REPOSITORY:$TAG_OLD docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_PR $REGISTRY_NAME/$REPOSITORY:$TAG_OLD
docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_COMMIT $REGISTRY_NAME/$REPOSITORY:$TAG_OLD docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_COMMIT $REGISTRY_NAME/$REPOSITORY:$TAG_OLD
retag_server: retag_server:
name: Re-Tag Server name: Re-Tag Server
@@ -74,7 +74,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
suffix: [''] suffix: [""]
steps: steps:
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
@@ -125,11 +125,6 @@ jobs:
device: openvino device: openvino
suffix: -openvino suffix: -openvino
- platforms: linux/amd64
runner: mich
device: rocm
suffix: -rocm
- platform: linux/arm64 - platform: linux/arm64
runner: ubuntu-24.04-arm runner: ubuntu-24.04-arm
device: armnn device: armnn
@@ -255,7 +250,7 @@ jobs:
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
env: env:
DOCKER_METADATA_PR_HEAD_SHA: 'true' DOCKER_METADATA_PR_HEAD_SHA: "true"
with: with:
flavor: | flavor: |
# Disable latest tag # Disable latest tag
@@ -408,7 +403,7 @@ jobs:
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
env: env:
DOCKER_METADATA_PR_HEAD_SHA: 'true' DOCKER_METADATA_PR_HEAD_SHA: "true"
with: with:
flavor: | flavor: |
# Disable latest tag # Disable latest tag

6
cli/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.52", "version": "2.2.53",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.52", "version": "2.2.53",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
@@ -55,7 +55,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.128.0", "version": "1.129.0",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.52", "version": "2.2.53",
"description": "Command Line Interface (CLI) for Immich", "description": "Command Line Interface (CLI) for Immich",
"type": "module", "type": "module",
"exports": "./dist/index.js", "exports": "./dist/index.js",
@@ -72,4 +72,4 @@
"volta": { "volta": {
"node": "22.14.0" "node": "22.14.0"
} }
} }

View File

@@ -95,12 +95,12 @@ services:
image: immich-machine-learning-dev:latest image: immich-machine-learning-dev:latest
# extends: # extends:
# file: hwaccel.ml.yml # file: hwaccel.ml.yml
# service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl] for accelerated inference # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference
build: build:
context: ../machine-learning context: ../machine-learning
dockerfile: Dockerfile dockerfile: Dockerfile
args: args:
- DEVICE=cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl] for accelerated inference - DEVICE=cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference
ports: ports:
- 3003:3003 - 3003:3003
volumes: volumes:

View File

@@ -38,12 +38,12 @@ services:
image: immich-machine-learning:latest image: immich-machine-learning:latest
# extends: # extends:
# file: hwaccel.ml.yml # file: hwaccel.ml.yml
# service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl] for accelerated inference # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference
build: build:
context: ../machine-learning context: ../machine-learning
dockerfile: Dockerfile dockerfile: Dockerfile
args: args:
- DEVICE=cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl] for accelerated inference - DEVICE=cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference
ports: ports:
- 3003:3003 - 3003:3003
volumes: volumes:

View File

@@ -33,12 +33,12 @@ services:
immich-machine-learning: immich-machine-learning:
container_name: immich_machine_learning container_name: immich_machine_learning
# For hardware acceleration, add one of -[armnn, cuda, rocm, openvino] to the image tag. # For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag.
# Example tag: ${IMMICH_VERSION:-release}-cuda # Example tag: ${IMMICH_VERSION:-release}-cuda
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
# extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration # extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration
# file: hwaccel.ml.yml # file: hwaccel.ml.yml
# service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable
volumes: volumes:
- model-cache:/cache - model-cache:/cache
env_file: env_file:

View File

@@ -26,13 +26,6 @@ services:
capabilities: capabilities:
- gpu - gpu
rocm:
group_add:
- video
devices:
- /dev/dri:/dev/dri
- /dev/kfd:/dev/kfd
openvino: openvino:
device_cgroup_rules: device_cgroup_rules:
- 'c 189:* rmw' - 'c 189:* rmw'

View File

@@ -11,7 +11,6 @@ You do not need to redo any machine learning jobs after enabling hardware accele
- ARM NN (Mali) - ARM NN (Mali)
- CUDA (NVIDIA GPUs with [compute capability](https://developer.nvidia.com/cuda-gpus) 5.2 or higher) - CUDA (NVIDIA GPUs with [compute capability](https://developer.nvidia.com/cuda-gpus) 5.2 or higher)
- ROCm (AMD GPUs)
- OpenVINO (Intel GPUs such as Iris Xe and Arc) - OpenVINO (Intel GPUs such as Iris Xe and Arc)
## Limitations ## Limitations
@@ -42,10 +41,6 @@ You do not need to redo any machine learning jobs after enabling hardware accele
- The installed driver must be >= 535 (it must support CUDA 12.2). - The installed driver must be >= 535 (it must support CUDA 12.2).
- On Linux (except for WSL2), you also need to have [NVIDIA Container Toolkit][nvct] installed. - On Linux (except for WSL2), you also need to have [NVIDIA Container Toolkit][nvct] installed.
#### ROCm
- The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=<a supported version, e.g. 10.3.0>`.
#### OpenVINO #### OpenVINO
- Integrated GPUs are more likely to experience issues than discrete GPUs, especially for older processors or servers with low RAM. - Integrated GPUs are more likely to experience issues than discrete GPUs, especially for older processors or servers with low RAM.
@@ -56,12 +51,12 @@ You do not need to redo any machine learning jobs after enabling hardware accele
1. If you do not already have it, download the latest [`hwaccel.ml.yml`][hw-file] file and ensure it's in the same folder as the `docker-compose.yml`. 1. If you do not already have it, download the latest [`hwaccel.ml.yml`][hw-file] file and ensure it's in the same folder as the `docker-compose.yml`.
2. In the `docker-compose.yml` under `immich-machine-learning`, uncomment the `extends` section and change `cpu` to the appropriate backend. 2. In the `docker-compose.yml` under `immich-machine-learning`, uncomment the `extends` section and change `cpu` to the appropriate backend.
3. Still in `immich-machine-learning`, add one of -[armnn, cuda, rocm, openvino] to the `image` section's tag at the end of the line. 3. Still in `immich-machine-learning`, add one of -[armnn, cuda, openvino] to the `image` section's tag at the end of the line.
4. Redeploy the `immich-machine-learning` container with these updated settings. 4. Redeploy the `immich-machine-learning` container with these updated settings.
### Confirming Device Usage ### Confirming Device Usage
You can confirm the device is being recognized and used by checking its utilization. There are many tools to display this, such as `nvtop` for NVIDIA or Intel, `intel_gpu_top` for Intel, and `radeontop` for AMD. You can confirm the device is being recognized and used by checking its utilization. There are many tools to display this, such as `nvtop` for NVIDIA or Intel and `intel_gpu_top` for Intel.
You can also check the logs of the `immich-machine-learning` container. When a Smart Search or Face Detection job begins, or when you search with text in Immich, you should either see a log for `Available ORT providers` containing the relevant provider (e.g. `CUDAExecutionProvider` in the case of CUDA), or a `Loaded ANN model` log entry without errors in the case of ARM NN. You can also check the logs of the `immich-machine-learning` container. When a Smart Search or Face Detection job begins, or when you search with text in Immich, you should either see a log for `Available ORT providers` containing the relevant provider (e.g. `CUDAExecutionProvider` in the case of CUDA), or a `Loaded ANN model` log entry without errors in the case of ARM NN.

View File

@@ -23,12 +23,12 @@ name: immich_remote_ml
services: services:
immich-machine-learning: immich-machine-learning:
container_name: immich_machine_learning container_name: immich_machine_learning
# For hardware acceleration, add one of -[armnn, cuda, rocm, openvino] to the image tag. # For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag.
# Example tag: ${IMMICH_VERSION:-release}-cuda # Example tag: ${IMMICH_VERSION:-release}-cuda
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
# extends: # extends:
# file: hwaccel.ml.yml # file: hwaccel.ml.yml
# service: # set to one of [armnn, cuda, rocm, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable # service: # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable
volumes: volumes:
- model-cache:/cache - model-cache:/cache
restart: always restart: always

View File

@@ -242,6 +242,13 @@ const roadmap: Item[] = [
]; ];
const milestones: Item[] = [ const milestones: Item[] = [
{
icon: mdiStar,
iconColor: 'gold',
title: '60,000 Stars',
description: 'Reached 60K Stars on GitHub!',
getDateLabel: withLanguage(new Date(2025, 2, 4)),
},
withRelease({ withRelease({
icon: mdiLinkEdit, icon: mdiLinkEdit,
iconColor: 'crimson', iconColor: 'crimson',

View File

@@ -1,4 +1,8 @@
[ [
{
"label": "v1.129.0",
"url": "https://v1.129.0.archive.immich.app"
},
{ {
"label": "v1.128.0", "label": "v1.128.0",
"url": "https://v1.128.0.archive.immich.app" "url": "https://v1.128.0.archive.immich.app"

13
e2e/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.128.0", "version": "1.129.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.128.0", "version": "1.129.0",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
@@ -45,13 +45,15 @@
}, },
"../cli": { "../cli": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.52", "version": "2.2.53",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"chokidar": "^4.0.3",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"fastq": "^1.17.1", "fastq": "^1.17.1",
"lodash-es": "^4.17.21" "lodash-es": "^4.17.21",
"micromatch": "^4.0.8"
}, },
"bin": { "bin": {
"immich": "dist/index.js" "immich": "dist/index.js"
@@ -63,6 +65,7 @@
"@types/byte-size": "^8.1.0", "@types/byte-size": "^8.1.0",
"@types/cli-progress": "^3.11.0", "@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/micromatch": "^4.0.9",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/node": "^22.13.5", "@types/node": "^22.13.5",
"@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/eslint-plugin": "^8.15.0",
@@ -92,7 +95,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.128.0", "version": "1.129.0",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.128.0", "version": "1.129.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",

View File

@@ -66,8 +66,8 @@ download:
locale_code: es-MX locale_code: es-MX
- file: mobile/assets/i18n/sv-FI.json - file: mobile/assets/i18n/sv-FI.json
locale_code: sv-FI locale_code: sv-FI
- file: mobile/assets/i18n/ca-CA.json - file: mobile/assets/i18n/ca.json
locale_code: ca-CA locale_code: ca
- file: mobile/assets/i18n/hu-HU.json - file: mobile/assets/i18n/hu-HU.json
locale_code: hu-HU locale_code: hu-HU
- file: mobile/assets/i18n/lv-LV.json - file: mobile/assets/i18n/lv-LV.json

View File

@@ -15,34 +15,6 @@ RUN mkdir /opt/armnn && \
cd /opt/ann && \ cd /opt/ann && \
sh build.sh sh build.sh
# Warning: 25GiB+ disk space required to pull this image
# TODO: find a way to reduce the image size
FROM rocm/dev-ubuntu-22.04:6.3.1-complete AS builder-rocm
WORKDIR /code
RUN apt-get update && apt-get install -y --no-install-recommends wget git python3.10-venv
RUN wget -nv https://github.com/Kitware/CMake/releases/download/v3.30.1/cmake-3.30.1-linux-x86_64.sh && \
chmod +x cmake-3.30.1-linux-x86_64.sh && \
mkdir -p /code/cmake-3.30.1-linux-x86_64 && \
./cmake-3.30.1-linux-x86_64.sh --skip-license --prefix=/code/cmake-3.30.1-linux-x86_64 && \
rm cmake-3.30.1-linux-x86_64.sh
ENV PATH=/code/cmake-3.30.1-linux-x86_64/bin:${PATH}
RUN git clone --single-branch --branch v1.20.1 --recursive "https://github.com/Microsoft/onnxruntime" onnxruntime
WORKDIR /code/onnxruntime
# Fix for multi-threading based on comments in https://github.com/microsoft/onnxruntime/pull/19567
# TODO: find a way to fix this without disabling algo caching
COPY ./rocm-PR19567.patch /tmp/
RUN git apply /tmp/rocm-PR19567.patch
RUN /bin/sh ./dockerfiles/scripts/install_common_deps.sh
# Note: the `parallel` setting uses a substantial amount of RAM
RUN ./build.sh --allow_running_as_root --config Release --build_wheel --update --build --parallel 13 --cmake_extra_defines\
ONNXRUNTIME_VERSION=1.20.1 --use_rocm --rocm_home=/opt/rocm
RUN mv /code/onnxruntime/build/Linux/Release/dist/*.whl /opt/
FROM builder-${DEVICE} AS builder FROM builder-${DEVICE} AS builder
ARG DEVICE ARG DEVICE
@@ -60,9 +32,6 @@ RUN poetry config installer.max-workers 10 && \
RUN python3 -m venv /opt/venv RUN python3 -m venv /opt/venv
COPY poetry.lock pyproject.toml ./ COPY poetry.lock pyproject.toml ./
RUN if [ "$DEVICE" = "rocm" ]; then \
poetry add /opt/onnxruntime_rocm-*.whl; \
fi
RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev
FROM python:3.11-slim-bookworm@sha256:614c8691ab74150465ec9123378cd4dde7a6e57be9e558c3108df40664667a4c AS prod-cpu FROM python:3.11-slim-bookworm@sha256:614c8691ab74150465ec9123378cd4dde7a6e57be9e558c3108df40664667a4c AS prod-cpu
@@ -71,10 +40,10 @@ FROM prod-cpu AS prod-openvino
RUN apt-get update && \ RUN apt-get update && \
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \ apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17384.11/intel-igc-core_1.0.17384.11_amd64.deb && \ wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17384.11/intel-igc-core_1.0.17384.11_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17384.11/intel-igc-opencl_1.0.17384.11_amd64.deb && \ wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17384.11/intel-igc-opencl_1.0.17384.11_amd64.deb && \
wget -nv https://github.com/intel/compute-runtime/releases/download/24.31.30508.7/intel-opencl-icd_24.31.30508.7_amd64.deb && \ wget https://github.com/intel/compute-runtime/releases/download/24.31.30508.7/intel-opencl-icd_24.31.30508.7_amd64.deb && \
wget -nv https://github.com/intel/compute-runtime/releases/download/24.31.30508.7/libigdgmm12_22.4.1_amd64.deb && \ wget https://github.com/intel/compute-runtime/releases/download/24.31.30508.7/libigdgmm12_22.4.1_amd64.deb && \
dpkg -i *.deb && \ dpkg -i *.deb && \
rm *.deb && \ rm *.deb && \
apt-get remove wget -yqq && \ apt-get remove wget -yqq && \
@@ -111,15 +80,11 @@ COPY --from=builder-armnn \
/opt/ann/build.sh \ /opt/ann/build.sh \
/opt/armnn/ /opt/armnn/
FROM rocm/dev-ubuntu-22.04:6.3.1-complete AS prod-rocm
FROM prod-${DEVICE} AS prod FROM prod-${DEVICE} AS prod
ARG DEVICE ARG DEVICE
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends tini $(if ! [ "$DEVICE" = "openvino" ] && ! [ "$DEVICE" = "rocm" ]; then echo "libmimalloc2.0"; fi) && \ apt-get install -y --no-install-recommends tini $(if ! [ "$DEVICE" = "openvino" ]; then echo "libmimalloc2.0"; fi) && \
apt-get autoremove -yqq && \ apt-get autoremove -yqq && \
apt-get clean && \ apt-get clean && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*

View File

@@ -7,7 +7,7 @@
This project uses [Poetry](https://python-poetry.org/docs/#installation), so be sure to install it first. This project uses [Poetry](https://python-poetry.org/docs/#installation), so be sure to install it first.
Running `poetry install --no-root --with dev --with cpu` will install everything you need in an isolated virtual environment. Running `poetry install --no-root --with dev --with cpu` will install everything you need in an isolated virtual environment.
CUDA, ROCM and OpenVINO are supported as acceleration APIs. To use them, you can replace `--with cpu` with either of `--with cuda`, `--with rocm` or `--with openvino`. In the case of CUDA, a [compute capability](https://developer.nvidia.com/cuda-gpus) of 5.2 or higher is required. CUDA and OpenVINO are supported as acceleration APIs. To use them, you can replace `--with cpu` with either of `--with cuda` or `--with openvino`. In the case of CUDA, a [compute capability](https://developer.nvidia.com/cuda-gpus) of 5.2 or higher is required.
To add or remove dependencies, you can use the commands `poetry add $PACKAGE_NAME` and `poetry remove $PACKAGE_NAME`, respectively. To add or remove dependencies, you can use the commands `poetry add $PACKAGE_NAME` and `poetry remove $PACKAGE_NAME`, respectively.
Be sure to commit the `poetry.lock` and `pyproject.toml` files with `poetry lock --no-update` to reflect any changes in dependencies. Be sure to commit the `poetry.lock` and `pyproject.toml` files with `poetry lock --no-update` to reflect any changes in dependencies.
@@ -37,4 +37,4 @@ This project utilizes facial recognition models from the [InsightFace](https://g
## License and Use Restrictions ## License and Use Restrictions
We have received permission to use the InsightFace facial recognition models in our project, as granted via email by Jia Guo (guojia@insightface.ai) on 18th March 2023. However, it's important to note that this permission does not extend to the redistribution or commercial use of their models by third parties. Users and developers interested in using these models should review the licensing terms provided in the InsightFace GitHub repository. We have received permission to use the InsightFace facial recognition models in our project, as granted via email by Jia Guo (guojia@insightface.ai) on 18th March 2023. However, it's important to note that this permission does not extend to the redistribution or commercial use of their models by third parties. Users and developers interested in using these models should review the licensing terms provided in the InsightFace GitHub repository.
For more information on the capabilities of the InsightFace models and to ensure compliance with their license, please refer to their [official repository](https://github.com/deepinsight/insightface). Adhering to the specified licensing terms is crucial for the respectful and lawful use of their work. For more information on the capabilities of the InsightFace models and to ensure compliance with their license, please refer to their [official repository](https://github.com/deepinsight/insightface). Adhering to the specified licensing terms is crucial for the respectful and lawful use of their work.

View File

@@ -63,12 +63,7 @@ _INSIGHTFACE_MODELS = {
} }
SUPPORTED_PROVIDERS = [ SUPPORTED_PROVIDERS = ["CUDAExecutionProvider", "OpenVINOExecutionProvider", "CPUExecutionProvider"]
"CUDAExecutionProvider",
"ROCMExecutionProvider",
"OpenVINOExecutionProvider",
"CPUExecutionProvider",
]
def get_model_source(model_name: str) -> ModelSource | None: def get_model_source(model_name: str) -> ModelSource | None:

View File

@@ -88,7 +88,7 @@ class OrtSession:
match provider: match provider:
case "CPUExecutionProvider": case "CPUExecutionProvider":
options = {"arena_extend_strategy": "kSameAsRequested"} options = {"arena_extend_strategy": "kSameAsRequested"}
case "CUDAExecutionProvider" | "ROCMExecutionProvider": case "CUDAExecutionProvider":
options = {"arena_extend_strategy": "kSameAsRequested", "device_id": settings.device_id} options = {"arena_extend_strategy": "kSameAsRequested", "device_id": settings.device_id}
case "OpenVINOExecutionProvider": case "OpenVINOExecutionProvider":
options = { options = {

View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. # This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
[[package]] [[package]]
name = "aiocache" name = "aiocache"
@@ -147,6 +147,10 @@ files = [
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec"},
{file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"},
{file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"},
{file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"},
@@ -159,8 +163,14 @@ files = [
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b"},
{file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"},
{file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"},
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28"},
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f"},
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"},
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"},
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"},
@@ -171,8 +181,24 @@ files = [
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839"},
{file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"},
{file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"},
{file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5"},
{file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8"},
{file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f"},
{file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648"},
{file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0"},
{file = "Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089"},
{file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368"},
{file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c"},
{file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284"},
{file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7"},
{file = "Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0"},
{file = "Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b"},
{file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"},
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"},
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"},
@@ -182,6 +208,10 @@ files = [
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52"},
{file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"},
{file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"},
{file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"},
@@ -193,6 +223,10 @@ files = [
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c"},
{file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"},
{file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"},
{file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"},
@@ -205,6 +239,10 @@ files = [
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a"},
{file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"},
{file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"},
{file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"},
@@ -217,6 +255,10 @@ files = [
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb"},
{file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"},
{file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"},
{file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"},
@@ -3693,4 +3735,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.10,<4.0" python-versions = ">=3.10,<4.0"
content-hash = "271a6c2a76b1b6286e02b91489ffd0c42e92daf151ae932514f5416c7869f71d" content-hash = "b690d5fbd141da3947f4f1dc029aba1b95e7faafd723166f2c4bdc47a66c095e"

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "machine-learning" name = "machine-learning"
version = "1.128.0" version = "1.129.0"
description = "" description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"] authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md" readme = "README.md"
@@ -47,11 +47,6 @@ optional = true
[tool.poetry.group.cuda.dependencies] [tool.poetry.group.cuda.dependencies]
onnxruntime-gpu = {version = "^1.17.0", source = "cuda12"} onnxruntime-gpu = {version = "^1.17.0", source = "cuda12"}
[tool.poetry.group.rocm]
optional = true
[tool.poetry.group.rocm.dependencies]
[tool.poetry.group.openvino] [tool.poetry.group.openvino]
optional = true optional = true

View File

@@ -1,176 +0,0 @@
From a598a88db258f82a6e4bca75810921bd6bcee7e0 Mon Sep 17 00:00:00 2001
From: David Nieto <dmnieto@gmail.com>
Date: Sat, 17 Feb 2024 11:23:12 -0800
Subject: [PATCH] Disable algo caching in ROCM EP
Similar to the work done by Liangxijun-1001 in
https://github.com/apache/tvm/pull/16178 the ROCM spec mandates calling
miopenFindConvolution*Algorithm() before using any Convolution API
This is the link to the porting guide describing this requirement
https://rocmdocs.amd.com/projects/MIOpen/en/latest/MIOpen_Porting_Guide.html
Thus, this change disables the algo cache and enforces the official
API semantics
Signed-off-by: David Nieto <dmnieto@gmail.com>
---
onnxruntime/core/providers/rocm/nn/conv.cc | 61 +++++++++----------
onnxruntime/core/providers/rocm/nn/conv.h | 6 --
.../core/providers/rocm/nn/conv_transpose.cc | 17 +++---
3 files changed, 36 insertions(+), 48 deletions(-)
diff --git a/onnxruntime/core/providers/rocm/nn/conv.cc b/onnxruntime/core/providers/rocm/nn/conv.cc
index 6214ec7bc0ea..b08aceca48b1 100644
--- a/onnxruntime/core/providers/rocm/nn/conv.cc
+++ b/onnxruntime/core/providers/rocm/nn/conv.cc
@@ -125,10 +125,8 @@ Status Conv<T, NHWC>::UpdateState(OpKernelContext* context, bool bias_expected)
if (input_dims_changed)
s_.last_x_dims = gsl::make_span(x_dims);
- if (w_dims_changed) {
+ if (w_dims_changed)
s_.last_w_dims = gsl::make_span(w_dims);
- s_.cached_benchmark_fwd_results.clear();
- }
ORT_RETURN_IF_ERROR(conv_attrs_.ValidateInputShape(X->Shape(), W->Shape(), channels_last, channels_last));
@@ -277,35 +275,6 @@ Status Conv<T, NHWC>::UpdateState(OpKernelContext* context, bool bias_expected)
HIP_CALL_THROW(hipMalloc(&s_.b_zero, malloc_size));
HIP_CALL_THROW(hipMemsetAsync(s_.b_zero, 0, malloc_size, Stream(context)));
}
-
- if (!s_.cached_benchmark_fwd_results.contains(x_dims_miopen)) {
- miopenConvAlgoPerf_t perf;
- int algo_count = 1;
- const ROCMExecutionProvider* rocm_ep = static_cast<const ROCMExecutionProvider*>(this->Info().GetExecutionProvider());
- static constexpr int num_algos = MIOPEN_CONVOLUTION_FWD_ALGO_COUNT;
- size_t max_ws_size = rocm_ep->GetMiopenConvUseMaxWorkspace() ? GetMaxWorkspaceSize(GetMiopenHandle(context), s_, kAllAlgos, num_algos)
- : AlgoSearchWorkspaceSize;
- IAllocatorUniquePtr<void> algo_search_workspace = GetTransientScratchBuffer<void>(max_ws_size);
- MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionForwardAlgorithm(
- GetMiopenHandle(context),
- s_.x_tensor,
- s_.x_data,
- s_.w_desc,
- s_.w_data,
- s_.conv_desc,
- s_.y_tensor,
- s_.y_data,
- 1, // requestedAlgoCount
- &algo_count, // returnedAlgoCount
- &perf,
- algo_search_workspace.get(),
- max_ws_size,
- false)); // Do not do exhaustive algo search.
- s_.cached_benchmark_fwd_results.insert(x_dims_miopen, {perf.fwd_algo, perf.memory});
- }
- const auto& perf = s_.cached_benchmark_fwd_results.at(x_dims_miopen);
- s_.fwd_algo = perf.fwd_algo;
- s_.workspace_bytes = perf.memory;
} else {
// set Y
s_.Y = context->Output(0, TensorShape(s_.y_dims));
@@ -319,6 +288,34 @@ Status Conv<T, NHWC>::UpdateState(OpKernelContext* context, bool bias_expected)
s_.y_data = reinterpret_cast<HipT*>(s_.Y->MutableData<T>());
}
}
+ {
+ /* FindConvolution must always be called by the runtime */
+ TensorShapeVector x_dims_miopen{x_dims.begin(), x_dims.end()};
+ miopenConvAlgoPerf_t perf;
+ int algo_count = 1;
+ const ROCMExecutionProvider* rocm_ep = static_cast<const ROCMExecutionProvider*>(this->Info().GetExecutionProvider());
+ static constexpr int num_algos = MIOPEN_CONVOLUTION_FWD_ALGO_COUNT;
+ size_t max_ws_size = rocm_ep->GetMiopenConvUseMaxWorkspace() ? GetMaxWorkspaceSize(GetMiopenHandle(context), s_, kAllAlgos, num_algos)
+ : AlgoSearchWorkspaceSize;
+ IAllocatorUniquePtr<void> algo_search_workspace = GetTransientScratchBuffer<void>(max_ws_size);
+ MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionForwardAlgorithm(
+ GetMiopenHandle(context),
+ s_.x_tensor,
+ s_.x_data,
+ s_.w_desc,
+ s_.w_data,
+ s_.conv_desc,
+ s_.y_tensor,
+ s_.y_data,
+ 1, // requestedAlgoCount
+ &algo_count, // returnedAlgoCount
+ &perf,
+ algo_search_workspace.get(),
+ max_ws_size,
+ false)); // Do not do exhaustive algo search.
+ s_.fwd_algo = perf.fwd_algo;
+ s_.workspace_bytes = perf.memory;
+ }
return Status::OK();
}
diff --git a/onnxruntime/core/providers/rocm/nn/conv.h b/onnxruntime/core/providers/rocm/nn/conv.h
index bc9846203e57..d54218f25854 100644
--- a/onnxruntime/core/providers/rocm/nn/conv.h
+++ b/onnxruntime/core/providers/rocm/nn/conv.h
@@ -108,9 +108,6 @@ class lru_unordered_map {
list_type lru_list_;
};
-// cached miopen descriptors
-constexpr size_t MAX_CACHED_ALGO_PERF_RESULTS = 10000;
-
template <typename AlgoPerfType>
struct MiopenConvState {
// if x/w dims changed, update algo and miopenTensors
@@ -148,9 +145,6 @@ struct MiopenConvState {
decltype(AlgoPerfType().memory) memory;
};
- lru_unordered_map<TensorShapeVector, PerfFwdResultParams, vector_hash> cached_benchmark_fwd_results{MAX_CACHED_ALGO_PERF_RESULTS};
- lru_unordered_map<TensorShapeVector, PerfBwdResultParams, vector_hash> cached_benchmark_bwd_results{MAX_CACHED_ALGO_PERF_RESULTS};
-
// Some properties needed to support asymmetric padded Conv nodes
bool post_slicing_required;
TensorShapeVector slice_starts;
diff --git a/onnxruntime/core/providers/rocm/nn/conv_transpose.cc b/onnxruntime/core/providers/rocm/nn/conv_transpose.cc
index 7447113fdf84..45ed4c8ac37a 100644
--- a/onnxruntime/core/providers/rocm/nn/conv_transpose.cc
+++ b/onnxruntime/core/providers/rocm/nn/conv_transpose.cc
@@ -76,7 +76,6 @@ Status ConvTranspose<T, NHWC>::DoConvTranspose(OpKernelContext* context, bool dy
if (w_dims_changed) {
s_.last_w_dims = gsl::make_span(w_dims);
- s_.cached_benchmark_bwd_results.clear();
}
ConvTransposeAttributes::Prepare p;
@@ -127,12 +126,13 @@ Status ConvTranspose<T, NHWC>::DoConvTranspose(OpKernelContext* context, bool dy
y_data = reinterpret_cast<HipT*>(p.Y->MutableData<T>());
- if (!s_.cached_benchmark_bwd_results.contains(x_dims)) {
- IAllocatorUniquePtr<void> algo_search_workspace = GetScratchBuffer<void>(AlgoSearchWorkspaceSize, context->GetComputeStream());
-
- miopenConvAlgoPerf_t perf;
- int algo_count = 1;
- MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionBackwardDataAlgorithm(
+ }
+ // The following is required before calling convolution, we cannot cache the results
+ {
+ IAllocatorUniquePtr<void> algo_search_workspace = GetScratchBuffer<void>(AlgoSearchWorkspaceSize, context->GetComputeStream());
+ miopenConvAlgoPerf_t perf;
+ int algo_count = 1;
+ MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionBackwardDataAlgorithm(
GetMiopenHandle(context),
s_.x_tensor,
x_data,
@@ -147,10 +147,7 @@ Status ConvTranspose<T, NHWC>::DoConvTranspose(OpKernelContext* context, bool dy
algo_search_workspace.get(),
AlgoSearchWorkspaceSize,
false));
- s_.cached_benchmark_bwd_results.insert(x_dims, {perf.bwd_data_algo, perf.memory});
- }
- const auto& perf = s_.cached_benchmark_bwd_results.at(x_dims);
s_.bwd_data_algo = perf.bwd_data_algo;
s_.workspace_bytes = perf.memory;
}

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" => 186, "android.injected.version.code" => 187,
"android.injected.version.name" => "1.128.0", "android.injected.version.name" => "1.129.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

@@ -1,24 +1,35 @@
{ {
"action_common_cancel": "Cancel·la", "action_common_back": "Enrere",
"action_common_update": "Actualitza", "action_common_cancel": "Cancel·lar",
"add_to_album_bottom_sheet_added": "S'ha afegit a {album}", "action_common_clear": "Buida",
"add_to_album_bottom_sheet_already_exists": "Ja es troba en {album}", "action_common_confirm": "Confirmar",
"action_common_save": "Desa",
"action_common_select": "Selecciona",
"action_common_update": "Actualitzar",
"add_a_name": "Afegeix un nom",
"add_endpoint": "afegir endpoint",
"add_to_album_bottom_sheet_added": "Added to {album}",
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
"advanced_settings_log_level_title": "Log level: {}", "advanced_settings_log_level_title": "Log level: {}",
"advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.", "advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.",
"advanced_settings_prefer_remote_title": "Prefereix imatges remotes", "advanced_settings_prefer_remote_title": "Prefereix imatges remotes",
"advanced_settings_proxy_headers_subtitle": "Definiu les capçaleres de proxy que Immich per enviar amb cada sol·licitud de xarxa",
"advanced_settings_proxy_headers_title": "Capçaleres de proxy",
"advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.",
"advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates", "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates",
"advanced_settings_tile_subtitle": "Configuració avançada", "advanced_settings_tile_subtitle": "Advanced user's settings",
"advanced_settings_tile_title": "Avançat", "advanced_settings_tile_title": "Avançat",
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting", "advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
"advanced_settings_troubleshooting_title": "Resolució de problemes", "advanced_settings_troubleshooting_title": "Resolució de problemes",
"album_info_card_backup_album_excluded": "Exclosos", "album_info_card_backup_album_excluded": "Exclosos",
"album_info_card_backup_album_included": "Inclosos", "album_info_card_backup_album_included": "Inclosos",
"album_thumbnail_card_item": "1 element", "albums": "Àlbums",
"album_thumbnail_card_items": "{} elements", "album_thumbnail_card_item": "1 item",
"album_thumbnail_card_shared": " · Compartit", "album_thumbnail_card_items": "{} items",
"album_thumbnail_card_shared": " · Shared",
"album_thumbnail_owned": "Owned", "album_thumbnail_owned": "Owned",
"album_thumbnail_shared_by": "Compartit per {}", "album_thumbnail_shared_by": "Compartit per {}",
"album_viewer_appbar_delete_confirm": "Confirmes que vols suprimir aquest àlbum del teu compte?",
"album_viewer_appbar_share_delete": "Esborra l'àlbum", "album_viewer_appbar_share_delete": "Esborra l'àlbum",
"album_viewer_appbar_share_err_delete": "Error al esborrar l'àlbum", "album_viewer_appbar_share_err_delete": "Error al esborrar l'àlbum",
"album_viewer_appbar_share_err_leave": "Error al sortir de l'àlbum", "album_viewer_appbar_share_err_leave": "Error al sortir de l'àlbum",
@@ -28,25 +39,39 @@
"album_viewer_appbar_share_remove": "Treu de l'àlbum", "album_viewer_appbar_share_remove": "Treu de l'àlbum",
"album_viewer_appbar_share_to": "Share To", "album_viewer_appbar_share_to": "Share To",
"album_viewer_page_share_add_users": "Afegeix usuaris", "album_viewer_page_share_add_users": "Afegeix usuaris",
"all": "Tot",
"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 want to 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",
"archived": "Arxivat",
"archive_page_no_archived_assets": "No s'ha trobat res arxivat", "archive_page_no_archived_assets": "No s'ha trobat res arxivat",
"archive_page_title": "Arxiu({})", "archive_page_title": "Arxiu({})",
"asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping", "asset_action_delete_err_read_only": "No es poden esborrar el fitxer(s) de només lectura, ometent",
"asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping", "asset_action_share_err_offline": "No s'ha pogut obtenir el fitxer(s) sense connexió, ometent",
"asset_list_group_by_sub_title": "Group by", "asset_list_group_by_sub_title": "Agrupar per",
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
"asset_list_layout_settings_group_automatically": "Automàtic", "asset_list_layout_settings_group_automatically": "Automàtic",
"asset_list_layout_settings_group_by": "Group assets by", "asset_list_layout_settings_group_by": "Group assets by",
"asset_list_layout_settings_group_by_month": "Month", "asset_list_layout_settings_group_by_month": "Month",
"asset_list_layout_settings_group_by_month_day": "Month + day", "asset_list_layout_settings_group_by_month_day": "Month + day",
"asset_list_layout_sub_title": "Layout", "asset_list_layout_sub_title": "Disseny",
"asset_list_settings_subtitle": "Photo grid layout settings", "asset_list_settings_subtitle": "Photo grid layout settings",
"asset_list_settings_title": "Photo Grid", "asset_list_settings_title": "Photo Grid",
"asset_viewer_settings_title": "Asset Viewer", "asset_restored_successfully": "Element recuperat correctament",
"assets_deleted_permanently": "{} element(s) esborrats permanentment",
"assets_deleted_permanently_from_server": "{} element(s) esborrats permanentment del servidor d'Immich",
"assets_removed_permanently_from_device": "{} element(s) esborrat permanentment del dispositiu",
"assets_restored_successfully": "{} element(s) recuperats correctament",
"assets_trashed": "{} element(s) enviat a la paperera",
"assets_trashed_from_server": "{} element(s) enviat a la paperera del servidor d'Immich",
"asset_viewer_settings_subtitle": "Gestiona la configuració del visualitzador de la galeria",
"asset_viewer_settings_title": "Visor d'arxius",
"automatic_endpoint_switching_subtitle": "Connecteu-vos localment a través de la Wi-Fi designada quan estigui disponible i utilitzeu connexions alternatives en altres llocs",
"automatic_endpoint_switching_title": "Canvi automàtic d'URL",
"background_location_permission": "Permís d'ubicació en segon pla",
"background_location_permission_content": "Per canviar de xarxa quan s'executa en segon pla, Immich ha de *sempre* tenir accés a la ubicació precisa perquè l'aplicació pugui llegir el nom de la xarxa Wi-Fi",
"backup_album_selection_page_albums_device": "Àlbums al dispositiu ({})", "backup_album_selection_page_albums_device": "Àlbums al dispositiu ({})",
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude", "backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.", "backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
@@ -108,9 +133,11 @@
"backup_info_card_assets": "elements", "backup_info_card_assets": "elements",
"backup_manual_cancelled": "Cancelled", "backup_manual_cancelled": "Cancelled",
"backup_manual_failed": "Failed", "backup_manual_failed": "Failed",
"backup_manual_in_progress": "Upload already in progress. Try after some time", "backup_manual_in_progress": "Upload already in progress. Try after sometime",
"backup_manual_success": "Success", "backup_manual_success": "Success",
"backup_manual_title": "Upload status", "backup_manual_title": "Upload status",
"backup_options_page_title": "Opcions de còpia de seguretat",
"backup_setting_subtitle": "Gestiona la configuració de càrrega en segon pla i en primer pla",
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)", "cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
"cache_settings_clear_cache_button": "Clear cache", "cache_settings_clear_cache_button": "Clear cache",
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.", "cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
@@ -129,73 +156,136 @@
"cache_settings_tile_subtitle": "Control the local storage behaviour", "cache_settings_tile_subtitle": "Control the local storage behaviour",
"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",
"cancel": "Cancel·la",
"canceled": "Cancel·lat",
"change_display_order": "Canvia l'ordre de visualització",
"change_password_form_confirm_password": "Confirma la contrasenya", "change_password_form_confirm_password": "Confirma la contrasenya",
"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_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",
"check_corrupt_asset_backup": "Comprovar les còpies de seguretat corruptes",
"check_corrupt_asset_backup_button": "Realitzar comprovació",
"check_corrupt_asset_backup_description": "Executeu aquesta comprovació només mitjançant Wi-Fi i un cop s'hagi fet una còpia de seguretat de tots els actius. El procediment pot trigar uns minuts.",
"client_cert_dialog_msg_confirm": "OK",
"client_cert_enter_password": "Introdueix la contrasenya",
"client_cert_import": "Importar",
"client_cert_import_success_msg": "S'ha importat el certificat del client",
"client_cert_invalid_msg": "Fitxer de certificat no vàlid o contrasenya incorrecta",
"client_cert_remove": "Eliminar",
"client_cert_remove_msg": "S'ha eliminat el certificat del client",
"client_cert_subtitle": "Només admet el format PKCS12 (.p12, .pfx). La importació/eliminació de certificats només està disponible abans d'iniciar sessió",
"client_cert_title": "Certificat de client SSL",
"common_add_to_album": "Add to album", "common_add_to_album": "Add to album",
"common_change_password": "Change Password", "common_change_password": "Change Password",
"common_create_new_album": "Crea un àlbum nou", "common_create_new_album": "Crea un àlbum nou",
"common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.", "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
"common_shared": "Compartit", "common_shared": "Compartit",
"completed": "Completat",
"contextual_search": "Sortida del sol a la platja",
"control_bottom_app_bar_add_to_album": "Add to album", "control_bottom_app_bar_add_to_album": "Add to album",
"control_bottom_app_bar_album_info": "{} elements", "control_bottom_app_bar_album_info": "{} elements",
"control_bottom_app_bar_album_info_shared": "{} elements - Compartits", "control_bottom_app_bar_album_info_shared": "{} elements - Compartits",
"control_bottom_app_bar_archive": "Arxiu", "control_bottom_app_bar_archive": "Arxiu",
"control_bottom_app_bar_create_new_album": "Crea un àlbum nou", "control_bottom_app_bar_create_new_album": "Crea un àlbum nou",
"control_bottom_app_bar_delete": "Esborra", "control_bottom_app_bar_delete": "Esborra",
"control_bottom_app_bar_delete_from_immich": "Delete from Immich", "control_bottom_app_bar_delete_from_immich": "Suprimeix del Immich",
"control_bottom_app_bar_delete_from_local": "Delete from device", "control_bottom_app_bar_delete_from_local": "Suprimeix del dispositiu",
"control_bottom_app_bar_download": "Descarrega",
"control_bottom_app_bar_edit": "Edita",
"control_bottom_app_bar_edit_location": "Edit Location", "control_bottom_app_bar_edit_location": "Edit Location",
"control_bottom_app_bar_edit_time": "Edit Date & Time", "control_bottom_app_bar_edit_time": "Edit Date & Time",
"control_bottom_app_bar_favorite": "Preferit", "control_bottom_app_bar_favorite": "Preferit",
"control_bottom_app_bar_share": "Share", "control_bottom_app_bar_share": "Share",
"control_bottom_app_bar_share_to": "Share To", "control_bottom_app_bar_share_to": "Share To",
"control_bottom_app_bar_stack": "Stack", "control_bottom_app_bar_stack": "Stack",
"control_bottom_app_bar_trash_from_immich": "Move to Trash", "control_bottom_app_bar_trash_from_immich": "Mou a paperera",
"control_bottom_app_bar_unarchive": "Desarxiva", "control_bottom_app_bar_unarchive": "Desarxiva",
"control_bottom_app_bar_unfavorite": "Unfavorite", "control_bottom_app_bar_unfavorite": "Unfavorite",
"control_bottom_app_bar_upload": "Upload", "control_bottom_app_bar_upload": "Upload",
"create_album": "Crear àlbum",
"create_album_page_untitled": "Untitled", "create_album_page_untitled": "Untitled",
"create_new": "CREAR NOU",
"create_shared_album_page_create": "Create", "create_shared_album_page_create": "Create",
"create_shared_album_page_share": "Comparteix", "create_shared_album_page_share": "Comparteix",
"create_shared_album_page_share_add_assets": "AFEGEIX ELEMENTS", "create_shared_album_page_share_add_assets": "AFEGEIX ELEMENTS",
"create_shared_album_page_share_select_photos": "Escull fotografies", "create_shared_album_page_share_select_photos": "Escull fotografies",
"crop": "Retalla",
"curated_location_page_title": "Localitzacions", "curated_location_page_title": "Localitzacions",
"curated_object_page_title": "Coses", "curated_object_page_title": "Coses",
"current_server_address": "Current server address",
"daily_title_text_date": "E, MMM dd", "daily_title_text_date": "E, MMM dd",
"daily_title_text_date_year": "E, MMM dd, yyyy", "daily_title_text_date_year": "E, MMM dd, yyyy",
"date_format": "E, LLL d, y • h:mm a", "date_format": "E, LLL d, y • h:mm a",
"delete_dialog_alert": "These items will be permanently deleted from Immich and from your device", "delete_dialog_alert": "These items will be permanently deleted from Immich and from your device",
"delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server", "delete_dialog_alert_local": "Aquests elements s'eliminaran permanentment del vostre dispositiu, però encara estaran disponibles al servidor Immich",
"delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device", "delete_dialog_alert_local_non_backed_up": "Alguns dels elements no tenen còpia de seguretat a Immich i s'eliminaran permanentment del dispositiu",
"delete_dialog_alert_remote": "These items will be permanently deleted from the Immich server", "delete_dialog_alert_remote": "Aquests elements s'eliminaran permanentment del servidor Immich",
"delete_dialog_cancel": "Cancel·la", "delete_dialog_cancel": "Cancel·la",
"delete_dialog_ok": "Esborra", "delete_dialog_ok": "Esborra",
"delete_dialog_ok_force": "Delete Anyway", "delete_dialog_ok_force": "Suprimeix de totes maneres",
"delete_dialog_title": "Esborra permanentment", "delete_dialog_title": "Esborra permanentment",
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only", "delete_local_dialog_ok_backed_up_only": "Esborrar només les que tinguin còpia de seguretat",
"delete_local_dialog_ok_force": "Delete Anyway", "delete_local_dialog_ok_force": "Suprimeix de totes maneres",
"delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?",
"delete_shared_link_dialog_title": "Delete Shared Link", "delete_shared_link_dialog_title": "Delete Shared Link",
"description_input_hint_text": "Afegeix descripció...", "description_input_hint_text": "Afegeix descripció...",
"description_input_submit_error": "Error updating description, check the log for more details", "description_input_submit_error": "Error updating description, check the log for more details",
"edit_date_time_dialog_date_time": "Date and Time", "description_search": "Jornada de senderisme a Sapa",
"edit_date_time_dialog_timezone": "Timezone", "download_canceled": "Descàrrega cancel·lada",
"edit_location_dialog_title": "Location", "download_complete": "Descàrrega completada",
"download_enqueue": "Descàrrega en cua",
"download_error": "Error de descàrrega",
"download_failed": "Descàrrega ha fallat",
"download_filename": "arxiu: {}",
"download_finished": "Descàrrega acabada",
"downloading": "Descarregant...",
"downloading_media": "Descàrrega multimèdia",
"download_notfound": "No s'ha trobat la descàrrega",
"download_paused": "Descàrrega pausada",
"download_started": "Descàrrega ha començat",
"download_sucess": "Descarregat amb èxit",
"download_sucess_android": "El multimedia s'ha descarregat a DCIM/Immich",
"download_waiting_to_retry": "Esperant per tornar-ho a intentar",
"edit_date_time_dialog_date_time": "Data i Hora",
"edit_date_time_dialog_search_timezone": "Cerca zona horària...",
"edit_date_time_dialog_timezone": "Zona horària",
"edit_image_title": "Editar",
"edit_location_dialog_title": "Ubicació",
"end_date": "Data final",
"enqueued": "En cua",
"enter_wifi_name": "Introdueix el nom de WiFi",
"error_change_sort_album": "No s'ha pogut canviar l'ordre d'ordenació dels àlbums",
"error_saving_image": "Error: {}",
"exif_bottom_sheet_description": "Afegeix descripció", "exif_bottom_sheet_description": "Afegeix descripció",
"exif_bottom_sheet_details": "DETALLS", "exif_bottom_sheet_details": "DETALLS",
"exif_bottom_sheet_location": "UBICACIÓ", "exif_bottom_sheet_location": "UBICACIÓ",
"exif_bottom_sheet_location_add": "Add a location", "exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PEOPLE", "exif_bottom_sheet_people": "PERSONES",
"exif_bottom_sheet_person_add_person": "Add name", "exif_bottom_sheet_person_add_person": "Afegir nom",
"experimental_settings_new_asset_list_subtitle": "Work in progress", "experimental_settings_new_asset_list_subtitle": "Work in progress",
"experimental_settings_new_asset_list_title": "Enable experimental photo grid", "experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!", "experimental_settings_subtitle": "Use at your own risk!",
"experimental_settings_title": "Experimental", "experimental_settings_title": "Experimental",
"external_network": "Xarxa externa",
"external_network_sheet_info": "Quan no estigui a la xarxa WiFi preferida, l'aplicació es connectarà al servidor mitjançant el primer dels URL següents a què pot arribar, començant de dalt a baix.",
"failed": "Fallat",
"favorites": "Favorits",
"favorites_page_no_favorites": "No s'han trobat preferits", "favorites_page_no_favorites": "No s'han trobat preferits",
"favorites_page_title": "Favorites", "favorites_page_title": "Favorites",
"filename_search": "Nom o extensió del fitxer",
"filter": "Filtrar",
"get_wifiname_error": "No s'ha pogut obtenir el nom de la Wi-Fi. Assegureu-vos que heu concedit els permisos necessaris i que esteu connectat a una xarxa Wi-Fi",
"grant_permission": "Grant permission",
"haptic_feedback_switch": "Activa la resposta hàptica",
"haptic_feedback_title": "Resposta Hàptica",
"header_settings_add_header_tip": "Afegeix Capçalera",
"header_settings_field_validator_msg": "El valor no pot estar buit",
"header_settings_header_name_input": "Nom de la capçalera",
"header_settings_header_value_input": "Valor de la capçalera",
"header_settings_page_title": "Capçaleres de proxy",
"headers_settings_tile_subtitle": "Definiu les capçaleres de proxy que l'aplicació hauria d'enviar amb cada sol·licitud de xarxa",
"headers_settings_tile_title": "Custom proxy headers",
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.", "home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping", "home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
"home_page_add_to_album_success": "Added {added} assets to album {album}.", "home_page_add_to_album_success": "Added {added} assets to album {album}.",
@@ -204,16 +294,22 @@
"home_page_archive_err_partner": "Can not archive partner assets, skipping", "home_page_archive_err_partner": "Can not archive partner assets, skipping",
"home_page_building_timeline": "Building the timeline", "home_page_building_timeline": "Building the timeline",
"home_page_delete_err_partner": "Can not delete partner assets, skipping", "home_page_delete_err_partner": "Can not delete partner assets, skipping",
"home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping", "home_page_delete_remote_err_local": "Elements locals a la selecció d'eliminació remota, ometent",
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping", "home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping", "home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).", "home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"home_page_share_err_local": "Can not share local assets via link, skipping", "home_page_share_err_local": "Can not share local assets via link, skipping",
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping", "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
"ignore_icloud_photos": "Ignora fotos d'iCloud",
"ignore_icloud_photos_description": "Les fotos emmagatzemades a iCloud no es penjaran al servidor Immich",
"image_saved_successfully": "Imatge desada",
"image_viewer_page_state_provider_download_error": "Download Error", "image_viewer_page_state_provider_download_error": "Download Error",
"image_viewer_page_state_provider_download_started": "Download Started", "image_viewer_page_state_provider_download_started": "Descàrrega començada",
"image_viewer_page_state_provider_download_success": "Download Success", "image_viewer_page_state_provider_download_success": "Download Success",
"image_viewer_page_state_provider_share_error": "Share Error", "image_viewer_page_state_provider_share_error": "Share Error",
"invalid_date": "Data invàlida",
"invalid_date_format": "Format de data invàlid",
"library": "Llibreria",
"library_page_albums": "Àlbums", "library_page_albums": "Àlbums",
"library_page_archive": "Arxiu", "library_page_archive": "Arxiu",
"library_page_device_albums": "Àlbums al Dispositiu", "library_page_device_albums": "Àlbums al Dispositiu",
@@ -226,13 +322,17 @@
"library_page_sort_most_oldest_photo": "Oldest photo", "library_page_sort_most_oldest_photo": "Oldest photo",
"library_page_sort_most_recent_photo": "Most recent photo", "library_page_sort_most_recent_photo": "Most recent photo",
"library_page_sort_title": "Album title", "library_page_sort_title": "Album title",
"location_picker_choose_on_map": "Choose on map", "local_network": "Xarxa local",
"location_picker_latitude": "Latitude", "local_network_sheet_info": "L'aplicació es connectarà al servidor mitjançant aquest URL quan utilitzeu la xarxa Wi-Fi especificada",
"location_picker_latitude_error": "Enter a valid latitude", "location_permission": "Permís d'ubicació",
"location_permission_content": "Per utilitzar la funció de canvi automàtic, Immich necessita un permís de ubicació precisa perquè pugui llegir el nom de la xarxa WiFi actual",
"location_picker_choose_on_map": "Escollir en el mapa",
"location_picker_latitude": "Latitud",
"location_picker_latitude_error": "Introdueix una latitud vàlida",
"location_picker_latitude_hint": "Enter your latitude here", "location_picker_latitude_hint": "Enter your latitude here",
"location_picker_longitude": "Longitude", "location_picker_longitude": "Longitud",
"location_picker_longitude_error": "Enter a valid longitude", "location_picker_longitude_error": "Introdueix una longitud vàlida",
"location_picker_longitude_hint": "Enter your longitude here", "location_picker_longitude_hint": "Introdueix aquí la longitud",
"login_disabled": "Login has been disabled", "login_disabled": "Login has been disabled",
"login_form_api_exception": "API exception. Please check the server URL and try again.", "login_form_api_exception": "API exception. Please check the server URL and try again.",
"login_form_back_button_text": "Back", "login_form_back_button_text": "Back",
@@ -258,12 +358,12 @@
"login_form_server_error": "Could not connect to server.", "login_form_server_error": "Could not connect to server.",
"login_password_changed_error": "There was an error updating your password", "login_password_changed_error": "There was an error updating your password",
"login_password_changed_success": "Password updated successfully", "login_password_changed_success": "Password updated successfully",
"map_assets_in_bound": "{} photo", "map_assets_in_bound": "{} foto",
"map_assets_in_bounds": "{} photos", "map_assets_in_bounds": "{} fotos",
"map_cannot_get_user_location": "Cannot get user's location", "map_cannot_get_user_location": "Cannot get user's location",
"map_location_dialog_cancel": "Cancel", "map_location_dialog_cancel": "Cancel",
"map_location_dialog_yes": "Yes", "map_location_dialog_yes": "Yes",
"map_location_picker_page_use_location": "Use this location", "map_location_picker_page_use_location": "Utilitzar aquesta ubicació",
"map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?",
"map_location_service_disabled_title": "Location Service disabled", "map_location_service_disabled_title": "Location Service disabled",
"map_no_assets_in_bounds": "No photos in this area", "map_no_assets_in_bounds": "No photos in this area",
@@ -271,32 +371,44 @@
"map_no_location_permission_title": "Location Permission denied", "map_no_location_permission_title": "Location Permission denied",
"map_settings_dark_mode": "Dark mode", "map_settings_dark_mode": "Dark mode",
"map_settings_date_range_option_all": "All", "map_settings_date_range_option_all": "All",
"map_settings_date_range_option_day": "Past 24 hours", "map_settings_date_range_option_day": "Últimes 24 hores",
"map_settings_date_range_option_days": "Past {} days", "map_settings_date_range_option_days": "Darrers {} dies",
"map_settings_date_range_option_year": "Past year", "map_settings_date_range_option_year": "Any passat",
"map_settings_date_range_option_years": "Past {} years", "map_settings_date_range_option_years": "Darrers {} anys",
"map_settings_dialog_cancel": "Cancel", "map_settings_dialog_cancel": "Cancel",
"map_settings_dialog_save": "Save", "map_settings_dialog_save": "Save",
"map_settings_dialog_title": "Map Settings", "map_settings_dialog_title": "Map Settings",
"map_settings_include_show_archived": "Include Archived", "map_settings_include_show_archived": "Include Archived",
"map_settings_include_show_partners": "Incloure companys",
"map_settings_only_relative_range": "Date range", "map_settings_only_relative_range": "Date range",
"map_settings_only_show_favorites": "Show Favorite Only", "map_settings_only_show_favorites": "Show Favorite Only",
"map_settings_theme_settings": "Map Theme", "map_settings_theme_settings": "Tema del Mapa",
"map_zoom_to_see_photos": "Zoom out to see photos", "map_zoom_to_see_photos": "Zoom out to see photos",
"memories_all_caught_up": "All caught up", "memories_all_caught_up": "Posat al dia",
"memories_check_back_tomorrow": "Check back tomorrow for more memories", "memories_check_back_tomorrow": "Torna demà per veure més records",
"memories_start_over": "Start Over", "memories_start_over": "Torna a començar",
"memories_swipe_to_close": "Swipe up to close", "memories_swipe_to_close": "Llisca per tancar",
"memories_year_ago": "Fa un any",
"memories_years_ago": "Fa {} anys",
"monthly_title_text_date_format": "MMMM y", "monthly_title_text_date_format": "MMMM y",
"motion_photos_page_title": "Motion Photos", "motion_photos_page_title": "Motion Photos",
"multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping", "multiselect_grid_edit_date_time_err_read_only": "No es pot canviar la data del fitxer(s) de només lectura, ometent",
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping", "multiselect_grid_edit_gps_err_read_only": "No es pot canviar la localització de fitxers de només lectura. Saltant.",
"my_albums": "Els meus àlbums",
"networking_settings": "Xarxes",
"networking_subtitle": "Gestiona la configuració del endpoint del servidor",
"no_assets_to_show": "No hi ha elements per mostrar",
"no_name": "Sense nom",
"notification_permission_dialog_cancel": "Cancel·la", "notification_permission_dialog_cancel": "Cancel·la",
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.", "notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
"notification_permission_dialog_settings": "Configuració", "notification_permission_dialog_settings": "Configuració",
"notification_permission_list_tile_content": "Grant permission to enable notifications.", "notification_permission_list_tile_content": "Grant permission to enable notifications.",
"notification_permission_list_tile_enable_button": "Activa les notificacions", "notification_permission_list_tile_enable_button": "Activa les notificacions",
"notification_permission_list_tile_title": "Notification Permission", "notification_permission_list_tile_title": "Notification Permission",
"not_selected": "No seleccionat",
"on_this_device": "En aquest dispositiu",
"partner_list_user_photos": "fotos de {user}",
"partner_list_view_all": "Veure tot",
"partner_page_add_partner": "Afegeix company", "partner_page_add_partner": "Afegeix company",
"partner_page_empty_message": "Your photos are not yet shared with any partner.", "partner_page_empty_message": "Your photos are not yet shared with any partner.",
"partner_page_no_more_users": "No more users to add", "partner_page_no_more_users": "No more users to add",
@@ -306,6 +418,9 @@
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.", "partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
"partner_page_stop_sharing_title": "Stop sharing your photos?", "partner_page_stop_sharing_title": "Stop sharing your photos?",
"partner_page_title": "Company", "partner_page_title": "Company",
"partners": "Companys",
"paused": "Pausat",
"people": "Persones",
"permission_onboarding_back": "Back", "permission_onboarding_back": "Back",
"permission_onboarding_continue_anyway": "Continue anyway", "permission_onboarding_continue_anyway": "Continue anyway",
"permission_onboarding_get_started": "Get started", "permission_onboarding_get_started": "Get started",
@@ -316,7 +431,9 @@
"permission_onboarding_permission_granted": "Permission granted! You are all set.", "permission_onboarding_permission_granted": "Permission granted! You are all set.",
"permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.", "permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
"permission_onboarding_request": "Immich requires permission to view your photos and videos.", "permission_onboarding_request": "Immich requires permission to view your photos and videos.",
"preferences_settings_title": "Preferences", "places": "Llocs",
"preferences_settings_subtitle": "Gestiona les preferències de l'aplicació",
"preferences_settings_title": "Preferències",
"profile_drawer_app_logs": "Logs", "profile_drawer_app_logs": "Logs",
"profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.", "profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.",
"profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.", "profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.",
@@ -328,9 +445,44 @@
"profile_drawer_settings": "Settings", "profile_drawer_settings": "Settings",
"profile_drawer_sign_out": "Tanca la sessió", "profile_drawer_sign_out": "Tanca la sessió",
"profile_drawer_trash": "Trash", "profile_drawer_trash": "Trash",
"recently_added": "Afegit recentment",
"recently_added_page_title": "Recently Added", "recently_added_page_title": "Recently Added",
"scaffold_body_error_occurred": "Error occurred", "save": "Desa",
"save_to_gallery": "Desa a galeria",
"scaffold_body_error_occurred": "S'ha produït un error",
"search_albums": "Cerca àlbums",
"search_bar_hint": "Search your photos", "search_bar_hint": "Search your photos",
"search_filter_apply": "Aplicar filtre",
"search_filter_camera": "Càmera",
"search_filter_camera_make": "Marca",
"search_filter_camera_model": "Model",
"search_filter_camera_title": "Selecciona el tipus de càmera",
"search_filter_contextual": "Cerca per contexte",
"search_filter_date": "Data",
"search_filter_date_interval": "{start} a {end}",
"search_filter_date_title": "Selecciona un rang de dates",
"search_filter_description": "Cerca per descripció",
"search_filter_display_option_archive": "Arxivat",
"search_filter_display_option_favorite": "Favorit",
"search_filter_display_option_not_in_album": "No en àlbum",
"search_filter_display_options": "Opcions de Visualització",
"search_filter_display_options_title": "Opcions de visualització",
"search_filter_filename": "Cerca pel nom del fitxer",
"search_filter_location": "Ubicació",
"search_filter_location_city": "Ciutat",
"search_filter_location_country": "País",
"search_filter_location_state": "Estat",
"search_filter_location_title": "Selecciona l'ubicació",
"search_filter_media_type": "Tipus de multimèdia",
"search_filter_media_type_all": "Tot",
"search_filter_media_type_image": "Imatge",
"search_filter_media_type_title": "Selecciona tipus de multimèdia",
"search_filter_media_type_video": "Vídeo",
"search_filter_people": "Persones",
"search_filter_people_hint": "Filtra persones",
"search_filter_people_title": "Selecciona persones",
"search_no_more_result": "No més resultats",
"search_no_result": "No s'han trobat resultats, proveu un terme de cerca o una combinació diferents",
"search_page_categories": "Categories", "search_page_categories": "Categories",
"search_page_favorites": "Preferides", "search_page_favorites": "Preferides",
"search_page_motion_photos": "Fotografies animades", "search_page_motion_photos": "Fotografies animades",
@@ -347,6 +499,7 @@
"search_page_places": "Llocs", "search_page_places": "Llocs",
"search_page_recently_added": "Afegit recentment", "search_page_recently_added": "Afegit recentment",
"search_page_screenshots": "Captures de pantalla", "search_page_screenshots": "Captures de pantalla",
"search_page_search_photos_videos": "Cerca les teves fotos i vídeos",
"search_page_selfies": "Autofotos", "search_page_selfies": "Autofotos",
"search_page_things": "Coses", "search_page_things": "Coses",
"search_page_videos": "Videos", "search_page_videos": "Videos",
@@ -359,6 +512,7 @@
"select_additional_user_for_sharing_page_suggestions": "Suggeriments", "select_additional_user_for_sharing_page_suggestions": "Suggeriments",
"select_user_for_sharing_page_err_album": "Error al crear l'àlbum", "select_user_for_sharing_page_err_album": "Error al crear l'àlbum",
"select_user_for_sharing_page_share_suggestions": "Suggestions", "select_user_for_sharing_page_share_suggestions": "Suggestions",
"server_endpoint": "Endpoint de Servidor",
"server_info_box_app_version": "Versió de l'aplicació", "server_info_box_app_version": "Versió de l'aplicació",
"server_info_box_latest_release": "Latest Version", "server_info_box_latest_release": "Latest Version",
"server_info_box_server_url": "Server URL", "server_info_box_server_url": "Server URL",
@@ -368,6 +522,10 @@
"setting_image_viewer_original_title": "Load original image", "setting_image_viewer_original_title": "Load original image",
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.", "setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
"setting_image_viewer_preview_title": "Load preview image", "setting_image_viewer_preview_title": "Load preview image",
"setting_image_viewer_title": "Imatges",
"setting_languages_apply": "Aplicar",
"setting_languages_subtitle": "Canvia el llenguatge de l'aplicació",
"setting_languages_title": "Idiomes",
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}", "setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
"setting_notifications_notify_hours": "{} hours", "setting_notifications_notify_hours": "{} hours",
"setting_notifications_notify_immediately": "immediately", "setting_notifications_notify_immediately": "immediately",
@@ -382,9 +540,15 @@
"setting_notifications_total_progress_title": "Show background backup total progress", "setting_notifications_total_progress_title": "Show background backup total progress",
"setting_pages_app_bar_settings": "Settings", "setting_pages_app_bar_settings": "Settings",
"settings_require_restart": "Please restart Immich to apply this setting", "settings_require_restart": "Please restart Immich to apply this setting",
"setting_video_viewer_looping_subtitle": "Habilita per reproduir automàticament un vídeo al visualitzador de detalls.",
"setting_video_viewer_looping_title": "Bucle",
"setting_video_viewer_original_video_subtitle": "Quan reproduïu un vídeo des del servidor, reproduïu l'original encara que hi hagi una transcodificació disponible. Pot conduir a l'amortització. Els vídeos disponibles localment es reprodueixen en qualitat original independentment d'aquesta configuració.",
"setting_video_viewer_original_video_title": "Força el vídeo original",
"setting_video_viewer_title": "Vídeos",
"share_add": "Afegeix", "share_add": "Afegeix",
"share_add_photos": "Afegeix fotografies", "share_add_photos": "Afegeix fotografies",
"share_add_title": "Afegeix un títol", "share_add_title": "Afegeix un títol",
"share_assets_selected": "{} seleccionats",
"share_create_album": "Crea un àlbum", "share_create_album": "Crea un àlbum",
"shared_album_activities_input_disable": "Comment is disabled", "shared_album_activities_input_disable": "Comment is disabled",
"shared_album_activities_input_hint": "Say something", "shared_album_activities_input_hint": "Say something",
@@ -398,6 +562,7 @@
"shared_album_section_people_owner_label": "Owner", "shared_album_section_people_owner_label": "Owner",
"shared_album_section_people_title": "PEOPLE", "shared_album_section_people_title": "PEOPLE",
"share_dialog_preparing": "Preparing...", "share_dialog_preparing": "Preparing...",
"shared_intent_upload_button_progress_text": "{} / {} Pujat",
"shared_link_app_bar_title": "Shared Links", "shared_link_app_bar_title": "Shared Links",
"shared_link_clipboard_copied_massage": "Copied to clipboard", "shared_link_clipboard_copied_massage": "Copied to clipboard",
"shared_link_clipboard_text": "Link: {}\nPassword: {}", "shared_link_clipboard_text": "Link: {}\nPassword: {}",
@@ -412,13 +577,15 @@
"shared_link_edit_description": "Description", "shared_link_edit_description": "Description",
"shared_link_edit_description_hint": "Enter the share description", "shared_link_edit_description_hint": "Enter the share description",
"shared_link_edit_expire_after": "Expire after", "shared_link_edit_expire_after": "Expire after",
"shared_link_edit_expire_after_option_day": "1 day", "shared_link_edit_expire_after_option_day": "1 dia",
"shared_link_edit_expire_after_option_days": "{} days", "shared_link_edit_expire_after_option_days": "{} dies",
"shared_link_edit_expire_after_option_hour": "1 hour", "shared_link_edit_expire_after_option_hour": "1 hora",
"shared_link_edit_expire_after_option_hours": "{} hours", "shared_link_edit_expire_after_option_hours": "{} hores",
"shared_link_edit_expire_after_option_minute": "1 minute", "shared_link_edit_expire_after_option_minute": "1 minut",
"shared_link_edit_expire_after_option_minutes": "{} minutes", "shared_link_edit_expire_after_option_minutes": "{} minuts",
"shared_link_edit_expire_after_option_months": "{} mesos",
"shared_link_edit_expire_after_option_never": "Never", "shared_link_edit_expire_after_option_never": "Never",
"shared_link_edit_expire_after_option_year": "any {}",
"shared_link_edit_password": "Password", "shared_link_edit_password": "Password",
"shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_password_hint": "Enter the share password",
"shared_link_edit_show_meta": "Show metadata", "shared_link_edit_show_meta": "Show metadata",
@@ -426,65 +593,89 @@
"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_error_server_url_fetch": "Cannot fetch the server url",
"shared_link_expired": "Expired", "shared_link_expired": "Expired",
"shared_link_expires_day": "Expires in {} day", "shared_link_expires_day": "Caduca d'aquí a {} dia",
"shared_link_expires_days": "Expires in {} days", "shared_link_expires_days": "Caduca d'aquí a {} dies",
"shared_link_expires_hour": "Expires in {} hour", "shared_link_expires_hour": "Caduca d'aquí a {} hora",
"shared_link_expires_hours": "Expires in {} hours", "shared_link_expires_hours": "Caduca d'aquí a {} hores",
"shared_link_expires_minute": "Expires in {} minute", "shared_link_expires_minute": "Caduca d'aquí a {} minut",
"shared_link_expires_minutes": "Expires in {} minutes", "shared_link_expires_minutes": "Expires in {} minutes",
"shared_link_expires_never": "Expires ∞", "shared_link_expires_never": "Expires ∞",
"shared_link_expires_second": "Expires in {} second", "shared_link_expires_second": "Caduca d'aquí a {} segon",
"shared_link_expires_seconds": "Expires in {} seconds", "shared_link_expires_seconds": "Expires in {} seconds",
"shared_link_info_chip_download": "Baixa", "shared_link_individual_shared": "Individual compartit",
"shared_link_info_chip_download": "Download",
"shared_link_info_chip_metadata": "EXIF", "shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Puja", "shared_link_info_chip_upload": "Puja",
"shared_link_manage_links": "Manage Shared links", "shared_link_manage_links": "Manage Shared links",
"share_done": "Fet", "shared_link_public_album": "Àlbum públic",
"shared_links": "Enllaços compartits",
"share_done": "Done",
"shared_with_me": "Compartit amb mi",
"share_invite": "Convida a l'àlbum", "share_invite": "Convida a l'àlbum",
"sharing_page_album": "Àlbums compartits", "sharing_page_album": "Shared albums",
"sharing_page_description": "Create shared albums to share photos and videos with people in your network.", "sharing_page_description": "Create shared albums to share photos and videos with people in your network.",
"sharing_page_empty_list": "EMPTY LIST", "sharing_page_empty_list": "EMPTY LIST",
"sharing_silver_appbar_create_shared_album": "Crea àlbum compartit", "sharing_silver_appbar_create_shared_album": "Crea àlbum compartit",
"sharing_silver_appbar_shared_links": "Shared links", "sharing_silver_appbar_shared_links": "Shared links",
"sharing_silver_appbar_share_partner": "Comparteix amb un company", "sharing_silver_appbar_share_partner": "Comparteix amb un company",
"tab_controller_nav_library": "Bibilioteca", "start_date": "Data inicial",
"tab_controller_nav_photos": "Fotos", "sync": "Sincronitzar",
"sync_albums": "Sincronitzar àlbums",
"sync_albums_manual_subtitle": "Sincronitza tots els vídeos i fotos penjats amb els àlbums de còpia de seguretat seleccionats",
"sync_upload_album_setting_subtitle": "Creeu i pugeu les seves fotos i vídeos als àlbums seleccionats a Immich",
"tab_controller_nav_library": "Library",
"tab_controller_nav_photos": "Fotografies",
"tab_controller_nav_search": "Cerca", "tab_controller_nav_search": "Cerca",
"tab_controller_nav_sharing": "Compartint", "tab_controller_nav_sharing": "Compartint",
"theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles", "theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})", "theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
"theme_setting_dark_mode_switch": "Modes fosc", "theme_setting_colorful_interface_subtitle": "Apliqueu color primari a les superfícies de fons.",
"theme_setting_colorful_interface_title": "Interfície colorida",
"theme_setting_dark_mode_switch": "Dark mode",
"theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer", "theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
"theme_setting_image_viewer_quality_title": "Image viewer quality", "theme_setting_image_viewer_quality_title": "Image viewer quality",
"theme_setting_primary_color_subtitle": "Trieu un color per a les accions i els accents principals.",
"theme_setting_primary_color_title": "Color primari",
"theme_setting_system_primary_color_title": "Utilitza color de sistema",
"theme_setting_system_theme_switch": "Automatic (Follow system setting)", "theme_setting_system_theme_switch": "Automatic (Follow system setting)",
"theme_setting_theme_subtitle": "Choose the app's theme setting", "theme_setting_theme_subtitle": "Choose the app's theme setting",
"theme_setting_theme_title": "Tema", "theme_setting_theme_title": "Theme",
"theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load", "theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load",
"theme_setting_three_stage_loading_title": "Enable three-stage loading", "theme_setting_three_stage_loading_title": "Enable three-stage loading",
"translated_text_options": "Options", "translated_text_options": "Options",
"trash_page_delete": "Elimina", "trash": "Paperera",
"trash_page_delete_all": "Elimina-ho tot", "trash_emptied": "Paperera buidada",
"trash_page_empty_trash_btn": "Buida la paperera", "trash_page_delete": "Delete",
"trash_page_delete_all": "Delete All",
"trash_page_empty_trash_btn": "Empty trash",
"trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich",
"trash_page_empty_trash_dialog_ok": "Ok", "trash_page_empty_trash_dialog_ok": "Ok",
"trash_page_info": "Trashed items will be permanently deleted after {} days", "trash_page_info": "Trashed items will be permanently deleted after {} days",
"trash_page_no_assets": "No trashed assets", "trash_page_no_assets": "No trashed assets",
"trash_page_restore": "Recupera", "trash_page_restore": "Restore",
"trash_page_restore_all": "Recupera-ho tot", "trash_page_restore_all": "Restore All",
"trash_page_select_assets_btn": "Select assets", "trash_page_select_assets_btn": "Select assets",
"trash_page_select_btn": "Select", "trash_page_select_btn": "Select",
"trash_page_title": "Trash ({})", "trash_page_title": "Trash ({})",
"upload_dialog_cancel": "Cancel·la", "upload": "Puja",
"upload_dialog_cancel": "Cancel",
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
"upload_dialog_ok": "Upload", "upload_dialog_ok": "Upload",
"upload_dialog_title": "Upload Asset", "upload_dialog_title": "Upload Asset",
"uploading": "Pujant",
"upload_to_immich": "Puja a Immich ({})",
"use_current_connection": "utilitzar la connexió actual",
"validate_endpoint_error": "Per favor introdueix un URL vàlid",
"version_announcement_overlay_ack": "Acknowledge", "version_announcement_overlay_ack": "Acknowledge",
"version_announcement_overlay_release_notes": "release notes", "version_announcement_overlay_release_notes": "release notes",
"version_announcement_overlay_text_1": "Hi friend, there is a new release of", "version_announcement_overlay_text_1": "Hi friend, there is a new release of",
"version_announcement_overlay_text_2": "please take your time to visit the ", "version_announcement_overlay_text_2": "please take your time to visit the ",
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.", "version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89", "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
"videos": "Vídeos",
"viewer_remove_from_stack": "Remove from Stack", "viewer_remove_from_stack": "Remove from Stack",
"viewer_stack_use_as_main_asset": "Use as Main Asset", "viewer_stack_use_as_main_asset": "Use as Main Asset",
"viewer_unstack": "Un-Stack" "viewer_unstack": "Un-Stack",
} "wifi_name": "Nom WiFi",
"your_wifi_name": "El teu nom WiFi"
}

View File

@@ -133,7 +133,7 @@
"backup_info_card_assets": "assets", "backup_info_card_assets": "assets",
"backup_manual_cancelled": "Cancelled", "backup_manual_cancelled": "Cancelled",
"backup_manual_failed": "Failed", "backup_manual_failed": "Failed",
"backup_manual_in_progress": "Upload already in progress. Try after some time", "backup_manual_in_progress": "Upload already in progress. Try after sometime",
"backup_manual_success": "Success", "backup_manual_success": "Success",
"backup_manual_title": "Upload status", "backup_manual_title": "Upload status",
"backup_options_page_title": "Backup options", "backup_options_page_title": "Backup options",
@@ -678,4 +678,4 @@
"viewer_unstack": "Un-Stack", "viewer_unstack": "Un-Stack",
"wifi_name": "WiFi Name", "wifi_name": "WiFi Name",
"your_wifi_name": "Your WiFi name" "your_wifi_name": "Your WiFi name"
} }

View File

@@ -133,7 +133,7 @@
"backup_info_card_assets": "assets", "backup_info_card_assets": "assets",
"backup_manual_cancelled": "Cancelled", "backup_manual_cancelled": "Cancelled",
"backup_manual_failed": "Failed", "backup_manual_failed": "Failed",
"backup_manual_in_progress": "Upload already in progress. Try after some time", "backup_manual_in_progress": "Upload already in progress. Try after sometime",
"backup_manual_success": "Success", "backup_manual_success": "Success",
"backup_manual_title": "Upload status", "backup_manual_title": "Upload status",
"backup_options_page_title": "Backup options", "backup_options_page_title": "Backup options",
@@ -678,4 +678,4 @@
"viewer_unstack": "Un-Stack", "viewer_unstack": "Un-Stack",
"wifi_name": "WiFi Name", "wifi_name": "WiFi Name",
"your_wifi_name": "Your WiFi name" "your_wifi_name": "Your WiFi name"
} }

View File

@@ -133,7 +133,7 @@
"backup_info_card_assets": "assets", "backup_info_card_assets": "assets",
"backup_manual_cancelled": "Cancelled", "backup_manual_cancelled": "Cancelled",
"backup_manual_failed": "Failed", "backup_manual_failed": "Failed",
"backup_manual_in_progress": "Upload already in progress. Try after some time", "backup_manual_in_progress": "Upload already in progress. Try after sometime",
"backup_manual_success": "Success", "backup_manual_success": "Success",
"backup_manual_title": "Upload status", "backup_manual_title": "Upload status",
"backup_options_page_title": "Backup options", "backup_options_page_title": "Backup options",
@@ -678,4 +678,4 @@
"viewer_unstack": "स्टैक रद्द करें", "viewer_unstack": "स्टैक रद्द करें",
"wifi_name": "WiFi Name", "wifi_name": "WiFi Name",
"your_wifi_name": "Your WiFi name" "your_wifi_name": "Your WiFi name"
} }

View File

@@ -133,7 +133,7 @@
"backup_info_card_assets": "assets", "backup_info_card_assets": "assets",
"backup_manual_cancelled": "Cancelled", "backup_manual_cancelled": "Cancelled",
"backup_manual_failed": "Failed", "backup_manual_failed": "Failed",
"backup_manual_in_progress": "Upload already in progress. Try after some time", "backup_manual_in_progress": "Upload already in progress. Try after sometime",
"backup_manual_success": "Success", "backup_manual_success": "Success",
"backup_manual_title": "Upload status", "backup_manual_title": "Upload status",
"backup_options_page_title": "Backup options", "backup_options_page_title": "Backup options",
@@ -678,4 +678,4 @@
"viewer_unstack": "Un-Stack", "viewer_unstack": "Un-Stack",
"wifi_name": "WiFi Name", "wifi_name": "WiFi Name",
"your_wifi_name": "Your WiFi name" "your_wifi_name": "Your WiFi name"
} }

View File

@@ -133,7 +133,7 @@
"backup_info_card_assets": "assets", "backup_info_card_assets": "assets",
"backup_manual_cancelled": "Cancelled", "backup_manual_cancelled": "Cancelled",
"backup_manual_failed": "Failed", "backup_manual_failed": "Failed",
"backup_manual_in_progress": "Upload already in progress. Try after some time", "backup_manual_in_progress": "Upload already in progress. Try after sometime",
"backup_manual_success": "Success", "backup_manual_success": "Success",
"backup_manual_title": "Upload status", "backup_manual_title": "Upload status",
"backup_options_page_title": "Backup options", "backup_options_page_title": "Backup options",
@@ -678,4 +678,4 @@
"viewer_unstack": "Un-Stack", "viewer_unstack": "Un-Stack",
"wifi_name": "WiFi Name", "wifi_name": "WiFi Name",
"your_wifi_name": "Your WiFi name" "your_wifi_name": "Your WiFi name"
} }

View File

@@ -133,7 +133,7 @@
"backup_info_card_assets": "assets", "backup_info_card_assets": "assets",
"backup_manual_cancelled": "Cancelled", "backup_manual_cancelled": "Cancelled",
"backup_manual_failed": "Failed", "backup_manual_failed": "Failed",
"backup_manual_in_progress": "Upload already in progress. Try after some time", "backup_manual_in_progress": "Upload already in progress. Try after sometime",
"backup_manual_success": "Success", "backup_manual_success": "Success",
"backup_manual_title": "Upload status", "backup_manual_title": "Upload status",
"backup_options_page_title": "Backup options", "backup_options_page_title": "Backup options",
@@ -678,4 +678,4 @@
"viewer_unstack": "Un-Stack", "viewer_unstack": "Un-Stack",
"wifi_name": "WiFi Name", "wifi_name": "WiFi Name",
"your_wifi_name": "Your WiFi name" "your_wifi_name": "Your WiFi name"
} }

View File

@@ -133,7 +133,7 @@
"backup_info_card_assets": "zapisi", "backup_info_card_assets": "zapisi",
"backup_manual_cancelled": "Cancelled", "backup_manual_cancelled": "Cancelled",
"backup_manual_failed": "Failed", "backup_manual_failed": "Failed",
"backup_manual_in_progress": "Upload already in progress. Try after some time", "backup_manual_in_progress": "Upload already in progress. Try after sometime",
"backup_manual_success": "Success", "backup_manual_success": "Success",
"backup_manual_title": "Upload status", "backup_manual_title": "Upload status",
"backup_options_page_title": "Backup options", "backup_options_page_title": "Backup options",
@@ -678,4 +678,4 @@
"viewer_unstack": "Un-Stack", "viewer_unstack": "Un-Stack",
"wifi_name": "WiFi Name", "wifi_name": "WiFi Name",
"your_wifi_name": "Your WiFi name" "your_wifi_name": "Your WiFi name"
} }

View File

@@ -133,7 +133,7 @@
"backup_info_card_assets": "assets", "backup_info_card_assets": "assets",
"backup_manual_cancelled": "Cancelled", "backup_manual_cancelled": "Cancelled",
"backup_manual_failed": "Failed", "backup_manual_failed": "Failed",
"backup_manual_in_progress": "Upload already in progress. Try after some time", "backup_manual_in_progress": "Upload already in progress. Try after sometime",
"backup_manual_success": "Success", "backup_manual_success": "Success",
"backup_manual_title": "Upload status", "backup_manual_title": "Upload status",
"backup_options_page_title": "Backup options", "backup_options_page_title": "Backup options",
@@ -678,4 +678,4 @@
"viewer_unstack": "Un-Stack", "viewer_unstack": "Un-Stack",
"wifi_name": "WiFi Name", "wifi_name": "WiFi Name",
"your_wifi_name": "Your WiFi name" "your_wifi_name": "Your WiFi name"
} }

View File

@@ -7,10 +7,10 @@
"action_common_select": "Вибрати", "action_common_select": "Вибрати",
"action_common_update": "Оновити", "action_common_update": "Оновити",
"add_a_name": "Додати ім'я", "add_a_name": "Додати ім'я",
"add_endpoint": "Add endpoint", "add_endpoint": "Додати кінцеву точку",
"add_to_album_bottom_sheet_added": "Додано до {album}", "add_to_album_bottom_sheet_added": "Додано до {album}",
"add_to_album_bottom_sheet_already_exists": "Вже є в {album}", "add_to_album_bottom_sheet_already_exists": "Вже є в {album}",
"advanced_settings_log_level_title": "Log level: {}", "advanced_settings_log_level_title": "Рівень логування: {}",
"advanced_settings_prefer_remote_subtitle": "Деякі пристрої вельми повільно завантажують мініатюри із елементів на пристрої. Активуйте для завантаження віддалених мініатюр натомість.", "advanced_settings_prefer_remote_subtitle": "Деякі пристрої вельми повільно завантажують мініатюри із елементів на пристрої. Активуйте для завантаження віддалених мініатюр натомість.",
"advanced_settings_prefer_remote_title": "Перевага віддаленим зображенням", "advanced_settings_prefer_remote_title": "Перевага віддаленим зображенням",
"advanced_settings_proxy_headers_subtitle": "Визначте заголовки проксі-сервера, які Immich має надсилати з кожним мережевим запитом.", "advanced_settings_proxy_headers_subtitle": "Визначте заголовки проксі-сервера, які Immich має надсилати з кожним мережевим запитом.",
@@ -66,12 +66,12 @@
"assets_restored_successfully": "{} елемент(и) успішно відновлено", "assets_restored_successfully": "{} елемент(и) успішно відновлено",
"assets_trashed": "{} елемент(и) поміщено до кошика", "assets_trashed": "{} елемент(и) поміщено до кошика",
"assets_trashed_from_server": "{} елемент(и) поміщено до кошика на сервері Immich", "assets_trashed_from_server": "{} елемент(и) поміщено до кошика на сервері Immich",
"asset_viewer_settings_subtitle": "Manage your gallery viewer settings", "asset_viewer_settings_subtitle": "Керуйте налаштуваннями переглядача галереї",
"asset_viewer_settings_title": "Переглядач зображень", "asset_viewer_settings_title": "Переглядач зображень",
"automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere", "automatic_endpoint_switching_subtitle": "Підключатися локально через зазначену Wi-Fi мережу, коли це можливо, і використовувати альтернативні з'єднання в інших випадках",
"automatic_endpoint_switching_title": "Automatic URL switching", "automatic_endpoint_switching_title": "Автоматичне перемикання URL",
"background_location_permission": "Background location permission", "background_location_permission": "Дозвіл до місцезнаходження у фоні",
"background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name", "background_location_permission_content": "Щоб перемикати мережі у фоновому режимі, Immich має *завжди* мати доступ до точної геолокації, щоб зчитувати назву Wi-Fi мережі",
"backup_album_selection_page_albums_device": "Альбоми на пристрої ({})", "backup_album_selection_page_albums_device": "Альбоми на пристрої ({})",
"backup_album_selection_page_albums_tap": "Торкніться, щоб включити,\nторкніться двічі, щоб виключити", "backup_album_selection_page_albums_tap": "Торкніться, щоб включити,\nторкніться двічі, щоб виключити",
"backup_album_selection_page_assets_scatter": "Елементи можуть належати до кількох альбомів водночас. Таким чином, альбоми можуть бути включені або вилучені під час резервного копіювання.", "backup_album_selection_page_assets_scatter": "Елементи можуть належати до кількох альбомів водночас. Таким чином, альбоми можуть бути включені або вилучені під час резервного копіювання.",
@@ -119,7 +119,7 @@
"backup_controller_page_remainder_sub": "Решта знімків та відео для резервного копіювання з вибраних", "backup_controller_page_remainder_sub": "Решта знімків та відео для резервного копіювання з вибраних",
"backup_controller_page_select": "Вибрати", "backup_controller_page_select": "Вибрати",
"backup_controller_page_server_storage": "Сховище сервера", "backup_controller_page_server_storage": "Сховище сервера",
"backup_controller_page_start_backup": "Почати Резервне Копіювання", "backup_controller_page_start_backup": "Почати резервне копіювання",
"backup_controller_page_status_off": "Автоматичне резервне копіювання в активному режимі вимкнено", "backup_controller_page_status_off": "Автоматичне резервне копіювання в активному режимі вимкнено",
"backup_controller_page_status_on": "Автоматичне резервне копіювання в активному режимі ввімкнено", "backup_controller_page_status_on": "Автоматичне резервне копіювання в активному режимі ввімкнено",
"backup_controller_page_storage_format": "{} із {} спожито", "backup_controller_page_storage_format": "{} із {} спожито",
@@ -137,7 +137,7 @@
"backup_manual_success": "Успіх", "backup_manual_success": "Успіх",
"backup_manual_title": "Стан завантаження", "backup_manual_title": "Стан завантаження",
"backup_options_page_title": "Резервне копіювання", "backup_options_page_title": "Резервне копіювання",
"backup_setting_subtitle": "Manage background and foreground upload settings", "backup_setting_subtitle": "Управління налаштуваннями завантаження у фоновому та активному режимі",
"cache_settings_album_thumbnails": "Мініатюри сторінок бібліотеки ({} елементи)", "cache_settings_album_thumbnails": "Мініатюри сторінок бібліотеки ({} елементи)",
"cache_settings_clear_cache_button": "Очистити кеш", "cache_settings_clear_cache_button": "Очистити кеш",
"cache_settings_clear_cache_button_title": "Очищає кеш програми. Це суттєво знизить продуктивність програми, доки кеш не буде перебудовано.", "cache_settings_clear_cache_button_title": "Очищає кеш програми. Це суттєво знизить продуктивність програми, доки кеш не буде перебудовано.",
@@ -156,17 +156,17 @@
"cache_settings_tile_subtitle": "Керування поведінкою локального сховища", "cache_settings_tile_subtitle": "Керування поведінкою локального сховища",
"cache_settings_tile_title": "Локальне сховище", "cache_settings_tile_title": "Локальне сховище",
"cache_settings_title": "Налаштування кешування", "cache_settings_title": "Налаштування кешування",
"cancel": "Cancel", "cancel": "Скасувати",
"canceled": "Canceled", "canceled": "Скасовано",
"change_display_order": "Change display order", "change_display_order": "Змінити порядок відображення",
"change_password_form_confirm_password": "Підтвердити пароль", "change_password_form_confirm_password": "Підтвердити пароль",
"change_password_form_description": "Привіт {name},\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": "Повторіть новий пароль",
"check_corrupt_asset_backup": "Check for corrupt asset backups", "check_corrupt_asset_backup": "Перевірити на пошкоджені резервні копії активів",
"check_corrupt_asset_backup_button": "Perform check", "check_corrupt_asset_backup_button": "Виконати перевірку",
"check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.", "check_corrupt_asset_backup_description": "Запустіть цю перевірку лише через Wi-Fi та після того, як всі активи будуть завантажені на сервер. Процес може зайняти кілька хвилин.",
"client_cert_dialog_msg_confirm": "OK", "client_cert_dialog_msg_confirm": "OK",
"client_cert_enter_password": "Введіть пароль", "client_cert_enter_password": "Введіть пароль",
"client_cert_import": "Імпорт", "client_cert_import": "Імпорт",
@@ -181,7 +181,7 @@
"common_create_new_album": "Створити новий альбом", "common_create_new_album": "Створити новий альбом",
"common_server_error": "Будь ласка, перевірте з'єднання, переконайтеся, що сервер доступний і версія програми/сервера сумісна.", "common_server_error": "Будь ласка, перевірте з'єднання, переконайтеся, що сервер доступний і версія програми/сервера сумісна.",
"common_shared": "Спільні", "common_shared": "Спільні",
"completed": "Completed", "completed": "Завершено",
"contextual_search": "Схід сонця на пляжі", "contextual_search": "Схід сонця на пляжі",
"control_bottom_app_bar_add_to_album": "Додати у альбом", "control_bottom_app_bar_add_to_album": "Додати у альбом",
"control_bottom_app_bar_album_info": "{} елементи", "control_bottom_app_bar_album_info": "{} елементи",
@@ -199,7 +199,7 @@
"control_bottom_app_bar_share": "Поділитися", "control_bottom_app_bar_share": "Поділитися",
"control_bottom_app_bar_share_to": "Поділитися", "control_bottom_app_bar_share_to": "Поділитися",
"control_bottom_app_bar_stack": "Стек", "control_bottom_app_bar_stack": "Стек",
"control_bottom_app_bar_trash_from_immich": "Перемістити до кошика", "control_bottom_app_bar_trash_from_immich": "До кошика",
"control_bottom_app_bar_unarchive": "Розархівувати", "control_bottom_app_bar_unarchive": "Розархівувати",
"control_bottom_app_bar_unfavorite": "Видалити з улюблених", "control_bottom_app_bar_unfavorite": "Видалити з улюблених",
"control_bottom_app_bar_upload": "Завантажити", "control_bottom_app_bar_upload": "Завантажити",
@@ -213,7 +213,7 @@
"crop": "Кадрувати", "crop": "Кадрувати",
"curated_location_page_title": "Місця", "curated_location_page_title": "Місця",
"curated_object_page_title": "Речі", "curated_object_page_title": "Речі",
"current_server_address": "Current server address", "current_server_address": "Поточна адреса сервера",
"daily_title_text_date": "E, MMM dd", "daily_title_text_date": "E, MMM dd",
"daily_title_text_date_year": "E, MMM dd, yyyy", "daily_title_text_date_year": "E, MMM dd, yyyy",
"date_format": "E, LLL d, y • h:mm a", "date_format": "E, LLL d, y • h:mm a",
@@ -231,7 +231,7 @@
"delete_shared_link_dialog_title": "Видалити спільне посилання", "delete_shared_link_dialog_title": "Видалити спільне посилання",
"description_input_hint_text": "Додати опис...", "description_input_hint_text": "Додати опис...",
"description_input_submit_error": "Помилка оновлення опису, перевірте логи для подробиць", "description_input_submit_error": "Помилка оновлення опису, перевірте логи для подробиць",
"description_search": "Hiking day in Sapa", "description_search": "День походу в Сапі",
"download_canceled": "Завантаження скасовано", "download_canceled": "Завантаження скасовано",
"download_complete": "Завантаження закінчено", "download_complete": "Завантаження закінчено",
"download_enqueue": "Завантаження поставлено в чергу", "download_enqueue": "Завантаження поставлено в чергу",
@@ -248,14 +248,14 @@
"download_sucess_android": "Медіафайли завантажено в DCIM/Immich", "download_sucess_android": "Медіафайли завантажено в DCIM/Immich",
"download_waiting_to_retry": "Очікування повторної спроби", "download_waiting_to_retry": "Очікування повторної спроби",
"edit_date_time_dialog_date_time": "Дата і час", "edit_date_time_dialog_date_time": "Дата і час",
"edit_date_time_dialog_search_timezone": "Search timezone...", "edit_date_time_dialog_search_timezone": "Пошук часової зони...",
"edit_date_time_dialog_timezone": "Часовий пояс", "edit_date_time_dialog_timezone": "Часовий пояс",
"edit_image_title": "Редагувати", "edit_image_title": "Редагувати",
"edit_location_dialog_title": "Місцезнаходження", "edit_location_dialog_title": "Місцезнаходження",
"end_date": "End date", "end_date": "Дата завершення",
"enqueued": "Enqueued", "enqueued": "У черзі",
"enter_wifi_name": "Enter WiFi name", "enter_wifi_name": "Введіть назву WiFi",
"error_change_sort_album": "Failed to change album sort order", "error_change_sort_album": "Не вдалося змінити порядок сортування альбому",
"error_saving_image": "Помилка: {}", "error_saving_image": "Помилка: {}",
"exif_bottom_sheet_description": "Додати опис...", "exif_bottom_sheet_description": "Додати опис...",
"exif_bottom_sheet_details": "ПОДРОБИЦІ", "exif_bottom_sheet_details": "ПОДРОБИЦІ",
@@ -267,16 +267,16 @@
"experimental_settings_new_asset_list_title": "Експериментальний макет знімків", "experimental_settings_new_asset_list_title": "Експериментальний макет знімків",
"experimental_settings_subtitle": "На власний ризик!", "experimental_settings_subtitle": "На власний ризик!",
"experimental_settings_title": "Експериментальні", "experimental_settings_title": "Експериментальні",
"external_network": "External network", "external_network": "Зовнішня мережа",
"external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", "external_network_sheet_info": "Коли ви не підключені до переважної мережі WiFi, додаток підключатиметься до сервера через першу з наведених нижче URL-адрес, яку він зможе досягти, починаючи зверху вниз",
"failed": "Failed", "failed": "Не вдалося",
"favorites": "Вибране", "favorites": "Вибране",
"favorites_page_no_favorites": "Немає улюблених елементів", "favorites_page_no_favorites": "Немає улюблених елементів",
"favorites_page_title": "Улюблені", "favorites_page_title": "Улюблені",
"filename_search": "Ім'я або розширення файлу", "filename_search": "Ім'я або розширення файлу",
"filter": "Фільтр", "filter": "Фільтр",
"get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", "get_wifiname_error": "Не вдалося отримати назву Wi-Fi. Переконайтеся, що ви надали необхідні дозволи та підключені до Wi-Fi мережі",
"grant_permission": "Grant permission", "grant_permission": "Надати дозвіл",
"haptic_feedback_switch": "Увімкнути тактильну віддачу", "haptic_feedback_switch": "Увімкнути тактильну віддачу",
"haptic_feedback_title": "Тактильна віддача", "haptic_feedback_title": "Тактильна віддача",
"header_settings_add_header_tip": "Додати заголовок", "header_settings_add_header_tip": "Додати заголовок",
@@ -322,10 +322,10 @@
"library_page_sort_most_oldest_photo": "Найдавніші фото", "library_page_sort_most_oldest_photo": "Найдавніші фото",
"library_page_sort_most_recent_photo": "Найновіші фото", "library_page_sort_most_recent_photo": "Найновіші фото",
"library_page_sort_title": "Назва альбому", "library_page_sort_title": "Назва альбому",
"local_network": "Local network", "local_network": "Локальна мережа",
"local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network", "local_network_sheet_info": "Додаток підключатиметься до сервера через цей URL, коли використовується вказана Wi-Fi мережа",
"location_permission": "Location permission", "location_permission": "Дозвіл до місцезнаходження",
"location_permission_content": "In order to use the auto-switching feature, Immich needs precise location permission so it can read the current WiFi network's name", "location_permission_content": "Щоб перемикати мережі у фоновому режимі, Immich має *завжди* мати доступ до точної геолокації, щоб зчитувати назву Wi-Fi мережі",
"location_picker_choose_on_map": "Обрати на мапі", "location_picker_choose_on_map": "Обрати на мапі",
"location_picker_latitude": "Широта", "location_picker_latitude": "Широта",
"location_picker_latitude_error": "Вкажіть дійсну широту", "location_picker_latitude_error": "Вкажіть дійсну широту",
@@ -395,8 +395,8 @@
"multiselect_grid_edit_date_time_err_read_only": "Неможливо редагувати дату елементів лише для читання, пропущено", "multiselect_grid_edit_date_time_err_read_only": "Неможливо редагувати дату елементів лише для читання, пропущено",
"multiselect_grid_edit_gps_err_read_only": "Неможливо редагувати місцезнаходження елементів лише для читання, пропущено", "multiselect_grid_edit_gps_err_read_only": "Неможливо редагувати місцезнаходження елементів лише для читання, пропущено",
"my_albums": "Мої альбоми", "my_albums": "Мої альбоми",
"networking_settings": "Networking", "networking_settings": "Мережеві налаштування",
"networking_subtitle": "Manage the server endpoint settings", "networking_subtitle": "Керування налаштуваннями кінцевої точки сервера",
"no_assets_to_show": "Елементи відсутні", "no_assets_to_show": "Елементи відсутні",
"no_name": "Без імені", "no_name": "Без імені",
"notification_permission_dialog_cancel": "Скасувати", "notification_permission_dialog_cancel": "Скасувати",
@@ -405,7 +405,7 @@
"notification_permission_list_tile_content": "Надати дозвіл для сповіщень.", "notification_permission_list_tile_content": "Надати дозвіл для сповіщень.",
"notification_permission_list_tile_enable_button": "Увімкнути Сповіщення", "notification_permission_list_tile_enable_button": "Увімкнути Сповіщення",
"notification_permission_list_tile_title": "Дозвіл на Сповіщення", "notification_permission_list_tile_title": "Дозвіл на Сповіщення",
"not_selected": "Not selected", "not_selected": "Не вибрано",
"on_this_device": "На цьому пристрої", "on_this_device": "На цьому пристрої",
"partner_list_user_photos": "Фотографії {user}", "partner_list_user_photos": "Фотографії {user}",
"partner_list_view_all": "Переглянути усі", "partner_list_view_all": "Переглянути усі",
@@ -419,7 +419,7 @@
"partner_page_stop_sharing_title": "Припинити надання ваших знімків?", "partner_page_stop_sharing_title": "Припинити надання ваших знімків?",
"partner_page_title": "Партнер", "partner_page_title": "Партнер",
"partners": "\nПартнери", "partners": "\nПартнери",
"paused": "Paused", "paused": "Призупинено",
"people": "Люди", "people": "Люди",
"permission_onboarding_back": "Назад", "permission_onboarding_back": "Назад",
"permission_onboarding_continue_anyway": "Все одно продовжити", "permission_onboarding_continue_anyway": "Все одно продовжити",
@@ -432,7 +432,7 @@
"permission_onboarding_permission_limited": "Обмежений доступ. Аби дозволити Immich резервне копіювання та керування вашою галереєю, надайте доступ до знімків та відео у Налаштуваннях", "permission_onboarding_permission_limited": "Обмежений доступ. Аби дозволити Immich резервне копіювання та керування вашою галереєю, надайте доступ до знімків та відео у Налаштуваннях",
"permission_onboarding_request": "Immich потребує доступу до ваших знімків та відео.", "permission_onboarding_request": "Immich потребує доступу до ваших знімків та відео.",
"places": "Місця", "places": "Місця",
"preferences_settings_subtitle": "Manage the app's preferences", "preferences_settings_subtitle": "Керування налаштуваннями додатку",
"preferences_settings_title": "Параметри", "preferences_settings_title": "Параметри",
"profile_drawer_app_logs": "Журнал", "profile_drawer_app_logs": "Журнал",
"profile_drawer_client_out_of_date_major": "Мобільний додаток застарів. Будь ласка, оновіть до останньої мажорної версії.", "profile_drawer_client_out_of_date_major": "Мобільний додаток застарів. Будь ласка, оновіть до останньої мажорної версії.",
@@ -447,7 +447,7 @@
"profile_drawer_trash": "Кошик", "profile_drawer_trash": "Кошик",
"recently_added": "Нещодавно додані", "recently_added": "Нещодавно додані",
"recently_added_page_title": "Нещодавні", "recently_added_page_title": "Нещодавні",
"save": "Save", "save": "Зберегти",
"save_to_gallery": "Зберегти в галерею", "save_to_gallery": "Зберегти в галерею",
"scaffold_body_error_occurred": "Виникла помилка", "scaffold_body_error_occurred": "Виникла помилка",
"search_albums": "Пошук альбому", "search_albums": "Пошук альбому",
@@ -457,17 +457,17 @@
"search_filter_camera_make": "Виробник", "search_filter_camera_make": "Виробник",
"search_filter_camera_model": "Модель", "search_filter_camera_model": "Модель",
"search_filter_camera_title": "Виберіть тип камери", "search_filter_camera_title": "Виберіть тип камери",
"search_filter_contextual": "Search by context", "search_filter_contextual": "Пошук за контекстом",
"search_filter_date": "Дата", "search_filter_date": "Дата",
"search_filter_date_interval": "{start} до {end}", "search_filter_date_interval": "{start} до {end}",
"search_filter_date_title": "Виберіть діапазон дат", "search_filter_date_title": "Виберіть діапазон дат",
"search_filter_description": "Search by description", "search_filter_description": "Пошук за описом",
"search_filter_display_option_archive": "Архів", "search_filter_display_option_archive": "Архів",
"search_filter_display_option_favorite": "Улюблені", "search_filter_display_option_favorite": "Улюблені",
"search_filter_display_option_not_in_album": "Не в альбомі", "search_filter_display_option_not_in_album": "Не в альбомі",
"search_filter_display_options": "Параметри відображення", "search_filter_display_options": "Параметри відображення",
"search_filter_display_options_title": "Параметри відображення", "search_filter_display_options_title": "Параметри відображення",
"search_filter_filename": "Search by file name", "search_filter_filename": "Пошук за назвою файлу",
"search_filter_location": "Місцезнаходження", "search_filter_location": "Місцезнаходження",
"search_filter_location_city": "Місто", "search_filter_location_city": "Місто",
"search_filter_location_country": "Країна", "search_filter_location_country": "Країна",
@@ -479,10 +479,10 @@
"search_filter_media_type_title": "Виберіть тип носія", "search_filter_media_type_title": "Виберіть тип носія",
"search_filter_media_type_video": "Відео", "search_filter_media_type_video": "Відео",
"search_filter_people": "Люди", "search_filter_people": "Люди",
"search_filter_people_hint": "Filter people", "search_filter_people_hint": "Фільтрувати за людьми",
"search_filter_people_title": "Виберіть людей", "search_filter_people_title": "Виберіть людей",
"search_no_more_result": "No more results", "search_no_more_result": "Більше результатів немає",
"search_no_result": "No results found, try a different search term or combination", "search_no_result": "Результатів не знайдено, спробуйте інший запит або комбінацію",
"search_page_categories": "Категорії", "search_page_categories": "Категорії",
"search_page_favorites": "Улюблені", "search_page_favorites": "Улюблені",
"search_page_motion_photos": "Рухомі знімки", "search_page_motion_photos": "Рухомі знімки",
@@ -499,7 +499,7 @@
"search_page_places": "Місця", "search_page_places": "Місця",
"search_page_recently_added": "Нещодавно додані", "search_page_recently_added": "Нещодавно додані",
"search_page_screenshots": "Знімки екрану", "search_page_screenshots": "Знімки екрану",
"search_page_search_photos_videos": "Search for your photos and videos", "search_page_search_photos_videos": "Шукайте ваші фото та відео",
"search_page_selfies": "Селфі", "search_page_selfies": "Селфі",
"search_page_things": "Речі", "search_page_things": "Речі",
"search_page_videos": "Відео", "search_page_videos": "Відео",
@@ -512,7 +512,7 @@
"select_additional_user_for_sharing_page_suggestions": "Пропозиції", "select_additional_user_for_sharing_page_suggestions": "Пропозиції",
"select_user_for_sharing_page_err_album": "Не вдалося створити альбом", "select_user_for_sharing_page_err_album": "Не вдалося створити альбом",
"select_user_for_sharing_page_share_suggestions": "Пропозиції", "select_user_for_sharing_page_share_suggestions": "Пропозиції",
"server_endpoint": "Server Endpoint", "server_endpoint": "Кінцева точка сервера",
"server_info_box_app_version": "Версія додатка", "server_info_box_app_version": "Версія додатка",
"server_info_box_latest_release": "Остання версія", "server_info_box_latest_release": "Остання версія",
"server_info_box_server_url": "URL сервера", "server_info_box_server_url": "URL сервера",
@@ -524,7 +524,7 @@
"setting_image_viewer_preview_title": "Завантажувати зображення попереднього перегляду", "setting_image_viewer_preview_title": "Завантажувати зображення попереднього перегляду",
"setting_image_viewer_title": "Зображення", "setting_image_viewer_title": "Зображення",
"setting_languages_apply": "Застосувати", "setting_languages_apply": "Застосувати",
"setting_languages_subtitle": "Change the app's language", "setting_languages_subtitle": "Змінити мову додатку",
"setting_languages_title": "Мова", "setting_languages_title": "Мова",
"setting_notifications_notify_failures_grace_period": "Повідомити про помилки фонового резервного копіювання: {}", "setting_notifications_notify_failures_grace_period": "Повідомити про помилки фонового резервного копіювання: {}",
"setting_notifications_notify_hours": "{} годин", "setting_notifications_notify_hours": "{} годин",
@@ -542,8 +542,8 @@
"settings_require_restart": "Перезавантажте програму для застосування цього налаштування", "settings_require_restart": "Перезавантажте програму для застосування цього налаштування",
"setting_video_viewer_looping_subtitle": "Увімкнути циклічне відтворення відео", "setting_video_viewer_looping_subtitle": "Увімкнути циклічне відтворення відео",
"setting_video_viewer_looping_title": "Циклічне відтворення", "setting_video_viewer_looping_title": "Циклічне відтворення",
"setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", "setting_video_viewer_original_video_subtitle": "При трансляції відео з сервера відтворювати оригінал, навіть якщо доступна транскодування. Може призвести до буферизації. Відео, доступні локально, відтворюються в оригінальній якості, незважаючи на це налаштування.",
"setting_video_viewer_original_video_title": "Force original video", "setting_video_viewer_original_video_title": "Примусово відтворювати оригінальне відео",
"setting_video_viewer_title": "Відео", "setting_video_viewer_title": "Відео",
"share_add": "Додати", "share_add": "Додати",
"share_add_photos": "Додати знімки", "share_add_photos": "Додати знімки",
@@ -562,7 +562,7 @@
"shared_album_section_people_owner_label": "Власник", "shared_album_section_people_owner_label": "Власник",
"shared_album_section_people_title": "ЛЮДИ", "shared_album_section_people_title": "ЛЮДИ",
"share_dialog_preparing": "Підготовка...", "share_dialog_preparing": "Підготовка...",
"shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_intent_upload_button_progress_text": "{} / {} Завантажено",
"shared_link_app_bar_title": "Спільні посилання", "shared_link_app_bar_title": "Спільні посилання",
"shared_link_clipboard_copied_massage": "Скопійовано в буфер обміну", "shared_link_clipboard_copied_massage": "Скопійовано в буфер обміну",
"shared_link_clipboard_text": "Посилання: {}\nПароль: {}", "shared_link_clipboard_text": "Посилання: {}\nПароль: {}",
@@ -618,7 +618,7 @@
"sharing_silver_appbar_create_shared_album": "Створити спільний альбом", "sharing_silver_appbar_create_shared_album": "Створити спільний альбом",
"sharing_silver_appbar_shared_links": "Спільні посилання", "sharing_silver_appbar_shared_links": "Спільні посилання",
"sharing_silver_appbar_share_partner": "Поділитися з партнером", "sharing_silver_appbar_share_partner": "Поділитися з партнером",
"start_date": "Start date", "start_date": "Дата початку",
"sync": "Синхронізувати", "sync": "Синхронізувати",
"sync_albums": "Синхронізувати альбоми", "sync_albums": "Синхронізувати альбоми",
"sync_albums_manual_subtitle": "Синхронізувати всі завантажені фото та відео у вибрані альбоми для резервного копіювання", "sync_albums_manual_subtitle": "Синхронізувати всі завантажені фото та відео у вибрані альбоми для резервного копіювання",
@@ -657,15 +657,15 @@
"trash_page_select_assets_btn": "Вибрані елементи", "trash_page_select_assets_btn": "Вибрані елементи",
"trash_page_select_btn": "Вибрати", "trash_page_select_btn": "Вибрати",
"trash_page_title": "Кошик ({})", "trash_page_title": "Кошик ({})",
"upload": "Upload", "upload": "Завантажити",
"upload_dialog_cancel": "Скасувати", "upload_dialog_cancel": "Скасувати",
"upload_dialog_info": "Бажаєте створити резервну копію вибраних елементів на сервері?", "upload_dialog_info": "Бажаєте створити резервну копію вибраних елементів на сервері?",
"upload_dialog_ok": "Завантажити", "upload_dialog_ok": "Завантажити",
"upload_dialog_title": "Завантажити Елементи", "upload_dialog_title": "Завантажити Елементи",
"uploading": "Uploading", "uploading": "Завантаження",
"upload_to_immich": "Upload to Immich ({})", "upload_to_immich": "Завантажити в Immich ({})",
"use_current_connection": "use current connection", "use_current_connection": "використовувати поточне підключення",
"validate_endpoint_error": "Please enter a valid URL", "validate_endpoint_error": "Будь ласка, введіть дійсну URL-адресу",
"version_announcement_overlay_ack": "Прийняти", "version_announcement_overlay_ack": "Прийняти",
"version_announcement_overlay_release_notes": "примітки до випуску", "version_announcement_overlay_release_notes": "примітки до випуску",
"version_announcement_overlay_text_1": "Вітаємо, є новий випуск ", "version_announcement_overlay_text_1": "Вітаємо, є новий випуск ",
@@ -676,6 +676,6 @@
"viewer_remove_from_stack": "Видалити зі стеку", "viewer_remove_from_stack": "Видалити зі стеку",
"viewer_stack_use_as_main_asset": "Використовувати як основний елементи", "viewer_stack_use_as_main_asset": "Використовувати як основний елементи",
"viewer_unstack": "Розібрати стек", "viewer_unstack": "Розібрати стек",
"wifi_name": "WiFi Name", "wifi_name": "Назва WiFi",
"your_wifi_name": "Your WiFi name" "your_wifi_name": "Ваша назва WiFi"
} }

View File

@@ -541,7 +541,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 196; CURRENT_PROJECT_VERSION = 197;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -685,7 +685,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 196; CURRENT_PROJECT_VERSION = 197;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -715,7 +715,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 196; CURRENT_PROJECT_VERSION = 197;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -748,7 +748,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 196; CURRENT_PROJECT_VERSION = 197;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -791,7 +791,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 196; CURRENT_PROJECT_VERSION = 197;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -831,7 +831,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 196; CURRENT_PROJECT_VERSION = 197;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;

View File

@@ -78,7 +78,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.128.0</string> <string>1.129.0</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@@ -93,7 +93,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>196</string> <string>197</string>
<key>FLTEnableImpeller</key> <key>FLTEnableImpeller</key>
<true/> <true/>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>

View File

@@ -19,7 +19,7 @@ platform :ios do
desc "iOS Release" desc "iOS Release"
lane :release do lane :release do
increment_version_number( increment_version_number(
version_number: "1.128.0" version_number: "1.129.0"
) )
increment_build_number( increment_build_number(
build_number: latest_testflight_build_number + 1, build_number: latest_testflight_build_number + 1,

View File

@@ -5,7 +5,7 @@ const Map<String, Locale> locales = {
'English (en_US)': Locale('en', 'US'), 'English (en_US)': Locale('en', 'US'),
// Additional locales // Additional locales
'Arabic (ar_JO)': Locale('ar', 'JO'), 'Arabic (ar_JO)': Locale('ar', 'JO'),
'Catalan (ca_CA)': Locale('ca', 'CA'), 'Catalan (ca)': Locale('ca'),
'Chinese (zh_CN)': Locale('zh', 'CN'), 'Chinese (zh_CN)': Locale('zh', 'CN'),
'Chinese Simplified (zh_Hans)': Locale('zh', 'Hans'), 'Chinese Simplified (zh_Hans)': Locale('zh', 'Hans'),
'Chinese TW (zh_TW)': Locale('zh', 'TW'), 'Chinese TW (zh_TW)': Locale('zh', 'TW'),

View File

@@ -5,10 +5,11 @@ import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart'; import 'package:immich_mobile/interfaces/person_api.interface.dart';
class SearchLocationFilter { class SearchLocationFilter {
String? country; final String? country;
String? state; final String? state;
String? city; final String? city;
SearchLocationFilter({
const SearchLocationFilter({
this.country, this.country,
this.state, this.state,
this.city, this.city,
@@ -65,9 +66,10 @@ class SearchLocationFilter {
} }
class SearchCameraFilter { class SearchCameraFilter {
String? make; final String? make;
String? model; final String? model;
SearchCameraFilter({
const SearchCameraFilter({
this.make, this.make,
this.model, this.model,
}); });
@@ -116,9 +118,10 @@ class SearchCameraFilter {
} }
class SearchDateFilter { class SearchDateFilter {
DateTime? takenBefore; final DateTime? takenBefore;
DateTime? takenAfter; final DateTime? takenAfter;
SearchDateFilter({
const SearchDateFilter({
this.takenBefore, this.takenBefore,
this.takenAfter, this.takenAfter,
}); });
@@ -172,10 +175,11 @@ class SearchDateFilter {
} }
class SearchDisplayFilters { class SearchDisplayFilters {
bool isNotInAlbum = false; final bool isNotInAlbum;
bool isArchive = false; final bool isArchive;
bool isFavorite = false; final bool isFavorite;
SearchDisplayFilters({
const SearchDisplayFilters({
required this.isNotInAlbum, required this.isNotInAlbum,
required this.isArchive, required this.isArchive,
required this.isFavorite, required this.isFavorite,
@@ -233,19 +237,19 @@ class SearchDisplayFilters {
} }
class SearchFilter { class SearchFilter {
String? context; final String? context;
String? filename; final String? filename;
String? description; final String? description;
Set<Person> people; final Set<Person> people;
SearchLocationFilter location; final SearchLocationFilter location;
SearchCameraFilter camera; final SearchCameraFilter camera;
SearchDateFilter date; final SearchDateFilter date;
SearchDisplayFilters display; final SearchDisplayFilters display;
// Enum // Enum
AssetType mediaType; final AssetType mediaType;
SearchFilter({ const SearchFilter({
this.context, this.context,
this.filename, this.filename,
this.description, this.description,

View File

@@ -6,7 +6,7 @@ class SearchResult {
final List<Asset> assets; final List<Asset> assets;
final int? nextPage; final int? nextPage;
SearchResult({ const SearchResult({
required this.assets, required this.assets,
this.nextPage, this.nextPage,
}); });

View File

@@ -1,5 +1,6 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.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';
@@ -61,6 +62,37 @@ class MemoryPage extends HookConsumerWidget {
); );
} }
void toPreviousMemory() {
if (currentMemoryIndex.value > 0) {
// Move to the previous memory page
memoryPageController.previousPage(
duration: const Duration(milliseconds: 500),
curve: Curves.easeIn,
);
// Wait for the next frame to ensure the page is built
SchedulerBinding.instance.addPostFrameCallback((_) {
final previousIndex = currentMemoryIndex.value - 1;
final previousMemoryController =
memoryAssetPageControllers[previousIndex];
// Ensure the controller is attached
if (previousMemoryController.hasClients) {
previousMemoryController
.jumpToPage(memories[previousIndex].assets.length - 1);
} else {
// Wait for the next frame until it is attached
SchedulerBinding.instance.addPostFrameCallback((_) {
if (previousMemoryController.hasClients) {
previousMemoryController
.jumpToPage(memories[previousIndex].assets.length - 1);
}
});
}
});
}
}
toNextAsset(int currentAssetIndex) { toNextAsset(int currentAssetIndex) {
if (currentAssetIndex + 1 < currentMemory.value.assets.length) { if (currentAssetIndex + 1 < currentMemory.value.assets.length) {
// Go to the next asset // Go to the next asset
@@ -77,6 +109,22 @@ class MemoryPage extends HookConsumerWidget {
} }
} }
toPreviousAsset(int currentAssetIndex) {
if (currentAssetIndex > 0) {
// Go to the previous asset
PageController controller =
memoryAssetPageControllers[currentMemoryIndex.value];
controller.previousPage(
curve: Curves.easeInOut,
duration: const Duration(milliseconds: 500),
);
} else {
// Go to the previous memory since we are at the end of our assets
toPreviousMemory();
}
}
updateProgressText() { updateProgressText() {
assetProgress.value = assetProgress.value =
"${currentAssetPage.value + 1}|${currentMemory.value.assets.length}"; "${currentAssetPage.value + 1}|${currentMemory.value.assets.length}";
@@ -141,17 +189,17 @@ class MemoryPage extends HookConsumerWidget {
currentAssetPage.value = otherIndex; currentAssetPage.value = otherIndex;
updateProgressText(); updateProgressText();
// Wait for page change animation to finish
await Future.delayed(const Duration(milliseconds: 400));
// And then precache the next asset
await precacheAsset(otherIndex + 1);
final asset = currentMemory.value.assets[otherIndex]; final asset = currentMemory.value.assets[otherIndex];
currentAsset.value = asset; currentAsset.value = asset;
ref.read(currentAssetProvider.notifier).set(asset); ref.read(currentAssetProvider.notifier).set(asset);
if (asset.isVideo || asset.isMotionPhoto) { if (asset.isVideo || asset.isMotionPhoto) {
ref.read(videoPlaybackValueProvider.notifier).reset(); ref.read(videoPlaybackValueProvider.notifier).reset();
} }
// Wait for page change animation to finish
await Future.delayed(const Duration(milliseconds: 400));
// And then precache the next asset
await precacheAsset(otherIndex + 1);
} }
/* Notification listener is used instead of OnPageChanged callback since OnPageChanged is called /* Notification listener is used instead of OnPageChanged callback since OnPageChanged is called
@@ -248,19 +296,42 @@ class MemoryPage extends HookConsumerWidget {
itemCount: memories[mIndex].assets.length, itemCount: memories[mIndex].assets.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final asset = memories[mIndex].assets[index]; final asset = memories[mIndex].assets[index];
return GestureDetector( return Stack(
behavior: HitTestBehavior.translucent, children: [
onTap: () { Container(
toNextAsset(index); color: Colors.black,
}, child: MemoryCard(
child: Container( asset: asset,
color: Colors.black, title: memories[mIndex].title,
child: MemoryCard( showTitle: index == 0,
asset: asset, ),
title: memories[mIndex].title,
showTitle: index == 0,
), ),
), Positioned.fill(
child: Row(
children: [
// Left side of the screen
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
toPreviousAsset(index);
},
),
),
// Right side of the screen
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
toNextAsset(index);
},
),
),
],
),
),
],
); );
}, },
), ),

View File

@@ -6,520 +6,49 @@ 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/constants/enums.dart'; import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/pages/search/search_body.dart';
import 'package:immich_mobile/providers/search/paginated_search.provider.dart'; import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
import 'package:immich_mobile/providers/search/search_filters.provider.dart';
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'; import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
import 'package:immich_mobile/widgets/search/search_filter/camera_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/display_option_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart';
import 'package:immich_mobile/widgets/search/search_filter/location_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/media_type_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/people_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.dart';
@RoutePage() @RoutePage()
class SearchPage extends HookConsumerWidget { class SearchPage extends HookConsumerWidget {
const SearchPage({super.key, this.prefilter});
final SearchFilter? prefilter; final SearchFilter? prefilter;
const SearchPage({super.key, this.prefilter});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final textSearchType = useState<TextSearchType>(TextSearchType.context); final textSearchType = useState<TextSearchType>(TextSearchType.context);
final searchHintText = useState<String>('contextual_search'.tr()); final searchHintText = useState<String>('contextual_search'.tr());
final textSearchController = useTextEditingController(); final textSearchController = useTextEditingController();
final filter = useState<SearchFilter>(
SearchFilter(
people: prefilter?.people ?? {},
location: prefilter?.location ?? SearchLocationFilter(),
camera: prefilter?.camera ?? SearchCameraFilter(),
date: prefilter?.date ?? SearchDateFilter(),
display: prefilter?.display ??
SearchDisplayFilters(
isNotInAlbum: false,
isArchive: false,
isFavorite: false,
),
mediaType: prefilter?.mediaType ?? AssetType.other,
),
);
final previousFilter = useState<SearchFilter?>(null);
final peopleCurrentFilterWidget = useState<Widget?>(null);
final dateRangeCurrentFilterWidget = useState<Widget?>(null);
final cameraCurrentFilterWidget = useState<Widget?>(null);
final locationCurrentFilterWidget = useState<Widget?>(null);
final mediaTypeCurrentFilterWidget = useState<Widget?>(null);
final displayOptionCurrentFilterWidget = useState<Widget?>(null);
final isSearching = useState(false);
SnackBar searchInfoSnackBar(String message) {
return SnackBar(
content: Text(
message,
style: context.textTheme.labelLarge,
),
showCloseIcon: true,
behavior: SnackBarBehavior.fixed,
closeIconColor: context.colorScheme.onSurface,
);
}
search() async {
if (filter.value.isEmpty) {
return;
}
if (prefilter == null && filter.value == previousFilter.value) {
return;
}
isSearching.value = true;
ref.watch(paginatedSearchProvider.notifier).clear();
final hasResult = await ref
.watch(paginatedSearchProvider.notifier)
.search(filter.value);
if (!hasResult) {
context.showSnackBar(
searchInfoSnackBar('search_no_result'.tr()),
);
}
previousFilter.value = filter.value;
isSearching.value = false;
}
loadMoreSearchResult() async {
isSearching.value = true;
final hasResult = await ref
.watch(paginatedSearchProvider.notifier)
.search(filter.value);
if (!hasResult) {
context.showSnackBar(
searchInfoSnackBar('search_no_more_result'.tr()),
);
}
isSearching.value = false;
}
searchPrefilter() {
if (prefilter != null) {
Future.delayed(
Duration.zero,
() {
search();
if (prefilter!.location.city != null) {
locationCurrentFilterWidget.value = Text(
prefilter!.location.city!,
style: context.textTheme.labelLarge,
);
}
},
);
}
}
useEffect(
() {
Future.microtask(
() => ref.invalidate(paginatedSearchProvider),
);
searchPrefilter();
return null;
},
[],
);
showPeoplePicker() {
handleOnSelect(Set<Person> value) {
filter.value = filter.value.copyWith(
people: value,
);
peopleCurrentFilterWidget.value = Text(
value.map((e) => e.name != '' ? e.name : 'no_name'.tr()).join(', '),
style: context.textTheme.labelLarge,
);
}
handleClear() {
filter.value = filter.value.copyWith(
people: {},
);
peopleCurrentFilterWidget.value = null;
search();
}
showFilterBottomSheet(
context: context,
isScrollControlled: true,
child: FractionallySizedBox(
heightFactor: 0.8,
child: FilterBottomSheetScaffold(
title: 'search_filter_people_title'.tr(),
expanded: true,
onSearch: search,
onClear: handleClear,
child: PeoplePicker(
onSelect: handleOnSelect,
filter: filter.value.people,
),
),
),
);
}
showLocationPicker() {
handleOnSelect(Map<String, String?> value) {
filter.value = filter.value.copyWith(
location: SearchLocationFilter(
country: value['country'],
city: value['city'],
state: value['state'],
),
);
final locationText = <String>[];
if (value['country'] != null) {
locationText.add(value['country']!);
}
if (value['state'] != null) {
locationText.add(value['state']!);
}
if (value['city'] != null) {
locationText.add(value['city']!);
}
locationCurrentFilterWidget.value = Text(
locationText.join(', '),
style: context.textTheme.labelLarge,
);
}
handleClear() {
filter.value = filter.value.copyWith(
location: SearchLocationFilter(),
);
locationCurrentFilterWidget.value = null;
search();
}
showFilterBottomSheet(
context: context,
isScrollControlled: true,
isDismissible: true,
child: FilterBottomSheetScaffold(
title: 'search_filter_location_title'.tr(),
onSearch: search,
onClear: handleClear,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Container(
padding: EdgeInsets.only(
bottom: context.viewInsets.bottom,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: LocationPicker(
onSelected: handleOnSelect,
filter: filter.value.location,
),
),
),
),
),
);
}
showCameraPicker() {
handleOnSelect(Map<String, String?> value) {
filter.value = filter.value.copyWith(
camera: SearchCameraFilter(
make: value['make'],
model: value['model'],
),
);
cameraCurrentFilterWidget.value = Text(
'${value['make'] ?? ''} ${value['model'] ?? ''}',
style: context.textTheme.labelLarge,
);
}
handleClear() {
filter.value = filter.value.copyWith(
camera: SearchCameraFilter(),
);
cameraCurrentFilterWidget.value = null;
search();
}
showFilterBottomSheet(
context: context,
isScrollControlled: true,
isDismissible: true,
child: FilterBottomSheetScaffold(
title: 'search_filter_camera_title'.tr(),
onSearch: search,
onClear: handleClear,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: CameraPicker(
onSelect: handleOnSelect,
filter: filter.value.camera,
),
),
),
);
}
showDatePicker() async {
final firstDate = DateTime(1900);
final lastDate = DateTime.now();
final date = await showDateRangePicker(
context: context,
firstDate: firstDate,
lastDate: lastDate,
currentDate: DateTime.now(),
initialDateRange: DateTimeRange(
start: filter.value.date.takenAfter ?? lastDate,
end: filter.value.date.takenBefore ?? lastDate,
),
helpText: 'search_filter_date_title'.tr(),
cancelText: 'action_common_cancel'.tr(),
confirmText: 'action_common_select'.tr(),
saveText: 'action_common_save'.tr(),
errorFormatText: 'invalid_date_format'.tr(),
errorInvalidText: 'invalid_date'.tr(),
fieldStartHintText: 'start_date'.tr(),
fieldEndHintText: 'end_date'.tr(),
initialEntryMode: DatePickerEntryMode.calendar,
keyboardType: TextInputType.text,
);
if (date == null) {
filter.value = filter.value.copyWith(
date: SearchDateFilter(),
);
dateRangeCurrentFilterWidget.value = null;
search();
return;
}
filter.value = filter.value.copyWith(
date: SearchDateFilter(
takenAfter: date.start,
takenBefore: date.end.add(
const Duration(
hours: 23,
minutes: 59,
seconds: 59,
),
),
),
);
// If date range is less than 24 hours, set the end date to the end of the day
if (date.end.difference(date.start).inHours < 24) {
dateRangeCurrentFilterWidget.value = Text(
DateFormat.yMMMd().format(date.start.toLocal()),
style: context.textTheme.labelLarge,
);
} else {
dateRangeCurrentFilterWidget.value = Text(
'search_filter_date_interval'.tr(
namedArgs: {
"start": DateFormat.yMMMd().format(date.start.toLocal()),
"end": DateFormat.yMMMd().format(date.end.toLocal()),
},
),
style: context.textTheme.labelLarge,
);
}
search();
}
// MEDIA PICKER
showMediaTypePicker() {
handleOnSelected(AssetType assetType) {
filter.value = filter.value.copyWith(
mediaType: assetType,
);
mediaTypeCurrentFilterWidget.value = Text(
assetType == AssetType.image
? 'search_filter_media_type_image'.tr()
: assetType == AssetType.video
? 'search_filter_media_type_video'.tr()
: 'search_filter_media_type_all'.tr(),
style: context.textTheme.labelLarge,
);
}
handleClear() {
filter.value = filter.value.copyWith(
mediaType: AssetType.other,
);
mediaTypeCurrentFilterWidget.value = null;
search();
}
showFilterBottomSheet(
context: context,
child: FilterBottomSheetScaffold(
title: 'search_filter_media_type_title'.tr(),
onSearch: search,
onClear: handleClear,
child: MediaTypePicker(
onSelect: handleOnSelected,
filter: filter.value.mediaType,
),
),
);
}
// DISPLAY OPTION
showDisplayOptionPicker() {
handleOnSelect(Map<DisplayOption, bool> value) {
final filterText = <String>[];
value.forEach((key, value) {
switch (key) {
case DisplayOption.notInAlbum:
filter.value = filter.value.copyWith(
display: filter.value.display.copyWith(
isNotInAlbum: value,
),
);
if (value) {
filterText
.add('search_filter_display_option_not_in_album'.tr());
}
break;
case DisplayOption.archive:
filter.value = filter.value.copyWith(
display: filter.value.display.copyWith(
isArchive: value,
),
);
if (value) {
filterText.add('search_filter_display_option_archive'.tr());
}
break;
case DisplayOption.favorite:
filter.value = filter.value.copyWith(
display: filter.value.display.copyWith(
isFavorite: value,
),
);
if (value) {
filterText.add('search_filter_display_option_favorite'.tr());
}
break;
}
});
if (filterText.isEmpty) {
displayOptionCurrentFilterWidget.value = null;
return;
}
displayOptionCurrentFilterWidget.value = Text(
filterText.join(', '),
style: context.textTheme.labelLarge,
);
}
handleClear() {
filter.value = filter.value.copyWith(
display: SearchDisplayFilters(
isNotInAlbum: false,
isArchive: false,
isFavorite: false,
),
);
displayOptionCurrentFilterWidget.value = null;
search();
}
showFilterBottomSheet(
context: context,
child: FilterBottomSheetScaffold(
title: 'search_filter_display_options_title'.tr(),
onSearch: search,
onClear: handleClear,
child: DisplayOptionPicker(
onSelect: handleOnSelect,
filter: filter.value.display,
),
),
);
}
handleTextSubmitted(String value) { handleTextSubmitted(String value) {
switch (textSearchType.value) { final filter = ref.read(searchFiltersProvider);
case TextSearchType.context: ref.read(searchFiltersProvider.notifier).value =
filter.value = filter.value.copyWith( switch (textSearchType.value) {
TextSearchType.context => filter.copyWith(
filename: '', filename: '',
context: value, context: value,
description: '', description: '',
); ),
TextSearchType.filename => filter.copyWith(
break;
case TextSearchType.filename:
filter.value = filter.value.copyWith(
filename: value, filename: value,
context: '', context: '',
description: '', description: '',
); ),
TextSearchType.description => filter.copyWith(
break;
case TextSearchType.description:
filter.value = filter.value.copyWith(
filename: '', filename: '',
context: '', context: '',
description: value, description: value,
); ),
break; };
} ref.read(searchFiltersProvider.notifier).search();
search();
}
IconData getSearchPrefixIcon() {
switch (textSearchType.value) {
case TextSearchType.context:
return Icons.image_search_rounded;
case TextSearchType.filename:
return Icons.abc_rounded;
case TextSearchType.description:
return Icons.text_snippet_outlined;
default:
return Icons.search_rounded;
}
} }
return Scaffold( return Scaffold(
@@ -530,16 +59,14 @@ class SearchPage extends HookConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.only(right: 16.0), padding: const EdgeInsets.only(right: 16.0),
child: MenuAnchor( child: MenuAnchor(
style: MenuStyle( style: const MenuStyle(
elevation: const WidgetStatePropertyAll(1), elevation: WidgetStatePropertyAll(1),
shape: WidgetStateProperty.all( shape: WidgetStatePropertyAll(
RoundedRectangleBorder( RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.all(Radius.circular(24)),
), ),
), ),
padding: const WidgetStatePropertyAll( padding: WidgetStatePropertyAll(EdgeInsets.all(4)),
EdgeInsets.all(4),
),
), ),
builder: ( builder: (
BuildContext context, BuildContext context,
@@ -625,13 +152,13 @@ class SearchPage extends HookConsumerWidget {
), ),
), ),
], ],
title: Container( title: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: context.colorScheme.onSurface.withAlpha(0), color: context.colorScheme.onSurface.withAlpha(0),
width: 0, width: 0,
), ),
borderRadius: BorderRadius.circular(24), borderRadius: const BorderRadius.all(Radius.circular(24)),
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
context.colorScheme.primary.withOpacity(0.075), context.colorScheme.primary.withOpacity(0.075),
@@ -652,7 +179,7 @@ class SearchPage extends HookConsumerWidget {
prefixIcon: prefilter != null prefixIcon: prefilter != null
? null ? null
: Icon( : Icon(
getSearchPrefixIcon(), getSearchPrefixIcon(textSearchType.value),
color: context.colorScheme.primary, color: context.colorScheme.primary,
), ),
hintText: searchHintText.value, hintText: searchHintText.value,
@@ -660,25 +187,20 @@ class SearchPage extends HookConsumerWidget {
color: context.themeData.colorScheme.onSurfaceSecondary, color: context.themeData.colorScheme.onSurfaceSecondary,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25), borderRadius: const BorderRadius.all(Radius.circular(25)),
borderSide: BorderSide( borderSide: BorderSide(color: context.colorScheme.surfaceDim),
color: context.colorScheme.surfaceDim,
),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25), borderRadius: const BorderRadius.all(Radius.circular(25)),
borderSide: BorderSide( borderSide:
color: context.colorScheme.surfaceContainer, BorderSide(color: context.colorScheme.surfaceContainer),
),
), ),
disabledBorder: OutlineInputBorder( disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25), borderRadius: const BorderRadius.all(Radius.circular(25)),
borderSide: BorderSide( borderSide: BorderSide(color: context.colorScheme.surfaceDim),
color: context.colorScheme.surfaceDim,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25), borderRadius: const BorderRadius.all(Radius.circular(25)),
borderSide: BorderSide( borderSide: BorderSide(
color: context.colorScheme.primary.withAlpha(100), color: context.colorScheme.primary.withAlpha(100),
), ),
@@ -690,72 +212,35 @@ class SearchPage extends HookConsumerWidget {
), ),
), ),
), ),
body: Column( body: const SearchBody(),
children: [
Padding(
padding: const EdgeInsets.only(top: 12.0),
child: SizedBox(
height: 50,
child: ListView(
key: const Key('search_filter_chip_list'),
shrinkWrap: true,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
children: [
SearchFilterChip(
icon: Icons.people_alt_rounded,
onTap: showPeoplePicker,
label: 'search_filter_people'.tr(),
currentFilter: peopleCurrentFilterWidget.value,
),
SearchFilterChip(
icon: Icons.location_pin,
onTap: showLocationPicker,
label: 'search_filter_location'.tr(),
currentFilter: locationCurrentFilterWidget.value,
),
SearchFilterChip(
icon: Icons.camera_alt_rounded,
onTap: showCameraPicker,
label: 'search_filter_camera'.tr(),
currentFilter: cameraCurrentFilterWidget.value,
),
SearchFilterChip(
icon: Icons.date_range_rounded,
onTap: showDatePicker,
label: 'search_filter_date'.tr(),
currentFilter: dateRangeCurrentFilterWidget.value,
),
SearchFilterChip(
key: const Key('media_type_chip'),
icon: Icons.video_collection_outlined,
onTap: showMediaTypePicker,
label: 'search_filter_media_type'.tr(),
currentFilter: mediaTypeCurrentFilterWidget.value,
),
SearchFilterChip(
icon: Icons.display_settings_outlined,
onTap: showDisplayOptionPicker,
label: 'search_filter_display_options'.tr(),
currentFilter: displayOptionCurrentFilterWidget.value,
),
],
),
),
),
if (isSearching.value)
const Expanded(
child: Center(child: CircularProgressIndicator.adaptive()),
)
else
SearchResultGrid(
onScrollEnd: loadMoreSearchResult,
isSearching: isSearching.value,
),
],
),
); );
} }
SnackBar searchInfoSnackBar(
String message,
TextStyle? textStyle,
Color closeIconColor,
) {
return SnackBar(
content: Text(message, style: textStyle),
showCloseIcon: true,
behavior: SnackBarBehavior.fixed,
closeIconColor: closeIconColor,
);
}
IconData getSearchPrefixIcon(TextSearchType textSearchType) {
switch (textSearchType) {
case TextSearchType.context:
return Icons.image_search_rounded;
case TextSearchType.filename:
return Icons.abc_rounded;
case TextSearchType.description:
return Icons.text_snippet_outlined;
default:
return Icons.search_rounded;
}
}
} }
class SearchResultGrid extends StatelessWidget { class SearchResultGrid extends StatelessWidget {
@@ -798,12 +283,15 @@ class SearchResultGrid extends StatelessWidget {
editEnabled: true, editEnabled: true,
favoriteEnabled: true, favoriteEnabled: true,
stackEnabled: false, stackEnabled: false,
emptyIndicator: Padding( emptyIndicator: isSearching
padding: const EdgeInsets.symmetric(horizontal: 16.0), ? const Padding(
child: !isSearching padding: EdgeInsets.symmetric(horizontal: 16.0),
? const SearchEmptyContent() child: SizedBox.shrink(),
: const SizedBox.shrink(), )
), : const Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: SearchEmptyContent(),
),
), ),
), ),
), ),
@@ -822,14 +310,19 @@ class SearchEmptyContent extends StatelessWidget {
shrinkWrap: false, shrinkWrap: false,
children: [ children: [
const SizedBox(height: 40), const SizedBox(height: 40),
Center( context.isDarkTheme
child: Image.asset( ? const Center(
context.isDarkTheme child: Image(
? 'assets/polaroid-dark.png' image: AssetImage('assets/polaroid-dark.png'),
: 'assets/polaroid-light.png', height: 125,
height: 125, ),
), )
), : const Center(
child: Image(
image: AssetImage('assets/polaroid-light.png'),
height: 125,
),
),
const SizedBox(height: 16), const SizedBox(height: 16),
Center( Center(
child: Text( child: Text(
@@ -850,9 +343,9 @@ class QuickLinkList extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20), borderRadius: const BorderRadius.all(Radius.circular(20)),
border: Border.all( border: Border.all(
color: context.colorScheme.outline.withAlpha(10), color: context.colorScheme.outline.withAlpha(10),
width: 1, width: 1,
@@ -912,21 +405,34 @@ class QuickLink extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final borderRadius = BorderRadius.only( final shape = switch ((isTop, isBottom)) {
topLeft: Radius.circular(isTop ? 20 : 0), (true, false) => const RoundedRectangleBorder(
topRight: Radius.circular(isTop ? 20 : 0), borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(isBottom ? 20 : 0), topLeft: Radius.circular(20),
bottomRight: Radius.circular(isBottom ? 20 : 0), topRight: Radius.circular(20),
); ),
),
(false, true) => const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
(true, true) => const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
(false, false) =>
const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
};
return ListTile( return ListTile(
shape: RoundedRectangleBorder( shape: shape,
borderRadius: borderRadius, leading: Icon(icon, size: 26),
),
leading: Icon(
icon,
size: 26,
),
title: Text( title: Text(
title, title,
style: context.textTheme.titleSmall?.copyWith( style: context.textTheme.titleSmall?.copyWith(

View File

@@ -0,0 +1,306 @@
import 'dart:async';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/pages/search/show_camera_picker.dart';
import 'package:immich_mobile/pages/search/show_date_picker.dart';
import 'package:immich_mobile/pages/search/show_display_option_picker.dart';
import 'package:immich_mobile/pages/search/show_location_picker.dart';
import 'package:immich_mobile/pages/search/show_media_type_picker.dart';
import 'package:immich_mobile/pages/search/show_people_picker.dart';
import 'package:immich_mobile/providers/search/is_searching.provider.dart';
import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
import 'package:immich_mobile/providers/search/search_filters.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
class SearchBody extends HookConsumerWidget {
const SearchBody({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isSearching = ref.watch(isSearchingProvider);
loadMoreSearchResult() async {
final filter = ref.read(searchFiltersProvider);
final hasResult =
await ref.read(paginatedSearchProvider.notifier).search(filter);
if (!hasResult) {
context.showSnackBar(
searchInfoSnackBar(
'search_no_more_result'.tr(),
context.textTheme.labelLarge,
context.colorScheme.onSurface,
),
);
}
}
const pickers = Padding(
padding: EdgeInsets.only(top: 12.0),
child: SizedBox(
height: 50,
child: ListView.custom(
key: Key('search_filter_chip_list'),
shrinkWrap: true,
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: 16),
childrenDelegate: SliverChildListDelegate.fixed(
[
ShowPeoplePicker(),
ShowLocationPicker(),
ShowCameraPicker(),
ShowDatePicker(),
ShowMediaTypePicker(),
ShowDisplayOptionsPicker(),
],
addAutomaticKeepAlives: true,
addRepaintBoundaries: true,
addSemanticIndexes: true,
),
),
),
);
// TODO: extend render list without discarding the existing result grid
return isSearching
? const Column(
children: [
pickers,
Expanded(
child: Center(child: CircularProgressIndicator.adaptive()),
),
],
)
: Column(
children: [
pickers,
SearchResultGrid(onScrollEnd: loadMoreSearchResult),
],
);
}
SnackBar searchInfoSnackBar(
String message,
TextStyle? textStyle,
Color closeIconColor,
) {
return SnackBar(
content: Text(message, style: textStyle),
showCloseIcon: true,
behavior: SnackBarBehavior.fixed,
closeIconColor: closeIconColor,
);
}
IconData getSearchPrefixIcon(TextSearchType textSearchType) {
switch (textSearchType) {
case TextSearchType.context:
return Icons.image_search_rounded;
case TextSearchType.filename:
return Icons.abc_rounded;
case TextSearchType.description:
return Icons.text_snippet_outlined;
default:
return Icons.search_rounded;
}
}
}
class SearchResultGrid extends StatelessWidget {
final VoidCallback onScrollEnd;
const SearchResultGrid({
super.key,
required this.onScrollEnd,
});
@override
Widget build(BuildContext context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: NotificationListener<ScrollEndNotification>(
onNotification: (notification) {
final isBottomSheetNotification = notification.context
?.findAncestorWidgetOfExactType<
DraggableScrollableSheet>() !=
null;
final metrics = notification.metrics;
final isVerticalScroll = metrics.axis == Axis.vertical;
if (metrics.pixels >= metrics.maxScrollExtent &&
isVerticalScroll &&
!isBottomSheetNotification) {
onScrollEnd();
}
return true;
},
child: MultiselectGrid(
renderListProvider: paginatedSearchRenderListProvider,
archiveEnabled: true,
deleteEnabled: true,
editEnabled: true,
favoriteEnabled: true,
stackEnabled: false,
emptyIndicator: const Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: SearchEmptyContent(),
),
),
),
),
);
}
}
class SearchEmptyContent extends StatelessWidget {
const SearchEmptyContent({super.key});
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (_) => true,
child: ListView(
shrinkWrap: false,
children: [
const SizedBox(height: 40),
context.isDarkTheme
? const Center(
child: Image(
image: AssetImage('assets/polaroid-dark.png'),
height: 125,
),
)
: const Center(
child: Image(
image: AssetImage('assets/polaroid-light.png'),
height: 125,
),
),
const SizedBox(height: 16),
Center(
child: Text(
'search_page_search_photos_videos'.tr(),
style: context.textTheme.labelLarge,
),
),
const SizedBox(height: 32),
const QuickLinkList(),
],
),
);
}
}
class QuickLinkList extends StatelessWidget {
const QuickLinkList({super.key});
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(20)),
border: Border.all(
color: context.colorScheme.outline.withAlpha(10),
width: 1,
),
gradient: LinearGradient(
colors: [
context.colorScheme.primary.withAlpha(10),
context.colorScheme.primary.withAlpha(15),
context.colorScheme.primary.withAlpha(20),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: ListView(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: [
QuickLink(
title: 'recently_added'.tr(),
icon: const Icon(Icons.schedule_outlined, size: 26),
isTop: true,
onTap: () => context.pushRoute(const RecentlyAddedRoute()),
),
QuickLink(
title: 'videos'.tr(),
icon: const Icon(Icons.play_circle_outline_rounded, size: 26),
onTap: () => context.pushRoute(const AllVideosRoute()),
),
QuickLink(
title: 'favorites'.tr(),
icon: const Icon(Icons.favorite_border_rounded, size: 26),
isBottom: true,
onTap: () => context.pushRoute(const FavoritesRoute()),
),
],
),
);
}
}
class QuickLink extends StatelessWidget {
final String title;
final Icon icon;
final VoidCallback onTap;
final bool isTop;
final bool isBottom;
const QuickLink({
super.key,
required this.title,
required this.icon,
required this.onTap,
this.isTop = false,
this.isBottom = false,
});
@override
Widget build(BuildContext context) {
final shape = switch ((isTop, isBottom)) {
(true, false) => const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
(false, true) => const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
(true, true) => const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
(false, false) =>
const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
};
return ListTile(
shape: shape,
leading: icon,
title: Text(
title,
style:
context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500),
),
onTap: onTap,
);
}
}

View File

@@ -0,0 +1,64 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/providers/search/is_searching.provider.dart';
import 'package:immich_mobile/providers/search/search_filters.provider.dart';
import 'package:immich_mobile/widgets/search/search_filter/camera_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.dart';
class ShowCameraPicker extends ConsumerWidget {
const ShowCameraPicker({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final filter = ref.watch(searchFiltersProvider);
showCameraPicker() {
handleOnSelect(Map<String, String?> value) {
ref.read(searchFiltersProvider.notifier).camera = SearchCameraFilter(
make: value['make'],
model: value['model'],
);
}
handleClear() {
ref.read(searchFiltersProvider.notifier).value = filter.copyWith(
camera: const SearchCameraFilter(),
);
ref.read(searchFiltersProvider.notifier).search();
}
showFilterBottomSheet(
context: context,
isScrollControlled: true,
isDismissible: true,
child: FilterBottomSheetScaffold(
title: 'search_filter_camera_title'.tr(),
onSearch: () => ref.read(isSearchingProvider.notifier).value = true,
onClear: handleClear,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: CameraPicker(
onSelect: handleOnSelect,
filter: filter.camera,
),
),
),
);
}
return SearchFilterChip(
icon: Icons.camera_alt_rounded,
onTap: showCameraPicker,
label: 'search_filter_camera'.tr(),
currentFilter: Text(
'${filter.camera.make ?? ''} ${filter.camera.model ?? ''}',
style: context.textTheme.labelLarge,
),
);
}
}

View File

@@ -0,0 +1,88 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/providers/search/search_filters.provider.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart';
class ShowDatePicker extends ConsumerWidget {
const ShowDatePicker({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final date =
ref.watch(searchFiltersProvider.select((filters) => filters.date));
showDatePicker() async {
final firstDate = DateTime(1900);
final lastDate = DateTime.now();
final dateRange = await showDateRangePicker(
context: context,
firstDate: firstDate,
lastDate: lastDate,
currentDate: DateTime.now(),
initialDateRange: DateTimeRange(
start: date.takenAfter ?? lastDate,
end: date.takenBefore ?? lastDate,
),
helpText: 'search_filter_date_title'.tr(),
cancelText: 'action_common_cancel'.tr(),
confirmText: 'action_common_select'.tr(),
saveText: 'action_common_save'.tr(),
errorFormatText: 'invalid_date_format'.tr(),
errorInvalidText: 'invalid_date'.tr(),
fieldStartHintText: 'start_date'.tr(),
fieldEndHintText: 'end_date'.tr(),
initialEntryMode: DatePickerEntryMode.calendar,
keyboardType: TextInputType.text,
);
if (dateRange == null) {
ref.read(searchFiltersProvider.notifier).date =
const SearchDateFilter();
return;
}
ref.read(searchFiltersProvider.notifier).date = SearchDateFilter(
takenAfter: dateRange.start,
takenBefore: dateRange.end.add(
const Duration(
hours: 23,
minutes: 59,
seconds: 59,
),
),
);
ref.read(searchFiltersProvider.notifier).search();
}
return SearchFilterChip(
icon: Icons.date_range_rounded,
onTap: showDatePicker,
label: 'search_filter_date'.tr(),
currentFilter: Text(
getFormattedText(date.takenAfter, date.takenBefore),
style: context.textTheme.labelLarge,
),
);
}
String getFormattedText(DateTime? start, DateTime? end) {
if (start == null || end == null) {
return '';
}
if (end.difference(start).inHours < 24) {
return DateFormat.yMMMd().format(start.toLocal());
}
return 'search_filter_date_interval'.tr(
namedArgs: {
"start": DateFormat.yMMMd().format(start.toLocal()),
"end": DateFormat.yMMMd().format(end.toLocal()),
},
);
}
}

View File

@@ -0,0 +1,75 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/providers/search/is_searching.provider.dart';
import 'package:immich_mobile/providers/search/search_filters.provider.dart';
import 'package:immich_mobile/widgets/search/search_filter/display_option_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.dart';
class ShowDisplayOptionsPicker extends ConsumerWidget {
const ShowDisplayOptionsPicker({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final display =
ref.watch(searchFiltersProvider.select((filters) => filters.display));
showDisplayOptionPicker() {
handleOnSelect(Map<DisplayOption, bool> value) {
value.forEach((key, value) {
ref.read(searchFiltersProvider.notifier).display = switch (key) {
DisplayOption.notInAlbum => display.copyWith(isNotInAlbum: value),
DisplayOption.archive => display.copyWith(isArchive: value),
DisplayOption.favorite => display.copyWith(isFavorite: value),
};
});
}
handleClear() {
ref.read(searchFiltersProvider.notifier).display =
const SearchDisplayFilters(
isNotInAlbum: false,
isArchive: false,
isFavorite: false,
);
ref.read(searchFiltersProvider.notifier).search();
}
showFilterBottomSheet(
context: context,
child: FilterBottomSheetScaffold(
title: 'search_filter_display_options_title'.tr(),
onSearch: () => ref.read(isSearchingProvider.notifier).value = true,
onClear: handleClear,
child: DisplayOptionPicker(
onSelect: handleOnSelect,
filter: display,
),
),
);
}
return SearchFilterChip(
icon: Icons.display_settings_outlined,
onTap: showDisplayOptionPicker,
label: 'search_filter_display_options'.tr(),
currentFilter: Text(
getFormattedText(display),
style: context.textTheme.labelLarge,
),
);
}
getFormattedText(SearchDisplayFilters display) {
return [
if (display.isNotInAlbum)
'search_filter_display_option_not_in_album'.tr(),
if (display.isArchive) 'search_filter_display_option_archive'.tr(),
if (display.isFavorite) 'search_filter_display_option_favorite'.tr(),
].join(', ');
}
}

View File

@@ -0,0 +1,82 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/providers/search/is_searching.provider.dart';
import 'package:immich_mobile/providers/search/search_filters.provider.dart';
import 'package:immich_mobile/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart';
import 'package:immich_mobile/widgets/search/search_filter/location_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.dart';
class ShowLocationPicker extends ConsumerWidget {
const ShowLocationPicker({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final location =
ref.watch(searchFiltersProvider.select((filters) => filters.location));
showLocationPicker() {
handleOnSelect(Map<String, String?> value) {
ref.read(searchFiltersProvider.notifier).location =
SearchLocationFilter(
country: value['country'],
city: value['city'],
state: value['state'],
);
}
handleClear() {
ref.read(searchFiltersProvider.notifier).location =
const SearchLocationFilter();
ref.read(searchFiltersProvider.notifier).search();
}
showFilterBottomSheet(
context: context,
isScrollControlled: true,
isDismissible: true,
child: FilterBottomSheetScaffold(
title: 'search_filter_location_title'.tr(),
onSearch: () => ref.read(isSearchingProvider.notifier).value = true,
onClear: handleClear,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Container(
padding: EdgeInsets.only(
bottom: context.viewInsets.bottom,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: LocationPicker(
onSelected: handleOnSelect,
filter: location,
),
),
),
),
),
);
}
return SearchFilterChip(
icon: Icons.location_pin,
onTap: showLocationPicker,
label: 'search_filter_location'.tr(),
currentFilter: Text(
getFormattedText(location.city, location.state, location.country),
style: context.textTheme.labelLarge,
),
);
}
String getFormattedText(String? city, String? state, String? country) {
return [
if (city != null) city,
if (state != null) state,
if (country != null) country,
].join(', ');
}
}

View File

@@ -0,0 +1,66 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/search/is_searching.provider.dart';
import 'package:immich_mobile/providers/search/search_filters.provider.dart';
import 'package:immich_mobile/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart';
import 'package:immich_mobile/widgets/search/search_filter/media_type_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.dart';
class ShowMediaTypePicker extends ConsumerWidget {
const ShowMediaTypePicker({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final mediaType =
ref.watch(searchFiltersProvider.select((filters) => filters.mediaType));
showMediaTypePicker() {
handleOnSelected(AssetType assetType) {
ref.read(searchFiltersProvider.notifier).mediaType = assetType;
}
handleClear() {
ref.read(searchFiltersProvider.notifier).mediaType = AssetType.other;
ref.read(searchFiltersProvider.notifier).search();
}
showFilterBottomSheet(
context: context,
child: FilterBottomSheetScaffold(
title: 'search_filter_media_type_title'.tr(),
onSearch: () => ref.read(isSearchingProvider.notifier).value = true,
onClear: handleClear,
child: MediaTypePicker(
onSelect: handleOnSelected,
filter: mediaType,
),
),
);
}
return SearchFilterChip(
icon: Icons.video_collection_outlined,
onTap: showMediaTypePicker,
label: 'search_filter_media_type'.tr(),
currentFilter: Text(
getFormattedText(mediaType),
style: context.textTheme.labelLarge,
),
);
}
String getFormattedText(AssetType mediaType) {
switch (mediaType) {
case AssetType.image:
return 'search_filter_media_type_image'.tr();
case AssetType.video:
return 'search_filter_media_type_video'.tr();
default:
return 'search_filter_media_type_all'.tr();
}
}
}

View File

@@ -0,0 +1,65 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/providers/search/is_searching.provider.dart';
import 'package:immich_mobile/providers/search/search_filters.provider.dart';
import 'package:immich_mobile/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart';
import 'package:immich_mobile/widgets/search/search_filter/people_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.dart';
class ShowPeoplePicker extends ConsumerWidget {
const ShowPeoplePicker({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final people =
ref.watch(searchFiltersProvider.select((filters) => filters.people));
showPeoplePicker() {
handleOnSelect(Set<Person> value) {
ref.read(searchFiltersProvider.notifier).people = value;
}
handleClear() {
ref.read(searchFiltersProvider.notifier).people = const {};
ref.read(searchFiltersProvider.notifier).search();
}
showFilterBottomSheet(
context: context,
isScrollControlled: true,
child: FractionallySizedBox(
heightFactor: 0.8,
child: FilterBottomSheetScaffold(
title: 'search_filter_people_title'.tr(),
expanded: true,
onSearch: () => ref.read(isSearchingProvider.notifier).value = true,
onClear: handleClear,
child: PeoplePicker(
onSelect: handleOnSelect,
filter: people,
),
),
),
);
}
return SearchFilterChip(
icon: Icons.people_alt_rounded,
onTap: showPeoplePicker,
label: 'search_filter_people'.tr(),
currentFilter: Text(
getFormattedText(people),
style: context.textTheme.labelLarge,
),
);
}
String getFormattedText(Set<Person> people) {
final noName = 'no_name'.tr();
return people.map((e) => e.name != '' ? e.name : noName).join(', ');
}
}

View File

@@ -0,0 +1,22 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
/// Whether to display the video part of a motion photo
final isSearchingProvider = StateNotifierProvider<IsSearching, bool>((ref) {
return IsSearching(ref);
});
class IsSearching extends StateNotifier<bool> {
IsSearching(this.ref) : super(false);
final Ref ref;
bool get value => state;
set value(bool value) {
state = value;
}
void toggle() {
state = !state;
}
}

View File

@@ -1,5 +1,6 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/models/search/search_result.model.dart'; import 'package:immich_mobile/models/search/search_result.model.dart';
import 'package:immich_mobile/providers/search/is_searching.provider.dart';
import 'package:immich_mobile/services/timeline.service.dart'; import 'package:immich_mobile/services/timeline.service.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart';
@@ -10,36 +11,41 @@ part 'paginated_search.provider.g.dart';
final paginatedSearchProvider = final paginatedSearchProvider =
StateNotifierProvider<PaginatedSearchNotifier, SearchResult>( StateNotifierProvider<PaginatedSearchNotifier, SearchResult>(
(ref) => PaginatedSearchNotifier(ref.watch(searchServiceProvider)), (ref) => PaginatedSearchNotifier(ref),
); );
class PaginatedSearchNotifier extends StateNotifier<SearchResult> { class PaginatedSearchNotifier extends StateNotifier<SearchResult> {
final SearchService _searchService; final Ref ref;
PaginatedSearchNotifier(this._searchService) PaginatedSearchNotifier(this.ref)
: super(SearchResult(assets: [], nextPage: 1)); : super(const SearchResult(assets: [], nextPage: 1));
Future<bool> search(SearchFilter filter) async { Future<bool> search(SearchFilter filter) async {
if (state.nextPage == null) { if (state.nextPage == null) {
return false; return false;
} }
final result = await _searchService.search(filter, state.nextPage!); ref.read(isSearchingProvider.notifier).value = true;
try {
final result =
await ref.read(searchServiceProvider).search(filter, state.nextPage!);
if (result == null) {
return false;
}
if (result == null) { state = SearchResult(
return false; assets: [...state.assets, ...result.assets],
nextPage: result.nextPage,
);
} finally {
ref.read(isSearchingProvider.notifier).value = false;
} }
state = SearchResult(
assets: [...state.assets, ...result.assets],
nextPage: result.nextPage,
);
return true; return true;
} }
clear() { clear() {
state = SearchResult(assets: [], nextPage: 1); state = const SearchResult(assets: [], nextPage: 1);
} }
} }

View File

@@ -0,0 +1,81 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/providers/search/is_searching.provider.dart';
import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
final searchFiltersProvider =
StateNotifierProvider<SearchFilterNotifier, SearchFilter>((ref) {
return SearchFilterNotifier(ref);
});
const searchFiltersDefault = SearchFilter(
people: {},
location: SearchLocationFilter(),
camera: SearchCameraFilter(),
date: SearchDateFilter(),
display: SearchDisplayFilters(
isNotInAlbum: false,
isArchive: false,
isFavorite: false,
),
mediaType: AssetType.other,
);
class SearchFilterNotifier extends StateNotifier<SearchFilter> {
final Ref ref;
SearchFilterNotifier(this.ref) : super(searchFiltersDefault);
SearchFilter get value => state;
set value(SearchFilter value) {
state = value;
}
void reset() {
state = searchFiltersDefault;
}
Set<Person> get people => state.people;
SearchLocationFilter get location => state.location;
SearchCameraFilter get camera => state.camera;
SearchDateFilter get date => state.date;
SearchDisplayFilters get display => state.display;
AssetType get mediaType => state.mediaType;
set people(Set<Person> value) {
state = state.copyWith(people: value);
}
set location(SearchLocationFilter value) {
state = state.copyWith(location: value);
}
set camera(SearchCameraFilter value) {
state = state.copyWith(camera: value);
}
set date(SearchDateFilter value) {
state = state.copyWith(date: value);
}
set display(SearchDisplayFilters value) {
state = state.copyWith(display: value);
}
set mediaType(AssetType value) {
state = state.copyWith(mediaType: value);
}
Future<void> search() {
ref.read(paginatedSearchProvider.notifier).clear();
return ref.read(paginatedSearchProvider.notifier).search(state);
}
}

View File

@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.128.0 - API version: 1.129.0
- Generator version: 7.8.0 - Generator version: 7.8.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen - Build package: org.openapitools.codegen.languages.DartClientCodegen

View File

@@ -394,10 +394,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: easy_localization name: easy_localization
sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201 sha256: "0f5239c7b8ab06c66440cfb0e9aa4b4640429c6668d5a42fe389c5de42220b12"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.7" version: "3.0.7+1"
easy_logger: easy_logger:
dependency: transitive dependency: transitive
description: description:

View File

@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none' publish_to: 'none'
version: 1.128.0+186 version: 1.129.0+187
environment: environment:
sdk: '>=3.3.0 <4.0.0' sdk: '>=3.3.0 <4.0.0'
@@ -34,7 +34,7 @@ dependencies:
url_launcher: ^6.2.4 url_launcher: ^6.2.4
http: ^1.1.0 http: ^1.1.0
cancellation_token_http: ^2.0.0 cancellation_token_http: ^2.0.0
easy_localization: ^3.0.3 easy_localization: ^3.0.7+1
share_plus: ^10.0.0 share_plus: ^10.0.0
flutter_displaymode: ^0.6.0 flutter_displaymode: ^0.6.0
scrollable_positioned_list: ^0.3.8 scrollable_positioned_list: ^0.3.8

View File

@@ -7655,7 +7655,7 @@
"info": { "info": {
"title": "Immich", "title": "Immich",
"description": "Immich API", "description": "Immich API",
"version": "1.128.0", "version": "1.129.0",
"contact": {} "contact": {}
}, },
"tags": [], "tags": [],

View File

@@ -1,12 +1,12 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.128.0", "version": "1.129.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.128.0", "version": "1.129.0",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.128.0", "version": "1.129.0",
"description": "Auto-generated TypeScript SDK for the Immich API", "description": "Auto-generated TypeScript SDK for the Immich API",
"type": "module", "type": "module",
"main": "./build/index.js", "main": "./build/index.js",

View File

@@ -1,6 +1,6 @@
/** /**
* Immich * Immich
* 1.128.0 * 1.129.0
* DO NOT MODIFY - This file has been generated using oazapfts. * DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts * See https://www.npmjs.com/package/oazapfts
*/ */

View File

@@ -1,12 +1,12 @@
{ {
"name": "immich", "name": "immich",
"version": "1.128.0", "version": "1.129.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich", "name": "immich",
"version": "1.128.0", "version": "1.129.0",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@nestjs/bullmq": "^11.0.1", "@nestjs/bullmq": "^11.0.1",

View File

@@ -1,6 +1,6 @@
{ {
"name": "immich", "name": "immich",
"version": "1.128.0", "version": "1.129.0",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,

View File

@@ -10,7 +10,7 @@ export class MoveEntity {
@PrimaryGeneratedColumn('uuid') @PrimaryGeneratedColumn('uuid')
id!: string; id!: string;
@Column({ type: 'varchar' }) @Column({ type: 'uuid' })
entityId!: string; entityId!: string;
@Column({ type: 'varchar' }) @Column({ type: 'varchar' })

View File

@@ -0,0 +1,26 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class MoveHistoryUuidEntityId1741179334403 implements MigrationInterface {
name = 'MoveHistoryUuidEntityId1741179334403';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "move_history" ALTER COLUMN "entityId" TYPE uuid USING "entityId"::uuid;`);
await queryRunner.query(`delete from "move_history"
where
"move_history"."entityId" not in (
select
"id"
from
"assets"
where
"assets"."id" = "move_history"."entityId"
)
and "move_history"."pathType" = 'original'
`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "move_history" ALTER COLUMN "entityId" TYPE character varying`);
}
}

View File

@@ -15,3 +15,22 @@ where
"id" = $1 "id" = $1
returning returning
* *
-- MoveRepository.cleanMoveHistory
delete from "move_history"
where
"move_history"."entityId" not in (
select
"id"
from
"assets"
where
"assets"."id" = "move_history"."entityId"
)
and "move_history"."pathType" = 'original'
-- MoveRepository.cleanMoveHistorySingle
delete from "move_history"
where
"move_history"."pathType" = 'original'
and "entityId" = $1

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Insertable, Kysely, Updateable } from 'kysely'; import { Insertable, Kysely, sql, Updateable } from 'kysely';
import { InjectKysely } from 'nestjs-kysely'; import { InjectKysely } from 'nestjs-kysely';
import { DB, MoveHistory } from 'src/db'; import { DB, MoveHistory } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { MoveEntity } from 'src/entities/move.entity'; import { MoveEntity } from 'src/entities/move.entity';
import { PathType } from 'src/enum'; import { AssetPathType, PathType } from 'src/enum';
export type MoveCreate = Pick<MoveEntity, 'oldPath' | 'newPath' | 'entityId' | 'pathType'> & Partial<MoveEntity>; export type MoveCreate = Pick<MoveEntity, 'oldPath' | 'newPath' | 'entityId' | 'pathType'> & Partial<MoveEntity>;
@@ -47,4 +47,28 @@ export class MoveRepository {
.returningAll() .returningAll()
.executeTakeFirstOrThrow() as unknown as Promise<MoveEntity>; .executeTakeFirstOrThrow() as unknown as Promise<MoveEntity>;
} }
@GenerateSql()
async cleanMoveHistory(): Promise<void> {
await this.db
.deleteFrom('move_history')
.where((eb) =>
eb(
'move_history.entityId',
'not in',
eb.selectFrom('assets').select('id').whereRef('assets.id', '=', 'move_history.entityId'),
),
)
.where('move_history.pathType', '=', sql.lit(AssetPathType.ORIGINAL))
.execute();
}
@GenerateSql()
async cleanMoveHistorySingle(assetId: string): Promise<void> {
await this.db
.deleteFrom('move_history')
.where('move_history.pathType', '=', sql.lit(AssetPathType.ORIGINAL))
.where('entityId', '=', assetId)
.execute();
}
} }

View File

@@ -152,6 +152,7 @@ export class StorageTemplateService extends BaseService {
this.logger.log('Storage template migration disabled, skipping'); this.logger.log('Storage template migration disabled, skipping');
return JobStatus.SKIPPED; return JobStatus.SKIPPED;
} }
await this.moveRepository.cleanMoveHistory();
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) =>
this.assetRepository.getAll(pagination, { withExif: true, withArchived: true }), this.assetRepository.getAll(pagination, { withExif: true, withArchived: true }),
); );
@@ -175,6 +176,12 @@ export class StorageTemplateService extends BaseService {
return JobStatus.SUCCESS; return JobStatus.SUCCESS;
} }
@OnEvent({ name: 'asset.delete' })
async handleMoveHistoryCleanup({ assetId }: ArgOf<'asset.delete'>) {
this.logger.debug(`Cleaning up move history for asset ${assetId}`);
await this.moveRepository.cleanMoveHistorySingle(assetId);
}
async moveAsset(asset: AssetEntity, metadata: MoveAssetMetadata) { async moveAsset(asset: AssetEntity, metadata: MoveAssetMetadata) {
if (asset.isExternal || StorageCore.isAndroidMotionPath(asset.originalPath)) { if (asset.isExternal || StorageCore.isAndroidMotionPath(asset.originalPath)) {
// External assets are not affected by storage template // External assets are not affected by storage template

View File

@@ -8,5 +8,7 @@ export const newMoveRepositoryMock = (): Mocked<RepositoryInterface<MoveReposito
getByEntity: vitest.fn(), getByEntity: vitest.fn(),
update: vitest.fn(), update: vitest.fn(),
delete: vitest.fn(), delete: vitest.fn(),
cleanMoveHistory: vitest.fn(),
cleanMoveHistorySingle: vitest.fn(),
}; };
}; };

6
web/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "immich-web", "name": "immich-web",
"version": "1.128.0", "version": "1.129.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-web", "name": "immich-web",
"version": "1.128.0", "version": "1.129.0",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@formatjs/icu-messageformat-parser": "^2.9.8", "@formatjs/icu-messageformat-parser": "^2.9.8",
@@ -80,7 +80,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.128.0", "version": "1.129.0",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"

View File

@@ -1,6 +1,6 @@
{ {
"name": "immich-web", "name": "immich-web",
"version": "1.128.0", "version": "1.129.0",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"scripts": { "scripts": {
"dev": "vite dev --host 0.0.0.0 --port 3000", "dev": "vite dev --host 0.0.0.0 --port 3000",

View File

@@ -423,6 +423,12 @@ export class AssetStore {
this.setOptions(options); this.setOptions(options);
return; return;
} }
// Make sure to re-initialize if the tagId changes
if (this.options.tagId === options.tagId) {
this.setOptions(options);
return;
}
// TODO: don't call updateObjects frequently after // TODO: don't call updateObjects frequently after
// init - cancelation of the initialize tasks isn't // init - cancelation of the initialize tasks isn't
// performed right now, and will cause issues if // performed right now, and will cause issues if