Merge branch 'main' into new-asset-sync

This commit is contained in:
Alex 2025-03-10 12:04:01 -05:00 committed by GitHub
commit d3de6475bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
146 changed files with 6606 additions and 6759 deletions

View File

@ -32,5 +32,5 @@ The `/api/something` endpoint is now `/api/something-else`
- [ ] I have confirmed that any new dependencies are strictly necessary. - [ ] I have confirmed that any new dependencies are strictly necessary.
- [ ] I have written tests for new code (if applicable) - [ ] I have written tests for new code (if applicable)
- [ ] I have followed naming conventions/patterns in the surrounding code - [ ] I have followed naming conventions/patterns in the surrounding code
- [ ] All code in `src/services` uses repositories implementations for database calls, filesystem operations, etc. - [ ] All code in `src/services/` uses repositories implementations for database calls, filesystem operations, etc.
- [ ] All code in `src/repositories/` is pretty basic/simple and does not have any immich specific logic (that belongs in `src/services`) - [ ] All code in `src/repositories/` is pretty basic/simple and does not have any immich specific logic (that belongs in `src/services/`)

View File

@ -41,8 +41,8 @@ jobs:
with: with:
token: ${{ steps.generate-token.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
- name: Install Poetry - name: Install uv
run: pipx install poetry uses: astral-sh/setup-uv@v5
- name: Bump version - name: Bump version
run: misc/release/pump-version.sh -s "${{ inputs.serverBump }}" -m "${{ inputs.mobileBump }}" run: misc/release/pump-version.sh -s "${{ inputs.serverBump }}" -m "${{ inputs.mobileBump }}"
@ -74,7 +74,7 @@ jobs:
with: with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:

View File

@ -380,27 +380,28 @@ jobs:
working-directory: ./machine-learning working-directory: ./machine-learning
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install poetry - name: Install uv
run: pipx install poetry uses: astral-sh/setup-uv@v5
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: # TODO: add caching when supported (https://github.com/actions/setup-python/pull/818)
python-version: 3.11 # with:
cache: 'poetry' # python-version: 3.11
# cache: 'uv'
- name: Install dependencies - name: Install dependencies
run: | run: |
poetry install --with dev --with cpu uv sync --extra cpu
- name: Lint with ruff - name: Lint with ruff
run: | run: |
poetry run ruff check --output-format=github app export uv run ruff check --output-format=github app export
- name: Check black formatting - name: Check black formatting
run: | run: |
poetry run black --check app export uv run black --check app export
- name: Run mypy type checking - name: Run mypy type checking
run: | run: |
poetry run mypy --install-types --non-interactive --strict app/ uv run mypy --strict app/
- name: Run tests and coverage - name: Run tests and coverage
run: | run: |
poetry run pytest app --cov=app --cov-report term-missing uv run pytest app --cov=app --cov-report term-missing
shellcheck: shellcheck:
name: ShellCheck name: ShellCheck

View File

@ -1,11 +1,11 @@
<p align="center"> <p align="center">
<br/> <br/>
<a href="https://opensource.org/license/agpl-v3"><img src="https://img.shields.io/badge/License-AGPL_v3-blue.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="License: AGPLv3"></a> <a href="https://opensource.org/license/agpl-v3"><img src="https://img.shields.io/badge/License-AGPL_v3-blue.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="License: AGPLv3"></a>
<a href="https://discord.immich.app"> <a href="https://discord.immich.app">
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" alt="Discord"/> <img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" alt="Discord"/>
</a> </a>
<br/> <br/>
<br/> <br/>
</p> </p>
<p align="center"> <p align="center">
@ -63,7 +63,7 @@
Access the demo [here](https://demo.immich.app). The demo is running on a Free-tier Oracle VM in Amsterdam with a 2.4Ghz quad-core ARM64 CPU and 24GB RAM. Access the demo [here](https://demo.immich.app). The demo is running on a Free-tier Oracle VM in Amsterdam with a 2.4Ghz quad-core ARM64 CPU and 24GB RAM.
For the mobile app, you can use `https://demo.immich.app/api` for the `Server Endpoint URL` For the mobile app, you can use `https://demo.immich.app` for the `Server Endpoint URL`
### Login credentials ### Login credentials

View File

@ -25,7 +25,7 @@ services:
context: ../ context: ../
dockerfile: server/Dockerfile dockerfile: server/Dockerfile
target: dev target: dev
restart: always restart: unless-stopped
volumes: volumes:
- ../server:/usr/src/app - ../server:/usr/src/app
- ../open-api:/usr/src/open-api - ../open-api:/usr/src/open-api

View File

@ -117,7 +117,7 @@ See [Backup and Restore](/docs/administration/backup-and-restore.md).
### Does Immich support reading existing face tag metadata? ### Does Immich support reading existing face tag metadata?
No, it currently does not. There is an [open feature request on GitHub](https://github.com/immich-app/immich/discussions/4348). Yes, it creates new faces and persons from the imported asset metadata. For details see the [feature request #4348](https://github.com/immich-app/immich/discussions/4348) and [PR #6455](https://github.com/immich-app/immich/pull/6455).
### Does Immich support the filtering of NSFW images? ### Does Immich support the filtering of NSFW images?

View File

@ -53,7 +53,7 @@ docker compose create # Create Docker containers for Immich apps witho
docker start immich_postgres # Start Postgres server docker start immich_postgres # Start Postgres server
sleep 10 # Wait for Postgres server to start up sleep 10 # Wait for Postgres server to start up
# Check the database user if you deviated from the default # Check the database user if you deviated from the default
gunzip < "/path/to/backup/dump.sql.gz" \ gunzip --stdout "/path/to/backup/dump.sql.gz" \
| sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \ | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
| docker exec -i immich_postgres psql --dbname=postgres --username=<DB_USERNAME> # Restore Backup | docker exec -i immich_postgres psql --dbname=postgres --username=<DB_USERNAME> # Restore Backup
docker compose up -d # Start remainder of Immich apps docker compose up -d # Start remainder of Immich apps
@ -76,8 +76,8 @@ docker compose create # Create Docker containers for
docker start immich_postgres # Start Postgres server docker start immich_postgres # Start Postgres server
sleep 10 # Wait for Postgres server to start up sleep 10 # Wait for Postgres server to start up
docker exec -it immich_postgres bash # Enter the Docker shell and run the following command docker exec -it immich_postgres bash # Enter the Docker shell and run the following command
# Check the database user if you deviated from the default. If your backup ends in `.gz`, replace `cat` with `gunzip` # Check the database user if you deviated from the default. If your backup ends in `.gz`, replace `cat` with `gunzip --stdout`
cat < "/dump.sql" | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | psql --dbname=postgres --username=<DB_USERNAME> cat "/dump.sql" | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | psql --dbname=postgres --username=<DB_USERNAME>
exit # Exit the Docker shell exit # Exit the Docker shell
docker compose up -d # Start remainder of Immich apps docker compose up -d # Start remainder of Immich apps
``` ```

View File

@ -77,7 +77,7 @@ alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
7. Paste the entire contents of the [Immich example.env](https://github.com/immich-app/immich/releases/latest/download/example.env) file into the Unraid editor, then **before saving** edit the following: 7. Paste the entire contents of the [Immich example.env](https://github.com/immich-app/immich/releases/latest/download/example.env) file into the Unraid editor, then **before saving** edit the following:
- `UPLOAD_LOCATION`: Create a folder in your Images Unraid share and place the **absolute** location here > For example my _"images"_ share has a folder within it called _"immich"_. If I browse to this directory in the terminal and type `pwd` the output is `/mnt/user/images/immich`. This is the exact value I need to enter as my `UPLOAD_LOCATION` - `UPLOAD_LOCATION`: Create a folder in your Images Unraid share and place the **absolute** location here > For example my _"images"_ share has a folder within it called _"immich"_. If I browse to this directory in the terminal and type `pwd` the output is `/mnt/user/images/immich`. This is the exact value I need to enter as my `UPLOAD_LOCATION`
- `DB_DATA_LOCATION`: Change this to use an Unraid share (preferably a cache pool, e.g. `/mnt/user/appdata`). If left at default it will try to use Unraid's `/boot/config/plugins/compose.manager/projects/[stack_name]/postgres` folder which it doesn't have permissions to, resulting in this container continuously restarting. - `DB_DATA_LOCATION`: Change this to use an Unraid share (preferably a cache pool, e.g. `/mnt/user/appdata/postgresql/data`). This uses the `appdata` share. Do also create the `postgresql` folder, by running `mkdir /mnt/user/{share_location}/postgresql/data`. If left at default it will try to use Unraid's `/boot/config/plugins/compose.manager/projects/[stack_name]/postgres` folder which it doesn't have permissions to, resulting in this container continuously restarting.
<img <img
src={require('./img/unraid05.webp').default} src={require('./img/unraid05.webp').default}

View File

@ -18377,9 +18377,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.7.3", "version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",

View File

@ -201,7 +201,7 @@ describe('/people', () => {
expect(body).toMatchObject({ expect(body).toMatchObject({
id: expect.any(String), id: expect.any(String),
name: 'New Person', name: 'New Person',
birthDate: '1990-01-01T00:00:00.000Z', birthDate: '1990-01-01',
}); });
}); });
@ -262,7 +262,7 @@ describe('/people', () => {
.set('Authorization', `Bearer ${admin.accessToken}`) .set('Authorization', `Bearer ${admin.accessToken}`)
.send({ birthDate: '1990-01-01' }); .send({ birthDate: '1990-01-01' });
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toMatchObject({ birthDate: '1990-01-01T00:00:00.000Z' }); expect(body).toMatchObject({ birthDate: '1990-01-01' });
}); });
it('should clear a date of birth', async () => { it('should clear a date of birth', async () => {

View File

@ -240,7 +240,7 @@
"storage_template_hash_verification_enabled_description": "Enables hash verification, don't disable this unless you're certain of the implications", "storage_template_hash_verification_enabled_description": "Enables hash verification, don't disable this unless you're certain of the implications",
"storage_template_migration": "Storage template migration", "storage_template_migration": "Storage template migration",
"storage_template_migration_description": "Apply the current <link>{template}</link> to previously uploaded assets", "storage_template_migration_description": "Apply the current <link>{template}</link> to previously uploaded assets",
"storage_template_migration_info": "Template changes will only apply to new assets. To retroactively apply the template to previously uploaded assets, run the <link>{job}</link>.", "storage_template_migration_info": "The storage template will convert all extensions to lowercase. Template changes will only apply to new assets. To retroactively apply the template to previously uploaded assets, run the <link>{job}</link>.",
"storage_template_migration_job": "Storage Template Migration Job", "storage_template_migration_job": "Storage Template Migration Job",
"storage_template_more_details": "For more details about this feature, refer to the <template-link>Storage Template</template-link> and its <implications-link>implications</implications-link>", "storage_template_more_details": "For more details about this feature, refer to the <template-link>Storage Template</template-link> and its <implications-link>implications</implications-link>",
"storage_template_onboarding_description": "When enabled, this feature will auto-organize files based on a user-defined template. Due to stability issues the feature has been turned off by default. For more information, please see the <link>documentation</link>.", "storage_template_onboarding_description": "When enabled, this feature will auto-organize files based on a user-defined template. Due to stability issues the feature has been turned off by default. For more information, please see the <link>documentation</link>.",
@ -987,6 +987,7 @@
"permanently_deleted_asset": "Permanently deleted asset", "permanently_deleted_asset": "Permanently deleted asset",
"permanently_deleted_assets_count": "Permanently deleted {count, plural, one {# asset} other {# assets}}", "permanently_deleted_assets_count": "Permanently deleted {count, plural, one {# asset} other {# assets}}",
"person": "Person", "person": "Person",
"person_birthdate": "Born on {date}",
"person_hidden": "{name}{hidden, select, true { (hidden)} other {}}", "person_hidden": "{name}{hidden, select, true { (hidden)} other {}}",
"photo_shared_all_users": "Looks like you shared your photos with all users or you don't have any user to share with.", "photo_shared_all_users": "Looks like you shared your photos with all users or you don't have any user to share with.",
"photos": "Photos", "photos": "Photos",

View File

@ -19,20 +19,16 @@ FROM builder-${DEVICE} AS builder
ARG DEVICE ARG DEVICE
ENV PYTHONDONTWRITEBYTECODE=1 \ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1
PIP_NO_CACHE_DIR=true \ WORKDIR /usr/src/app
VIRTUAL_ENV="/opt/venv" \
PATH="/opt/venv/bin:${PATH}"
RUN apt-get update && apt-get install -y --no-install-recommends g++ RUN apt-get update && apt-get install -y --no-install-recommends g++
RUN pip install --upgrade pip && pip install poetry COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
RUN poetry config installer.max-workers 10 && \ RUN --mount=type=cache,target=/root/.cache/uv \
poetry config virtualenvs.create false --mount=type=bind,source=uv.lock,target=uv.lock \
RUN python3 -m venv /opt/venv --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --extra ${DEVICE} --no-dev --no-editable --no-install-project --compile-bytecode --no-progress
COPY poetry.lock pyproject.toml ./
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
@ -93,7 +89,7 @@ WORKDIR /usr/src/app
ENV TRANSFORMERS_CACHE=/cache \ ENV TRANSFORMERS_CACHE=/cache \
PYTHONDONTWRITEBYTECODE=1 \ PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
PATH="/opt/venv/bin:$PATH" \ PATH="/usr/src/app/.venv/bin:$PATH" \
PYTHONPATH=/usr/src \ PYTHONPATH=/usr/src \
DEVICE=${DEVICE} DEVICE=${DEVICE}
@ -102,7 +98,7 @@ RUN echo "hard core 0" >> /etc/security/limits.conf && \
echo "fs.suid_dumpable 0" >> /etc/sysctl.conf && \ echo "fs.suid_dumpable 0" >> /etc/sysctl.conf && \
echo 'ulimit -S -c 0 > /dev/null 2>&1' >> /etc/profile echo 'ulimit -S -c 0 > /dev/null 2>&1' >> /etc/profile
COPY --from=builder /opt/venv /opt/venv COPY --from=builder /usr/src/app/.venv /usr/src/app/.venv
COPY ann/ann.py /usr/src/ann/ann.py COPY ann/ann.py /usr/src/ann/ann.py
COPY start.sh log_conf.json gunicorn_conf.py ./ COPY start.sh log_conf.json gunicorn_conf.py ./
COPY app . COPY app .

View File

@ -5,13 +5,12 @@
# Setup # Setup
This project uses [Poetry](https://python-poetry.org/docs/#installation), so be sure to install it first. This project uses [uv](https://docs.astral.sh/uv/getting-started/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 `uv sync --extra cpu` will install everything you need in an isolated virtual environment.
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. CUDA and OpenVINO are supported as acceleration APIs. To use them, you can replace `--group cpu` with either of `--group cuda` or `--group 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.
Be sure to commit the `poetry.lock` and `pyproject.toml` files with `poetry lock --no-update` to reflect any changes in dependencies.
To add or remove dependencies, you can use the commands `uv add $PACKAGE_NAME` and `uv remove $PACKAGE_NAME`, respectively.
Be sure to commit the `uv.lock` and `pyproject.toml` files with `uv lock` to reflect any changes in dependencies.
# Load Testing # Load Testing
@ -19,22 +18,25 @@ To measure inference throughput and latency, you can use [Locust](https://locust
Locust works by querying the model endpoints and aggregating their statistics, meaning the app must be deployed. Locust works by querying the model endpoints and aggregating their statistics, meaning the app must be deployed.
You can change the models or adjust options like score thresholds through the Locust UI. You can change the models or adjust options like score thresholds through the Locust UI.
To get started, you can simply run `locust --web-host 127.0.0.1` and open `localhost:8089` in a browser to access the UI. See the [Locust documentation](https://docs.locust.io/en/stable/index.html) for more info on running Locust. To get started, you can simply run `locust --web-host 127.0.0.1` and open `localhost:8089` in a browser to access the UI. See the [Locust documentation](https://docs.locust.io/en/stable/index.html) for more info on running Locust.
Note that in Locust's jargon, concurrency is measured in `users`, and each user runs one task at a time. To achieve a particular per-endpoint concurrency, multiply that number by the number of endpoints to be queried. For example, if there are 3 endpoints and you want each of them to receive 8 requests at a time, you should set the number of users to 24. Note that in Locust's jargon, concurrency is measured in `users`, and each user runs one task at a time. To achieve a particular per-endpoint concurrency, multiply that number by the number of endpoints to be queried. For example, if there are 3 endpoints and you want each of them to receive 8 requests at a time, you should set the number of users to 24.
# Facial Recognition # Facial Recognition
## Acknowledgements ## Acknowledgements
This project utilizes facial recognition models from the [InsightFace](https://github.com/deepinsight/insightface/tree/master/model_zoo) project. We appreciate the work put into developing these models, which have been beneficial to the machine learning part of this project. This project utilizes facial recognition models from the [InsightFace](https://github.com/deepinsight/insightface/tree/master/model_zoo) project. We appreciate the work put into developing these models, which have been beneficial to the machine learning part of this project.
### Used Models ### Used Models
* antelopev2
* buffalo_l - antelopev2
* buffalo_m - buffalo_l
* buffalo_s - buffalo_m
- buffalo_s
## 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.

File diff suppressed because it is too large Load Diff

View File

@ -1,72 +1,77 @@
[tool.poetry] [project]
name = "machine-learning" name = "machine-learning"
version = "1.129.0" version = "1.129.0"
description = "" description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"] authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }]
requires-python = ">=3.10,<4.0"
readme = "README.md" readme = "README.md"
packages = [{include = "app"}] dependencies = [
"aiocache>=0.12.1,<1.0",
"fastapi>=0.95.2,<1.0",
"ftfy>=6.1.1",
"gunicorn>=21.1.0",
"huggingface-hub>=0.20.1,<1.0",
"insightface>=0.7.3,<1.0",
"opencv-python-headless>=4.7.0.72,<5.0",
"orjson>=3.9.5",
"pillow>=9.5.0,<11.0",
"pydantic>=2.0.0,<3",
"pydantic-settings>=2.5.2,<3",
"python-multipart>=0.0.6,<1.0",
"rich>=13.4.2",
"tokenizers>=0.15.0,<1.0",
"uvicorn[standard]>=0.22.0,<1.0",
]
[tool.poetry.dependencies] [dependency-groups]
python = ">=3.10,<4.0" test = [
insightface = ">=0.7.3,<1.0" "httpx>=0.24.1",
opencv-python-headless = ">=4.7.0.72,<5.0" "pytest>=7.3.1",
pillow = ">=9.5.0,<11.0" "pytest-asyncio>=0.21.0",
fastapi = ">=0.95.2,<1.0" "pytest-cov>=4.1.0",
uvicorn = {extras = ["standard"], version = ">=0.22.0,<1.0"} "pytest-mock>=3.11.1",
pydantic = "^2.0.0" ]
pydantic-settings = "^2.5.2" types = [
aiocache = ">=0.12.1,<1.0" "types-pyyaml>=6.0.12.20241230",
rich = ">=13.4.2" "types-requests>=2.32.0.20250306",
ftfy = ">=6.1.1" "types-setuptools>=75.8.2.20250305",
python-multipart = ">=0.0.6,<1.0" "types-simplejson>=3.20.0.20250218",
orjson = ">=3.9.5" "types-ujson>=5.10.0.20240515",
gunicorn = ">=21.1.0" ]
huggingface-hub = ">=0.20.1,<1.0" lint = [
tokenizers = ">=0.15.0,<1.0" "black>=23.3.0",
"mypy>=1.3.0",
"ruff>=0.0.272",
{ include-group = "types" },
]
dev = ["locust>=2.15.1", { include-group = "test" }, { include-group = "lint" }]
[tool.poetry.group.dev.dependencies] [project.optional-dependencies]
mypy = ">=1.3.0" cpu = ["onnxruntime>=1.15.0,<2"]
black = ">=23.3.0" cuda = ["onnxruntime-gpu>=1.17.0,<2"]
pytest = ">=7.3.1" openvino = ["onnxruntime-openvino>=1.17.1,<1.19.0"]
locust = ">=2.15.1" armnn = ["onnxruntime>=1.15.0,<2"]
httpx = ">=0.24.1"
pytest-asyncio = ">=0.21.0"
pytest-cov = ">=4.1.0"
ruff = ">=0.0.272"
pytest-mock = ">=3.11.1"
[tool.poetry.group.cpu] [tool.uv]
optional = true compile-bytecode = true
[tool.poetry.group.cpu.dependencies] [[tool.uv.index]]
onnxruntime = "^1.15.0"
[tool.poetry.group.cuda]
optional = true
[tool.poetry.group.cuda.dependencies]
onnxruntime-gpu = {version = "^1.17.0", source = "cuda12"}
[tool.poetry.group.openvino]
optional = true
[tool.poetry.group.openvino.dependencies]
onnxruntime-openvino = ">=1.17.1,<1.19.0"
[tool.poetry.group.armnn]
optional = true
[tool.poetry.group.armnn.dependencies]
onnxruntime = "^1.15.0"
[[tool.poetry.source]]
name = "cuda12" name = "cuda12"
url = "https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/" url = "https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/"
priority = "explicit" explicit = true
[tool.uv.sources]
onnxruntime-gpu = { index = "cuda12" }
[tool.hatch.build.targets.sdist]
include = ["app"]
[tool.hatch.build.targets.wheel]
include = ["app"]
[build-system] [build-system]
requires = ["poetry-core"] requires = ["hatchling"]
build-backend = "poetry.core.masonry.api" build-backend = "hatchling.build"
[tool.mypy] [tool.mypy]
python_version = "3.11" python_version = "3.11"

2648
machine-learning/uv.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -73,7 +73,7 @@ if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
npm --prefix web i --package-lock-only npm --prefix web i --package-lock-only
npm --prefix e2e version "$SERVER_PUMP" npm --prefix e2e version "$SERVER_PUMP"
npm --prefix e2e i --package-lock-only npm --prefix e2e i --package-lock-only
poetry --directory machine-learning version "$SERVER_PUMP" uvx --from=toml-cli toml set --toml-path=pyproject.toml project.version "$SERVER_PUMP"
fi fi
if [ "$CURRENT_MOBILE" != "$NEXT_MOBILE" ]; then if [ "$CURRENT_MOBILE" != "$NEXT_MOBILE" ]; then

View File

@ -93,9 +93,9 @@
<activity <activity
android:name="com.linusu.flutter_web_auth.CallbackActivity" android:name="com.linusu.flutter_web_auth_2.CallbackActivity"
android:exported="true"> android:exported="true">
<intent-filter android:label="flutter_web_auth"> <intent-filter android:label="flutter_web_auth_2">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />

View File

@ -221,7 +221,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
.setContentTitle(title) .setContentTitle(title)
.setTicker(title) .setTicker(title)
.setContentText(content) .setContentText(content)
.setSmallIcon(R.mipmap.ic_launcher) .setSmallIcon(R.drawable.notification_icon)
.build() .build()
notificationManager.notify(individualTag, NOTIFICATION_ERROR_ID, notification) notificationManager.notify(individualTag, NOTIFICATION_ERROR_ID, notification)
} }
@ -260,7 +260,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
var builder = if (isDetail) notificationDetailBuilder else notificationBuilder var builder = if (isDetail) notificationDetailBuilder else notificationBuilder
if (builder == null) { if (builder == null) {
builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID) builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher) .setSmallIcon(R.drawable.notification_icon)
.setOnlyAlertOnce(true) .setOnlyAlertOnce(true)
.setOngoing(true) .setOngoing(true)
if (isDetail) { if (isDetail) {

View File

@ -264,6 +264,7 @@
"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": "PEOPLE",
"exif_bottom_sheet_person_add_person": "Add name", "exif_bottom_sheet_person_add_person": "Add name",
"exif_bottom_sheet_person_age": "Age {}",
"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!",

View File

@ -48,7 +48,7 @@ PODS:
- flutter_udid (0.0.1): - flutter_udid (0.0.1):
- Flutter - Flutter
- SAMKeychain - SAMKeychain
- flutter_web_auth (0.6.0): - flutter_web_auth_2 (3.0.0):
- Flutter - Flutter
- fluttertoast (0.0.2): - fluttertoast (0.0.2):
- Flutter - Flutter
@ -117,7 +117,7 @@ DEPENDENCIES:
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`) - flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
- flutter_web_auth (from `.symlinks/plugins/flutter_web_auth/ios`) - flutter_web_auth_2 (from `.symlinks/plugins/flutter_web_auth_2/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`) - geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
@ -166,8 +166,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_native_splash/ios" :path: ".symlinks/plugins/flutter_native_splash/ios"
flutter_udid: flutter_udid:
:path: ".symlinks/plugins/flutter_udid/ios" :path: ".symlinks/plugins/flutter_udid/ios"
flutter_web_auth: flutter_web_auth_2:
:path: ".symlinks/plugins/flutter_web_auth/ios" :path: ".symlinks/plugins/flutter_web_auth_2/ios"
fluttertoast: fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios" :path: ".symlinks/plugins/fluttertoast/ios"
geolocator_apple: geolocator_apple:
@ -220,7 +220,7 @@ SPEC CHECKSUMS:
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04 flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04
flutter_web_auth: acc15a8fd7bba796a933c724a6dffc3d00f07c27 flutter_web_auth_2: 06d500582775790a0d4c323222fcb6d7990f9603
fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
geolocator_apple: 6cbaf322953988e009e5ecb481f07efece75c450 geolocator_apple: 6cbaf322953988e009e5ecb481f07efece75c450
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1

View File

@ -6,7 +6,7 @@ import 'package:immich_mobile/models/map/map_marker.model.dart';
import 'package:immich_mobile/utils/map_utils.dart'; import 'package:immich_mobile/utils/map_utils.dart';
import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:maplibre_gl/maplibre_gl.dart';
extension MapMarkers on MaplibreMapController { extension MapMarkers on MapLibreMapController {
static var _completer = Completer()..complete(); static var _completer = Completer()..complete();
Future<void> addGeoJSONSourceForMarkers(List<MapMarker> markers) async { Future<void> addGeoJSONSourceForMarkers(List<MapMarker> markers) async {
@ -40,12 +40,26 @@ extension MapMarkers on MaplibreMapController {
await addGeoJSONSourceForMarkers(markers); await addGeoJSONSourceForMarkers(markers);
await addHeatmapLayer( await addCircleLayer(
MapUtils.defaultSourceId, MapUtils.defaultSourceId,
MapUtils.defaultHeatMapLayerId, MapUtils.defaultHeatMapLayerId,
MapUtils.defaultHeatMapLayerProperties, const CircleLayerProperties(
circleRadius: 10,
circleColor: "rgba(150,86,34,0.7)",
circleBlur: 1.0,
circleOpacity: 0.7,
circleStrokeWidth: 0.1,
circleStrokeColor: "rgba(203,46,19,0.5)",
circleStrokeOpacity: 0.7,
),
); );
// await addHeatmapLayer(
// MapUtils.defaultSourceId,
// MapUtils.defaultHeatMapLayerId,
// MapUtils.defaultHeatMapLayerProperties,
// );
_completer.complete(); _completer.complete();
} }

View File

@ -8,20 +8,26 @@ class SearchCuratedContent {
/// The label to show associated with this curated object /// The label to show associated with this curated object
final String label; final String label;
/// The subtitle to show below the label
final String? subtitle;
/// The id to lookup the asset from the server /// The id to lookup the asset from the server
final String id; final String id;
SearchCuratedContent({ SearchCuratedContent({
required this.label, required this.label,
required this.id, required this.id,
this.subtitle,
}); });
SearchCuratedContent copyWith({ SearchCuratedContent copyWith({
String? label, String? label,
String? subtitle,
String? id, String? id,
}) { }) {
return SearchCuratedContent( return SearchCuratedContent(
label: label ?? this.label, label: label ?? this.label,
subtitle: subtitle ?? this.subtitle,
id: id ?? this.id, id: id ?? this.id,
); );
} }
@ -29,6 +35,7 @@ class SearchCuratedContent {
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return <String, dynamic>{ return <String, dynamic>{
'label': label, 'label': label,
'subtitle': subtitle,
'id': id, 'id': id,
}; };
} }
@ -36,6 +43,7 @@ class SearchCuratedContent {
factory SearchCuratedContent.fromMap(Map<String, dynamic> map) { factory SearchCuratedContent.fromMap(Map<String, dynamic> map) {
return SearchCuratedContent( return SearchCuratedContent(
label: map['label'] as String, label: map['label'] as String,
subtitle: map['subtitle'] as String?,
id: map['id'] as String, id: map['id'] as String,
); );
} }
@ -46,13 +54,14 @@ class SearchCuratedContent {
SearchCuratedContent.fromMap(json.decode(source) as Map<String, dynamic>); SearchCuratedContent.fromMap(json.decode(source) as Map<String, dynamic>);
@override @override
String toString() => 'CuratedContent(label: $label, id: $id)'; String toString() =>
'CuratedContent(label: $label, subtitle: $subtitle, id: $id)';
@override @override
bool operator ==(covariant SearchCuratedContent other) { bool operator ==(covariant SearchCuratedContent other) {
if (identical(this, other)) return true; if (identical(this, other)) return true;
return other.label == label && other.id == id; return other.label == label && other.subtitle == subtitle && other.id == id;
} }
@override @override

View File

@ -1,8 +1,10 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/providers/search/people.provider.dart'; import 'package:immich_mobile/providers/search/people.provider.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
@ -16,6 +18,8 @@ class PeopleCollectionPage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final people = ref.watch(getAllPeopleProvider); final people = ref.watch(getAllPeopleProvider);
final headers = ApiService.getRequestHeaders(); final headers = ApiService.getRequestHeaders();
final formFocus = useFocusNode();
final ValueNotifier<String?> search = useState(null);
showNameEditModel( showNameEditModel(
String personId, String personId,
@ -36,10 +40,70 @@ class PeopleCollectionPage extends HookConsumerWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('people'.tr()), automaticallyImplyLeading: search.value == null,
title: search.value != null
? TextField(
focusNode: formFocus,
onTapOutside: (_) => formFocus.unfocus(),
onChanged: (value) => search.value = value,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(left: 24),
filled: true,
fillColor: context.primaryColor.withOpacity(0.1),
hintStyle: context.textTheme.bodyLarge?.copyWith(
color: context.themeData.colorScheme.onSurfaceSecondary,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: BorderSide(
color: context.colorScheme.surfaceContainerHighest,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: BorderSide(
color: context.colorScheme.surfaceContainerHighest,
),
),
disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: BorderSide(
color: context.colorScheme.surfaceContainerHighest,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: BorderSide(
color: context.colorScheme.primary.withAlpha(150),
),
),
prefixIcon: Icon(
Icons.search_rounded,
color: context.colorScheme.primary,
),
hintText: 'search_filter_people_hint'.tr(),
),
autofocus: true,
)
: Text('people'.tr()),
actions: [
IconButton(
icon: Icon(search.value != null ? Icons.close : Icons.search),
onPressed: () {
search.value = search.value == null ? '' : null;
},
),
],
), ),
body: people.when( body: people.when(
data: (people) { data: (people) {
if (search.value != null) {
people = people.where((person) {
return person.name
.toLowerCase()
.contains(search.value!.toLowerCase());
}).toList();
}
return GridView.builder( return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isTablet ? 6 : 3, crossAxisCount: isTablet ? 6 : 3,

View File

@ -11,7 +11,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/latlngbounds_extension.dart';
import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart'; import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart';
import 'package:immich_mobile/models/map/map_event.model.dart'; import 'package:immich_mobile/models/map/map_event.model.dart';
import 'package:immich_mobile/models/map/map_marker.model.dart'; import 'package:immich_mobile/models/map/map_marker.model.dart';
@ -39,7 +38,7 @@ class MapPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final mapController = useRef<MaplibreMapController?>(null); final mapController = useRef<MapLibreMapController?>(null);
final markers = useRef<List<MapMarker>>([]); final markers = useRef<List<MapMarker>>([]);
final markersInBounds = useRef<List<MapMarker>>([]); final markersInBounds = useRef<List<MapMarker>>([]);
final bottomSheetStreamController = useStreamController<MapEvent>(); final bottomSheetStreamController = useStreamController<MapEvent>();
@ -162,7 +161,7 @@ class MapPage extends HookConsumerWidget {
} }
} }
void onMapCreated(MaplibreMapController controller) async { void onMapCreated(MapLibreMapController controller) async {
mapController.value = controller; mapController.value = controller;
controller.addListener(() { controller.addListener(() {
if (controller.isCameraMoving && selectedMarker.value != null) { if (controller.isCameraMoving && selectedMarker.value != null) {
@ -389,7 +388,7 @@ class _MapWithMarker extends StatelessWidget {
child: Stack( child: Stack(
children: [ children: [
style.widgetWhen( style.widgetWhen(
onData: (style) => MaplibreMap( onData: (style) => MapLibreMap(
initialCameraPosition: initialCameraPosition:
const CameraPosition(target: LatLng(0, 0)), const CameraPosition(target: LatLng(0, 0)),
styleString: style, styleString: style,
@ -403,7 +402,7 @@ class _MapWithMarker extends StatelessWidget {
tiltGesturesEnabled: false, tiltGesturesEnabled: false,
dragEnabled: false, dragEnabled: false,
myLocationEnabled: false, myLocationEnabled: false,
attributionButtonPosition: AttributionButtonPosition.TopRight, attributionButtonPosition: AttributionButtonPosition.topRight,
rotateGesturesEnabled: false, rotateGesturesEnabled: false,
), ),
), ),

View File

@ -24,7 +24,7 @@ class MapLocationPickerPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final selectedLatLng = useValueNotifier<LatLng>(initialLatLng); final selectedLatLng = useValueNotifier<LatLng>(initialLatLng);
final controller = useRef<MaplibreMapController?>(null); final controller = useRef<MapLibreMapController?>(null);
final marker = useRef<Symbol?>(null); final marker = useRef<Symbol?>(null);
Future<void> onStyleLoaded() async { Future<void> onStyleLoaded() async {
@ -74,7 +74,7 @@ class MapLocationPickerPage extends HookConsumerWidget {
bottomRight: Radius.circular(40), bottomRight: Radius.circular(40),
), ),
), ),
child: MaplibreMap( child: MapLibreMap(
initialCameraPosition: initialCameraPosition:
CameraPosition(target: initialLatLng, zoom: 12), CameraPosition(target: initialLatLng, zoom: 12),
styleString: style, styleString: style,

View File

@ -187,6 +187,8 @@ class AlbumActivityProvider extends AutoDisposeAsyncNotifierProviderImpl<
} }
} }
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin AlbumActivityRef on AutoDisposeAsyncNotifierProviderRef<List<Activity>> { mixin AlbumActivityRef on AutoDisposeAsyncNotifierProviderRef<List<Activity>> {
/// The parameter `albumId` of this provider. /// The parameter `albumId` of this provider.
String get albumId; String get albumId;
@ -206,4 +208,4 @@ class _AlbumActivityProviderElement
String? get assetId => (origin as AlbumActivityProvider).assetId; String? get assetId => (origin as AlbumActivityProvider).assetId;
} }
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -1,3 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/repositories/activity_api.repository.dart'; import 'package:immich_mobile/repositories/activity_api.repository.dart';
import 'package:immich_mobile/services/activity.service.dart'; import 'package:immich_mobile/services/activity.service.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -5,5 +6,5 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'activity_service.provider.g.dart'; part 'activity_service.provider.g.dart';
@riverpod @riverpod
ActivityService activityService(ActivityServiceRef ref) => ActivityService activityService(Ref ref) =>
ActivityService(ref.watch(activityApiRepositoryProvider)); ActivityService(ref.watch(activityApiRepositoryProvider));

View File

@ -20,6 +20,8 @@ final activityServiceProvider = AutoDisposeProvider<ActivityService>.internal(
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ActivityServiceRef = AutoDisposeProviderRef<ActivityService>; typedef ActivityServiceRef = AutoDisposeProviderRef<ActivityService>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -186,6 +186,8 @@ class ActivityStatisticsProvider
} }
} }
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin ActivityStatisticsRef on AutoDisposeNotifierProviderRef<int> { mixin ActivityStatisticsRef on AutoDisposeNotifierProviderRef<int> {
/// The parameter `albumId` of this provider. /// The parameter `albumId` of this provider.
String get albumId; String get albumId;
@ -205,4 +207,4 @@ class _ActivityStatisticsProviderElement
String? get assetId => (origin as ActivityStatisticsProvider).assetId; String? get assetId => (origin as ActivityStatisticsProvider).assetId;
} }
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -40,4 +40,4 @@ final albumSortOrderProvider =
typedef _$AlbumSortOrder = AutoDisposeNotifier<bool>; typedef _$AlbumSortOrder = AutoDisposeNotifier<bool>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -22,4 +22,4 @@ final currentAlbumProvider =
typedef _$CurrentAlbum = AutoDisposeNotifier<Album?>; typedef _$CurrentAlbum = AutoDisposeNotifier<Album?>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -1,7 +1,8 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'api.provider.g.dart'; part 'api.provider.g.dart';
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
ApiService apiService(ApiServiceRef ref) => ApiService(); ApiService apiService(Ref ref) => ApiService();

View File

@ -19,6 +19,8 @@ final apiServiceProvider = Provider<ApiService>.internal(
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ApiServiceRef = ProviderRef<ApiService>; typedef ApiServiceRef = ProviderRef<ApiService>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -1,8 +1,8 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'app_settings.provider.g.dart'; part 'app_settings.provider.g.dart';
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
AppSettingsService appSettingsService(AppSettingsServiceRef ref) => AppSettingsService appSettingsService(Ref ref) => AppSettingsService();
AppSettingsService();

View File

@ -21,6 +21,8 @@ final appSettingsServiceProvider = Provider<AppSettingsService>.internal(
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef AppSettingsServiceRef = ProviderRef<AppSettingsService>; typedef AppSettingsServiceRef = ProviderRef<AppSettingsService>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -31,7 +31,7 @@ class AssetNotifier extends StateNotifier<bool> {
final SyncService _syncService; final SyncService _syncService;
final ETagService _etagService; final ETagService _etagService;
final ExifService _exifService; final ExifService _exifService;
final StateNotifierProviderRef _ref; final Ref _ref;
final log = Logger('AssetNotifier'); final log = Logger('AssetNotifier');
bool _getAllAssetInProgress = false; bool _getAllAssetInProgress = false;
bool _deleteInProgress = false; bool _deleteInProgress = false;

View File

@ -171,6 +171,8 @@ class AssetPeopleNotifierProvider extends AutoDisposeAsyncNotifierProviderImpl<
} }
} }
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin AssetPeopleNotifierRef mixin AssetPeopleNotifierRef
on AutoDisposeAsyncNotifierProviderRef<List<PersonWithFacesResponseDto>> { on AutoDisposeAsyncNotifierProviderRef<List<PersonWithFacesResponseDto>> {
/// The parameter `asset` of this provider. /// The parameter `asset` of this provider.
@ -186,4 +188,4 @@ class _AssetPeopleNotifierProviderElement
Asset get asset => (origin as AssetPeopleNotifierProvider).asset; Asset get asset => (origin as AssetPeopleNotifierProvider).asset;
} }
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -39,6 +39,6 @@ final assetStackStateProvider = StateNotifierProvider.autoDispose
); );
@riverpod @riverpod
int assetStackIndex(AssetStackIndexRef ref, Asset asset) { int assetStackIndex(Ref ref, Asset asset) {
return -1; return -1;
} }

View File

@ -142,6 +142,8 @@ class AssetStackIndexProvider extends AutoDisposeProvider<int> {
} }
} }
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin AssetStackIndexRef on AutoDisposeProviderRef<int> { mixin AssetStackIndexRef on AutoDisposeProviderRef<int> {
/// The parameter `asset` of this provider. /// The parameter `asset` of this provider.
Asset get asset; Asset get asset;
@ -155,4 +157,4 @@ class _AssetStackIndexProviderElement extends AutoDisposeProviderElement<int>
Asset get asset => (origin as AssetStackIndexProvider).asset; Asset get asset => (origin as AssetStackIndexProvider).asset;
} }
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -22,4 +22,4 @@ final currentAssetProvider =
typedef _$CurrentAsset = AutoDisposeNotifier<Asset?>; typedef _$CurrentAsset = AutoDisposeNotifier<Asset?>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -24,4 +24,4 @@ final backupVerificationProvider =
typedef _$BackupVerification = AutoDisposeNotifier<bool>; typedef _$BackupVerification = AutoDisposeNotifier<bool>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -1,12 +1,13 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'immich_logo_provider.g.dart'; part 'immich_logo_provider.g.dart';
@riverpod @riverpod
Future<Uint8List> immichLogo(ImmichLogoRef ref) async { Future<Uint8List> immichLogo(Ref ref) async {
final json = await rootBundle.loadString('assets/immich-logo.json'); final json = await rootBundle.loadString('assets/immich-logo.json');
final j = jsonDecode(json); final j = jsonDecode(json);
return base64Decode(j['content']); return base64Decode(j['content']);

View File

@ -19,6 +19,8 @@ final immichLogoProvider = AutoDisposeFutureProvider<Uint8List>.internal(
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ImmichLogoRef = AutoDisposeFutureProviderRef<Uint8List>; typedef ImmichLogoRef = AutoDisposeFutureProviderRef<Uint8List>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -1,7 +1,8 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'db.provider.g.dart'; part 'db.provider.g.dart';
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
Isar isar(IsarRef ref) => throw UnimplementedError('isar'); Isar isar(Ref ref) => throw UnimplementedError('isar');

View File

@ -19,6 +19,8 @@ final isarProvider = Provider<Isar>.internal(
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef IsarRef = ProviderRef<Isar>; typedef IsarRef = ProviderRef<Isar>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -1,3 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
@ -6,5 +7,5 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'exif.provider.g.dart'; part 'exif.provider.g.dart';
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
IExifInfoRepository exifRepository(ExifRepositoryRef ref) => IExifInfoRepository exifRepository(Ref ref) =>
IsarExifRepository(ref.watch(isarProvider)); IsarExifRepository(ref.watch(isarProvider));

View File

@ -20,6 +20,8 @@ final exifRepositoryProvider = Provider<IExifInfoRepository>.internal(
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ExifRepositoryRef = ProviderRef<IExifInfoRepository>; typedef ExifRepositoryRef = ProviderRef<IExifInfoRepository>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -1,3 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/interfaces/store.interface.dart'; import 'package:immich_mobile/domain/interfaces/store.interface.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
@ -6,5 +7,5 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'store.provider.g.dart'; part 'store.provider.g.dart';
@riverpod @riverpod
IStoreRepository storeRepository(StoreRepositoryRef ref) => IStoreRepository storeRepository(Ref ref) =>
IsarStoreRepository(ref.watch(isarProvider)); IsarStoreRepository(ref.watch(isarProvider));

View File

@ -20,6 +20,8 @@ final storeRepositoryProvider = AutoDisposeProvider<IStoreRepository>.internal(
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef StoreRepositoryRef = AutoDisposeProviderRef<IStoreRepository>; typedef StoreRepositoryRef = AutoDisposeProviderRef<IStoreRepository>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -1,3 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/models/map/map_marker.model.dart'; import 'package:immich_mobile/models/map/map_marker.model.dart';
import 'package:immich_mobile/providers/map/map_service.provider.dart'; import 'package:immich_mobile/providers/map/map_service.provider.dart';
import 'package:immich_mobile/providers/map/map_state.provider.dart'; import 'package:immich_mobile/providers/map/map_state.provider.dart';
@ -6,7 +7,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'map_marker.provider.g.dart'; part 'map_marker.provider.g.dart';
@riverpod @riverpod
Future<List<MapMarker>> mapMarkers(MapMarkersRef ref) async { Future<List<MapMarker>> mapMarkers(Ref ref) async {
final service = ref.read(mapServiceProvider); final service = ref.read(mapServiceProvider);
final mapState = ref.read(mapStateNotifierProvider); final mapState = ref.read(mapStateNotifierProvider);
DateTime? fileCreatedAfter; DateTime? fileCreatedAfter;

View File

@ -19,6 +19,8 @@ final mapMarkersProvider = AutoDisposeFutureProvider<List<MapMarker>>.internal(
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef MapMarkersRef = AutoDisposeFutureProviderRef<List<MapMarker>>; typedef MapMarkersRef = AutoDisposeFutureProviderRef<List<MapMarker>>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -1,3 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/services/map.service.dart'; import 'package:immich_mobile/services/map.service.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -5,5 +6,4 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'map_service.provider.g.dart'; part 'map_service.provider.g.dart';
@riverpod @riverpod
MapSerivce mapService(MapServiceRef ref) => MapSerivce mapService(Ref ref) => MapSerivce(ref.watch(apiServiceProvider));
MapSerivce(ref.watch(apiServiceProvider));

View File

@ -19,6 +19,8 @@ final mapServiceProvider = AutoDisposeProvider<MapSerivce>.internal(
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef MapServiceRef = AutoDisposeProviderRef<MapSerivce>; typedef MapServiceRef = AutoDisposeProviderRef<MapSerivce>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -23,4 +23,4 @@ final mapStateNotifierProvider =
typedef _$MapStateNotifier = Notifier<MapState>; typedef _$MapStateNotifier = Notifier<MapState>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -45,7 +45,7 @@ class PaginatedSearchNotifier extends StateNotifier<SearchResult> {
@riverpod @riverpod
Future<RenderList> paginatedSearchRenderList( Future<RenderList> paginatedSearchRenderList(
PaginatedSearchRenderListRef ref, Ref ref,
) { ) {
final result = ref.watch(paginatedSearchProvider); final result = ref.watch(paginatedSearchProvider);
final timelineService = ref.watch(timelineServiceProvider); final timelineService = ref.watch(timelineServiceProvider);

View File

@ -22,6 +22,8 @@ final paginatedSearchRenderListProvider =
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef PaginatedSearchRenderListRef = AutoDisposeFutureProviderRef<RenderList>; typedef PaginatedSearchRenderListRef = AutoDisposeFutureProviderRef<RenderList>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -1,3 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart'; import 'package:immich_mobile/interfaces/person_api.interface.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/services/person.service.dart'; import 'package:immich_mobile/services/person.service.dart';
@ -9,7 +10,7 @@ part 'people.provider.g.dart';
@riverpod @riverpod
Future<List<Person>> getAllPeople( Future<List<Person>> getAllPeople(
GetAllPeopleRef ref, Ref ref,
) async { ) async {
final PersonService personService = ref.read(personServiceProvider); final PersonService personService = ref.read(personServiceProvider);
@ -19,7 +20,7 @@ Future<List<Person>> getAllPeople(
} }
@riverpod @riverpod
Future<RenderList> personAssets(PersonAssetsRef ref, String personId) async { Future<RenderList> personAssets(Ref ref, String personId) async {
final PersonService personService = ref.read(personServiceProvider); final PersonService personService = ref.read(personServiceProvider);
final assets = await personService.getPersonAssets(personId); final assets = await personService.getPersonAssets(personId);
@ -31,7 +32,7 @@ Future<RenderList> personAssets(PersonAssetsRef ref, String personId) async {
@riverpod @riverpod
Future<bool> updatePersonName( Future<bool> updatePersonName(
UpdatePersonNameRef ref, Ref ref,
String personId, String personId,
String updatedName, String updatedName,
) async { ) async {

View File

@ -19,6 +19,8 @@ final getAllPeopleProvider = AutoDisposeFutureProvider<List<Person>>.internal(
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef GetAllPeopleRef = AutoDisposeFutureProviderRef<List<Person>>; typedef GetAllPeopleRef = AutoDisposeFutureProviderRef<List<Person>>;
String _$personAssetsHash() => r'3dfecb67a54d07e4208bcb9581b2625acd2e1832'; String _$personAssetsHash() => r'3dfecb67a54d07e4208bcb9581b2625acd2e1832';
@ -156,6 +158,8 @@ class PersonAssetsProvider extends AutoDisposeFutureProvider<RenderList> {
} }
} }
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin PersonAssetsRef on AutoDisposeFutureProviderRef<RenderList> { mixin PersonAssetsRef on AutoDisposeFutureProviderRef<RenderList> {
/// The parameter `personId` of this provider. /// The parameter `personId` of this provider.
String get personId; String get personId;
@ -296,6 +300,8 @@ class UpdatePersonNameProvider extends AutoDisposeFutureProvider<bool> {
} }
} }
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin UpdatePersonNameRef on AutoDisposeFutureProviderRef<bool> { mixin UpdatePersonNameRef on AutoDisposeFutureProviderRef<bool> {
/// The parameter `personId` of this provider. /// The parameter `personId` of this provider.
String get personId; String get personId;
@ -314,4 +320,4 @@ class _UpdatePersonNameProviderElement
String get updatedName => (origin as UpdatePersonNameProvider).updatedName; String get updatedName => (origin as UpdatePersonNameProvider).updatedName;
} }
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -1,3 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/services/search.service.dart'; import 'package:immich_mobile/services/search.service.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -6,7 +7,7 @@ part 'search_filter.provider.g.dart';
@riverpod @riverpod
Future<List<String>> getSearchSuggestions( Future<List<String>> getSearchSuggestions(
GetSearchSuggestionsRef ref, Ref ref,
SearchSuggestionType type, { SearchSuggestionType type, {
String? locationCountry, String? locationCountry,
String? locationState, String? locationState,

View File

@ -189,6 +189,8 @@ class GetSearchSuggestionsProvider
} }
} }
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin GetSearchSuggestionsRef on AutoDisposeFutureProviderRef<List<String>> { mixin GetSearchSuggestionsRef on AutoDisposeFutureProviderRef<List<String>> {
/// The parameter `type` of this provider. /// The parameter `type` of this provider.
SearchSuggestionType get type; SearchSuggestionType get type;
@ -226,4 +228,4 @@ class _GetSearchSuggestionsProviderElement
String? get model => (origin as GetSearchSuggestionsProvider).model; String? get model => (origin as GetSearchSuggestionsProvider).model;
} }
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -788,6 +788,53 @@ class FilterImageRouteArgs {
} }
} }
/// generated route for
/// [FolderPage]
class FolderRoute extends PageRouteInfo<FolderRouteArgs> {
FolderRoute({
Key? key,
RecursiveFolder? folder,
List<PageRouteInfo>? children,
}) : super(
FolderRoute.name,
args: FolderRouteArgs(
key: key,
folder: folder,
),
initialChildren: children,
);
static const String name = 'FolderRoute';
static PageInfo page = PageInfo(
name,
builder: (data) {
final args =
data.argsAs<FolderRouteArgs>(orElse: () => const FolderRouteArgs());
return FolderPage(
key: args.key,
folder: args.folder,
);
},
);
}
class FolderRouteArgs {
const FolderRouteArgs({
this.key,
this.folder,
});
final Key? key;
final RecursiveFolder? folder;
@override
String toString() {
return 'FolderRouteArgs{key: $key, folder: $folder}';
}
}
/// generated route for /// generated route for
/// [GalleryViewerPage] /// [GalleryViewerPage]
class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> { class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
@ -1175,40 +1222,6 @@ class PartnerRoute extends PageRouteInfo<void> {
); );
} }
/// manually written (with love) route for
/// [FolderPage]
class FolderRoute extends PageRouteInfo<FolderRouteArgs> {
FolderRoute({
RecursiveFolder? folder,
List<PageRouteInfo>? children,
}) : super(
FolderRoute.name,
args: FolderRouteArgs(folder: folder),
initialChildren: children,
);
static const String name = 'FolderRoute';
static PageInfo page = PageInfo(
name,
builder: (data) {
final args = data.argsAs<FolderRouteArgs>();
return FolderPage(folder: args.folder);
},
);
}
class FolderRouteArgs {
const FolderRouteArgs({this.folder});
final RecursiveFolder? folder;
@override
String toString() {
return 'FolderRouteArgs{folder: $folder}';
}
}
/// generated route for /// generated route for
/// [PeopleCollectionPage] /// [PeopleCollectionPage]
class PeopleCollectionRoute extends PageRouteInfo<void> { class PeopleCollectionRoute extends PageRouteInfo<void> {

View File

@ -1,7 +1,7 @@
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
import 'package:flutter_web_auth/flutter_web_auth.dart';
// Redirect URL = app.immich:///oauth-callback // Redirect URL = app.immich:///oauth-callback
@ -32,7 +32,7 @@ class OAuthService {
} }
Future<LoginResponseDto?> oAuthLogin(String oauthUrl) async { Future<LoginResponseDto?> oAuthLogin(String oauthUrl) async {
String result = await FlutterWebAuth.authenticate( String result = await FlutterWebAuth2.authenticate(
url: oauthUrl, url: oauthUrl,
callbackUrlScheme: callbackUrlScheme, callbackUrlScheme: callbackUrlScheme,
); );

View File

@ -1,3 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/asset_api.interface.dart'; import 'package:immich_mobile/interfaces/asset_api.interface.dart';
@ -11,7 +12,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'person.service.g.dart'; part 'person.service.g.dart';
@riverpod @riverpod
PersonService personService(PersonServiceRef ref) => PersonService( PersonService personService(Ref ref) => PersonService(
ref.watch(personApiRepositoryProvider), ref.watch(personApiRepositoryProvider),
ref.watch(assetApiRepositoryProvider), ref.watch(assetApiRepositoryProvider),
ref.read(assetRepositoryProvider), ref.read(assetRepositoryProvider),

View File

@ -20,6 +20,8 @@ final personServiceProvider = AutoDisposeProvider<PersonService>.internal(
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef PersonServiceRef = AutoDisposeProviderRef<PersonService>; typedef PersonServiceRef = AutoDisposeProviderRef<PersonService>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -58,12 +58,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
final bool success = final bool success =
await ref.watch(albumProvider.notifier).deleteAlbum(album); await ref.watch(albumProvider.notifier).deleteAlbum(album);
if (album.shared) { context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]));
context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]));
} else {
context
.navigateTo(const TabControllerRoute(children: [LibraryRoute()]));
}
if (!success) { if (!success) {
ImmichToast.show( ImmichToast.show(

View File

@ -332,7 +332,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
); );
} }
if (index != -1 && index < widget.renderList.elements.length) { if (index < widget.renderList.elements.length) {
// Not sure why the index is shifted, but it works. :3 // Not sure why the index is shifted, but it works. :3
_scrollToIndex(index + 1); _scrollToIndex(index + 1);
} else { } else {

View File

@ -44,7 +44,19 @@ class PeopleInfo extends ConsumerWidget {
} }
final curatedPeople = people final curatedPeople = people
?.map((p) => SearchCuratedContent(id: p.id, label: p.name)) ?.map(
(p) => SearchCuratedContent(
id: p.id,
label: p.name,
subtitle: p.birthDate != null
? "exif_bottom_sheet_person_age".tr(
args: [
_calculateAge(p.birthDate!).toString(),
],
)
: null,
),
)
.toList() ?? .toList() ??
[]; [];
@ -99,4 +111,17 @@ class PeopleInfo extends ConsumerWidget {
), ),
); );
} }
int _calculateAge(DateTime birthDate) {
DateTime today = DateTime.now();
int age = today.year - birthDate.year;
// Check if the birthday has occurred this year
if (today.month < birthDate.month ||
(today.month == birthDate.month && today.day < birthDate.day)) {
age--;
}
return age;
}
} }

View File

@ -5,6 +5,8 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/album/current_album.provider.dart'; import 'package:immich_mobile/providers/album/current_album.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/scroll_to_date_notifier.provider.dart';
import 'package:immich_mobile/providers/tab.provider.dart';
import 'package:immich_mobile/widgets/album/add_to_album_bottom_sheet.dart'; import 'package:immich_mobile/widgets/album/add_to_album_bottom_sheet.dart';
import 'package:immich_mobile/providers/asset_viewer/download.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/download.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
@ -95,6 +97,16 @@ class GalleryAppBar extends ConsumerWidget {
ref.read(downloadStateProvider.notifier).downloadAsset(asset, context); ref.read(downloadStateProvider.notifier).downloadAsset(asset, context);
} }
handleLocateAsset() async {
// Go back to the gallery
await context.maybePop();
await context
.navigateTo(const TabControllerRoute(children: [PhotosRoute()]));
ref.read(tabProvider.notifier).update((state) => state = TabEnum.home);
// Scroll to the asset's date
scrollToDateNotifierProvider.scrollToDate(asset.fileCreatedAt);
}
return IgnorePointer( return IgnorePointer(
ignoring: !showControls, ignoring: !showControls,
child: AnimatedOpacity( child: AnimatedOpacity(
@ -107,6 +119,7 @@ class GalleryAppBar extends ConsumerWidget {
isPartner: isPartner, isPartner: isPartner,
asset: asset, asset: asset,
onMoreInfoPressed: showInfo, onMoreInfoPressed: showInfo,
onLocatePressed: handleLocateAsset,
onFavorite: toggleFavorite, onFavorite: toggleFavorite,
onRestorePressed: () => handleRestore(asset), onRestorePressed: () => handleRestore(asset),
onUploadPressed: asset.isLocal ? () => handleUpload(asset) : null, onUploadPressed: asset.isLocal ? () => handleUpload(asset) : null,

View File

@ -5,6 +5,7 @@ import 'package:immich_mobile/providers/activity_statistics.provider.dart';
import 'package:immich_mobile/providers/album/current_album.provider.dart'; import 'package:immich_mobile/providers/album/current_album.provider.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/providers/tab.provider.dart';
import 'package:immich_mobile/widgets/asset_viewer/motion_photo_button.dart'; import 'package:immich_mobile/widgets/asset_viewer/motion_photo_button.dart';
class TopControlAppBar extends HookConsumerWidget { class TopControlAppBar extends HookConsumerWidget {
@ -13,6 +14,7 @@ class TopControlAppBar extends HookConsumerWidget {
required this.asset, required this.asset,
required this.onMoreInfoPressed, required this.onMoreInfoPressed,
required this.onDownloadPressed, required this.onDownloadPressed,
required this.onLocatePressed,
required this.onAddToAlbumPressed, required this.onAddToAlbumPressed,
required this.onRestorePressed, required this.onRestorePressed,
required this.onFavorite, required this.onFavorite,
@ -26,6 +28,7 @@ class TopControlAppBar extends HookConsumerWidget {
final Function onMoreInfoPressed; final Function onMoreInfoPressed;
final VoidCallback? onUploadPressed; final VoidCallback? onUploadPressed;
final VoidCallback? onDownloadPressed; final VoidCallback? onDownloadPressed;
final VoidCallback onLocatePressed;
final VoidCallback onAddToAlbumPressed; final VoidCallback onAddToAlbumPressed;
final VoidCallback onRestorePressed; final VoidCallback onRestorePressed;
final VoidCallback onActivitiesPressed; final VoidCallback onActivitiesPressed;
@ -54,6 +57,18 @@ class TopControlAppBar extends HookConsumerWidget {
); );
} }
Widget buildLocateButton() {
return IconButton(
onPressed: () {
onLocatePressed();
},
icon: Icon(
Icons.image_search,
color: Colors.grey[200],
),
);
}
Widget buildMoreInfoButton() { Widget buildMoreInfoButton() {
return IconButton( return IconButton(
onPressed: () { onPressed: () {
@ -159,6 +174,8 @@ class TopControlAppBar extends HookConsumerWidget {
shape: const Border(), shape: const Border(),
actions: [ actions: [
if (asset.isRemote && isOwner) buildFavoriteButton(a), if (asset.isRemote && isOwner) buildFavoriteButton(a),
if (isOwner && ref.read(tabProvider.notifier).state != TabEnum.home)
buildLocateButton(),
if (asset.livePhotoVideoId != null) const MotionPhotoButton(), if (asset.livePhotoVideoId != null) const MotionPhotoButton(),
if (asset.isLocal && !asset.isRemote) buildUploadButton(), if (asset.isLocal && !asset.isRemote) buildUploadButton(),
if (asset.isRemote && !asset.isLocal && isOwner) buildDownloadButton(), if (asset.isRemote && !asset.isLocal && isOwner) buildDownloadButton(),

View File

@ -41,10 +41,10 @@ class MapThumbnail extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final offsettedCentre = LatLng(centre.latitude + 0.002, centre.longitude); final offsettedCentre = LatLng(centre.latitude + 0.002, centre.longitude);
final controller = useRef<MaplibreMapController?>(null); final controller = useRef<MapLibreMapController?>(null);
final position = useValueNotifier<Point<num>?>(null); final position = useValueNotifier<Point<num>?>(null);
Future<void> onMapCreated(MaplibreMapController mapController) async { Future<void> onMapCreated(MapLibreMapController mapController) async {
controller.value = mapController; controller.value = mapController;
if (assetMarkerRemoteId != null) { if (assetMarkerRemoteId != null) {
// The iOS impl returns wrong toScreenLocation without the delay // The iOS impl returns wrong toScreenLocation without the delay
@ -73,7 +73,7 @@ class MapThumbnail extends HookConsumerWidget {
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
style.widgetWhen( style.widgetWhen(
onData: (style) => MaplibreMap( onData: (style) => MapLibreMap(
initialCameraPosition: initialCameraPosition:
CameraPosition(target: offsettedCentre, zoom: zoom), CameraPosition(target: offsettedCentre, zoom: zoom),
styleString: style, styleString: style,

View File

@ -86,12 +86,22 @@ class CuratedPeopleRow extends StatelessWidget {
).tr(), ).tr(),
); );
} }
return Text( return Column(
person.label, mainAxisSize: MainAxisSize.min,
textAlign: TextAlign.center, children: [
overflow: TextOverflow.ellipsis, Text(
style: context.textTheme.labelLarge, person.label,
maxLines: 2, textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: context.textTheme.labelLarge,
maxLines: 2,
),
if (person.subtitle != null)
Text(
person.subtitle!,
textAlign: TextAlign.center,
),
],
); );
} }
} }

View File

@ -358,6 +358,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.10" version: "0.7.10"
desktop_webview_window:
dependency: transitive
description:
name: desktop_webview_window
sha256: "57cf20d81689d5cbb1adfd0017e96b669398a669d927906073b0e42fc64111c0"
url: "https://pub.dev"
source: hosted
version: "0.2.3"
device_info_plus: device_info_plus:
dependency: "direct main" dependency: "direct main"
description: description:
@ -508,10 +516,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_hooks name: flutter_hooks
sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 sha256: b772e710d16d7a20c0740c4f855095026b31c7eb5ba3ab67d2bd52021cd9461d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.20.5" version: "0.21.2"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -577,10 +585,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_riverpod name: flutter_riverpod
sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d" sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.1" version: "2.6.1"
flutter_svg: flutter_svg:
dependency: "direct main" dependency: "direct main"
description: description:
@ -602,14 +610,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.0"
flutter_web_auth: flutter_web_auth_2:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_web_auth name: flutter_web_auth_2
sha256: "95e4856e24fb6ac1678f5ff334743b63f782d839ab324543d29ccbd295176209" sha256: "561c32d32ed537853de43852c35849cf1d37f3482f41f22b718ab6112f96b333"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.0" version: "5.0.0-alpha.0"
flutter_web_auth_2_platform_interface:
dependency: transitive
description:
name: flutter_web_auth_2_platform_interface
sha256: "45927587ebb2364cd273675ec95f6f67b81725754b416cef2b65cdc63fd3e853"
url: "https://pub.dev"
source: hosted
version: "5.0.0-alpha.0"
flutter_web_plugins: flutter_web_plugins:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -712,10 +728,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: hooks_riverpod name: hooks_riverpod
sha256: "45b2030a18bcd6dbd680c2c91bc3b33e3fe7c323e3acb5ecec93a613e2fbaa8a" sha256: "70bba33cfc5670c84b796e6929c54b8bc5be7d0fe15bb28c2560500b9ad06966"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.1" version: "2.6.1"
hotreloader: hotreloader:
dependency: transitive dependency: transitive
description: description:
@ -948,26 +964,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: maplibre_gl name: maplibre_gl
sha256: "9dd9eebee52f42a45aaa9cdb912afa47845c37007b26a799aa482ecd368804c8" sha256: cd0adf2da87149cab556ac70977783d6dcb3bd73b17a5583cc8366a5aafa46f8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.19.0+2" version: "0.21.0"
maplibre_gl_platform_interface: maplibre_gl_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: maplibre_gl_platform_interface name: maplibre_gl_platform_interface
sha256: a95fa38a3532253f32dfe181389adfe9f402773e58ac902d9c4efad3209e0903 sha256: "6db8234705e58c09b6fd5a43747a817ba1e6e91a76deb3ed057a36a994d86f22"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.19.0+2" version: "0.21.0"
maplibre_gl_web: maplibre_gl_web:
dependency: transitive dependency: transitive
description: description:
name: maplibre_gl_web name: maplibre_gl_web
sha256: "7f1540b384f16f3c9bc8b4ebdfca96fb07f6dab5d9ef4dd0e102985dba238691" sha256: e1cbe04594fdb0d76de7cd448c0048290df8dc69dc37a85d23307dd595779141
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.19.0+2" version: "0.21.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -1276,42 +1292,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: riverpod name: riverpod
sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.1" version: "2.6.1"
riverpod_analyzer_utils: riverpod_analyzer_utils:
dependency: transitive dependency: transitive
description: description:
name: riverpod_analyzer_utils name: riverpod_analyzer_utils
sha256: ee72770090078e6841d51355292335f1bc254907c6694283389dcb8156d99a4d sha256: "0dcb0af32d561f8fa000c6a6d95633c9fb08ea8a8df46e3f9daca59f11218167"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.3" version: "0.5.6"
riverpod_annotation: riverpod_annotation:
dependency: "direct main" dependency: "direct main"
description: description:
name: riverpod_annotation name: riverpod_annotation
sha256: e5e796c0eba4030c704e9dae1b834a6541814963292839dcf9638d53eba84f5c sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.5" version: "2.6.1"
riverpod_generator: riverpod_generator:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: riverpod_generator name: riverpod_generator
sha256: "1ad626afbd8b01d168870b13c0b036f8a5bdb57c14cd426dc5b4595466bd6e2f" sha256: "851aedac7ad52693d12af3bf6d92b1626d516ed6b764eb61bf19e968b5e0b931"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.6.1"
riverpod_lint: riverpod_lint:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: riverpod_lint name: riverpod_lint
sha256: b95a8cdc6102397f7d51037131c25ce7e51be900be021af4bf0c2d6f1b8f7aa7 sha256: "0684c21a9a4582c28c897d55c7b611fa59a351579061b43f8c92c005804e63a8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.12" version: "2.6.1"
rxdart: rxdart:
dependency: transitive dependency: transitive
description: description:
@ -1813,6 +1829,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.4" version: "1.1.4"
window_to_front:
dependency: transitive
description:
name: window_to_front
sha256: "7aef379752b7190c10479e12b5fd7c0b9d92adc96817d9e96c59937929512aee"
url: "https://pub.dev"
source: hosted
version: "0.0.3"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -1846,5 +1870,5 @@ packages:
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.5.3 <4.0.0" dart: ">=3.5.0 <4.0.0"
flutter: ">=3.24.5" flutter: ">=3.24.5"

View File

@ -17,16 +17,16 @@ dependencies:
path_provider_ios: path_provider_ios:
photo_manager: ^3.6.1 photo_manager: ^3.6.1
photo_manager_image_provider: ^2.2.0 photo_manager_image_provider: ^2.2.0
flutter_hooks: ^0.20.4 flutter_hooks: ^0.21.2
hooks_riverpod: ^2.4.9 hooks_riverpod: ^2.6.1
riverpod_annotation: ^2.3.3 riverpod_annotation: ^2.6.1
cached_network_image: ^3.3.1 cached_network_image: ^3.3.1
flutter_cache_manager: ^3.3.1 flutter_cache_manager: ^3.3.1
intl: ^0.19.0 intl: ^0.19.0
auto_route: ^9.2.0 auto_route: ^9.2.0
fluttertoast: ^8.2.4 fluttertoast: ^8.2.4
socket_io_client: ^2.0.3+1 socket_io_client: ^2.0.3+1
maplibre_gl: 0.19.0+2 maplibre_gl: ^0.21.0
geolocator: ^11.0.0 # used to move to current location in map view geolocator: ^11.0.0 # used to move to current location in map view
flutter_udid: ^3.0.0 flutter_udid: ^3.0.0
flutter_svg: ^2.0.9 flutter_svg: ^2.0.9
@ -42,7 +42,7 @@ dependencies:
path_provider: ^2.1.2 path_provider: ^2.1.2
collection: ^1.18.0 collection: ^1.18.0
http_parser: ^4.0.2 http_parser: ^4.0.2
flutter_web_auth: 0.6.0 flutter_web_auth_2: ^5.0.0-alpha.0
easy_image_viewer: ^1.4.0 easy_image_viewer: ^1.4.0
isar: isar:
version: *isar_version version: *isar_version
@ -108,8 +108,8 @@ dev_dependencies:
integration_test: integration_test:
sdk: flutter sdk: flutter
custom_lint: ^0.6.4 custom_lint: ^0.6.4
riverpod_lint: ^2.3.7 riverpod_lint: ^2.6.1
riverpod_generator: ^2.3.9 riverpod_generator: ^2.6.1
mocktail: ^1.0.3 mocktail: ^1.0.3
immich_mobile_immich_lint: immich_mobile_immich_lint:
path: './immich_lint' path: './immich_lint'

View File

@ -33,9 +33,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.7.3", "version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {

View File

@ -65,7 +65,7 @@ https://immich.app
https://demo.immich.app https://demo.immich.app
بالنسبة لتطبيق الهاتف المحمول، يمكنك استخدام بالنسبة لتطبيق الهاتف المحمول، يمكنك استخدام
`https://demo.immich.app/api` `https://demo.immich.app`
ل `نقطة نهاية الخادم` ل `نقطة نهاية الخادم`
```bash title="Demo Credential" ```bash title="Demo Credential"

View File

@ -60,7 +60,7 @@ Podeu trobar la documentació principal, incloent les guies d'instal·lació, a
Podeu accedir a la demostració web a https://demo.immich.app Podeu accedir a la demostració web a https://demo.immich.app
Per a l'aplicació mòbil, podeu utilitzar `https://demo.immich.app/api` com a "URL de punt final del servidor". Per a l'aplicació mòbil, podeu utilitzar `https://demo.immich.app` com a "URL de punt final del servidor".
```bash title="Credencials de la demo" ```bash title="Credencials de la demo"
Les credencials Les credencials

View File

@ -64,7 +64,7 @@
Die Web-Demo kannst Du unter https://demo.immich.app finden. Die Web-Demo kannst Du unter https://demo.immich.app finden.
Die Demo läuft auf einer Free Tier Oracle VM in Amsterdam mit einer 2.4Ghz Quad-Core ARM64 CPU und 24GB RAM. Die Demo läuft auf einer Free Tier Oracle VM in Amsterdam mit einer 2.4Ghz Quad-Core ARM64 CPU und 24GB RAM.
Für die Handy-App kannst Du `https://demo.immich.app/api` als `Server Endpoint URL` angeben. Für die Handy-App kannst Du `https://demo.immich.app` als `Server Endpoint URL` angeben.
### Login Daten ### Login Daten

View File

@ -61,7 +61,7 @@ Puedes encontrar la documentación oficial, incluidas las guías de instalación
Puedes acceder a la demostración web en <https://demo.immich.app> Puedes acceder a la demostración web en <https://demo.immich.app>
Para la aplicación móvil, puedes usar `https://demo.immich.app/api` en la `URL del servidor`. Para la aplicación móvil, puedes usar `https://demo.immich.app` en la `URL del servidor`.
```bash title="Credenciales de la demo" ```bash title="Credenciales de la demo"
Credenciales Credenciales

View File

@ -61,7 +61,7 @@ Vous pouvez trouver la documentation principale ainsi que les guides d'installat
Vous pouvez accéder à la démo en ligne sur https://demo.immich.app Vous pouvez accéder à la démo en ligne sur https://demo.immich.app
Pour l'application mobile, vous pouvez utiliser `https://demo.immich.app/api` dans le champ `URL du point d'accès au serveur` Pour l'application mobile, vous pouvez utiliser `https://demo.immich.app` dans le champ `URL du point d'accès au serveur`
```bash title="Identifiants pour la démo" ```bash title="Identifiants pour la démo"
Les identifiants Les identifiants

View File

@ -61,7 +61,7 @@ La documentazione ufficiale, inclusa la guida all'installazione, è disponibile
Prova la demo del progetto https://demo.immich.app Prova la demo del progetto https://demo.immich.app
Sull'app mobile, imposta `https://demo.immich.app/api` come `Server Endpoint URL` Sull'app mobile, imposta `https://demo.immich.app` come `Server Endpoint URL`
```bash title="Demo Credential" ```bash title="Demo Credential"
Credenziali di accesso Credenziali di accesso

View File

@ -60,7 +60,7 @@
web デモは https://demo.immich.app からアクセスできます web デモは https://demo.immich.app からアクセスできます
モバイルアプリの場合、`Server Endpoint URL` には `https://demo.immich.app/api` を使用することができます モバイルアプリの場合、`Server Endpoint URL` には `https://demo.immich.app` を使用することができます
```bash title="Demo Credential" ```bash title="Demo Credential"
The credential The credential

View File

@ -63,7 +63,7 @@
[이곳](https://demo.immich.app)에서 데모를 체험해보세요. 데모 서버는 2.4Ghz 쿼드 코어 ARM64 CPU 및 24GB 램으로 구성된 Oracle Free-tier VM 암스테르담 리전에서 구동됩니다. [이곳](https://demo.immich.app)에서 데모를 체험해보세요. 데모 서버는 2.4Ghz 쿼드 코어 ARM64 CPU 및 24GB 램으로 구성된 Oracle Free-tier VM 암스테르담 리전에서 구동됩니다.
모바일 앱의 경우, `서버 엔드포인트 URL``https://demo.immich.app/api`를 입력하세요. 모바일 앱의 경우, `서버 엔드포인트 URL``https://demo.immich.app`를 입력하세요.
### 로그인 정보 ### 로그인 정보

View File

@ -61,7 +61,7 @@ De belangrijkste documentatie, inclusief installatie handleidingen, zijn te vind
Je kunt de demo [hier](https://demo.immich.app/) bekijken. De demo server is actief op een Free-tier Oracle VM in Amsterdam met een 2.4GHz quad-core ARM64 CPU en 24GB RAM. Je kunt de demo [hier](https://demo.immich.app/) bekijken. De demo server is actief op een Free-tier Oracle VM in Amsterdam met een 2.4GHz quad-core ARM64 CPU en 24GB RAM.
Voor de mobiele app kun je gebruik maken van `https://demo.immich.app/api` voor de `Server Endpoint URL` Voor de mobiele app kun je gebruik maken van `https://demo.immich.app` voor de `Server Endpoint URL`
### Login gegevens ### Login gegevens

View File

@ -71,7 +71,7 @@ hospedada no Nível Gratuito da Oracle VM em Amsterdam com um processador 2.4Ghz
quad-core ARM64 e 24GB de RAM. quad-core ARM64 e 24GB de RAM.
No aplicativo para dispositivos móveis, você pode usar No aplicativo para dispositivos móveis, você pode usar
`https://demo.immich.app/api` no campo `Server Endpoint URL` `https://demo.immich.app` no campo `Server Endpoint URL`
### Credenciais de login ### Credenciais de login

View File

@ -64,7 +64,7 @@
Вы можете опробовать [Web демонстрационную версию](https://demo.immich.app/) Вы можете опробовать [Web демонстрационную версию](https://demo.immich.app/)
В мобильном приложении укажите `https://demo.immich.app/api` в поле `URL-адрес сервера` В мобильном приложении укажите `https://demo.immich.app` в поле `URL-адрес сервера`
### Данные для входа ### Данные для входа

View File

@ -62,7 +62,7 @@ Dokumentation och installationsguider hittas på https://imiich.app/.
Ett webb-demo finns att testa på https://demo.immich.app Ett webb-demo finns att testa på https://demo.immich.app
Använd `https://demo.immich.app/api` i mobilappen som `Server Endpoint URL` Använd `https://demo.immich.app` i mobilappen som `Server Endpoint URL`
```bash title="Inloggningsuppgifter För Demo" ```bash title="Inloggningsuppgifter För Demo"
Inloggsningsuppgifter Inloggsningsuppgifter

View File

@ -65,7 +65,7 @@
เข้าถึงการสาธิตได้ [ที่นี่](https://demo.immich.app) โดยการสาธิตนี้ทำงานบน Oracle VM Free-tier ตั้งอยู่ที่อัมสเตอร์ดัม ใช้ซีพียู ARM64 quad-core 2.4Ghz และแรม 24GB เข้าถึงการสาธิตได้ [ที่นี่](https://demo.immich.app) โดยการสาธิตนี้ทำงานบน Oracle VM Free-tier ตั้งอยู่ที่อัมสเตอร์ดัม ใช้ซีพียู ARM64 quad-core 2.4Ghz และแรม 24GB
สำหรับแอปมือถือ คุณสามารถใช้ `https://demo.immich.app/api` เป็น `Server Endpoint URL` สำหรับแอปมือถือ คุณสามารถใช้ `https://demo.immich.app` เป็น `Server Endpoint URL`
### ข้อมูลการเข้าสู่ระบบ ### ข้อมูลการเข้าสู่ระบบ

View File

@ -60,7 +60,7 @@ Kurulum dahil olmak üzere resmi belgeleri https://immich.app/ adresinde bulabil
Web demo adresi: https://demo.immich.app Web demo adresi: https://demo.immich.app
Mobil uygulama için `Server Endpoint URL` olarak `https://demo.immich.app/api` adresini kullanabilirsiniz. Mobil uygulama için `Server Endpoint URL` olarak `https://demo.immich.app` adresini kullanabilirsiniz.
```bash title="Demo Bilgileri" ```bash title="Demo Bilgileri"
Giriş bilgileri: Giriş bilgileri:

View File

@ -63,7 +63,7 @@
Доступ до демо-версії [тут](https://demo.immich.app). Демоверсія працює на безкоштовному Oracle VM у Амстердамі з чотириядерним ARM64 процесором (2.4 ГГц) і 24 ГБ оперативної пам’яті. Доступ до демо-версії [тут](https://demo.immich.app). Демоверсія працює на безкоштовному Oracle VM у Амстердамі з чотириядерним ARM64 процесором (2.4 ГГц) і 24 ГБ оперативної пам’яті.
Для мобільного додатку ви можете використовувати `https://demo.immich.app/api` в якості `Server Endpoint URL`. Для мобільного додатку ви можете використовувати `https://demo.immich.app` в якості `Server Endpoint URL`.
### Облікові дані для входу ### Облікові дані для входу

View File

@ -65,7 +65,7 @@
Truy cập bản demo [tại đây](https://demo.immich.app). Bản demo đang chạy trên máy ảo Oracle Free-tier ở Amsterdam với CPU ARM64 lõi tứ 2,4 GHz và RAM 24 GB. Truy cập bản demo [tại đây](https://demo.immich.app). Bản demo đang chạy trên máy ảo Oracle Free-tier ở Amsterdam với CPU ARM64 lõi tứ 2,4 GHz và RAM 24 GB.
Đối với ứng dụng di động, bạn có thể sử dụng `https://demo.immich.app/api` cho `Server Endpoint URL` Đối với ứng dụng di động, bạn có thể sử dụng `https://demo.immich.app` cho `Server Endpoint URL`
### Thông tin đăng nhập ### Thông tin đăng nhập

View File

@ -67,7 +67,7 @@
您可以在[此处](https://demo.immich.app)访问在线演示网站。该示例网站运行的机器配置为:甲骨文免费虚拟机套餐——阿姆斯特丹 4核 2.4Ghz ARM64 CPU24GB RAM。 您可以在[此处](https://demo.immich.app)访问在线演示网站。该示例网站运行的机器配置为:甲骨文免费虚拟机套餐——阿姆斯特丹 4核 2.4Ghz ARM64 CPU24GB RAM。
在移动端,您可以使用 `https://demo.immich.app/api` 作为 `服务终端链接` 在移动端,您可以使用 `https://demo.immich.app` 作为 `服务终端链接`
### 登录认证信息 ### 登录认证信息

3430
server/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -58,7 +58,8 @@
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"cookie-parser": "^1.4.6", "cookie": "^1.0.2",
"cookie-parser": "^1.4.7",
"exiftool-vendored": "^28.3.1", "exiftool-vendored": "^28.3.1",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"fluent-ffmpeg": "^2.1.2", "fluent-ffmpeg": "^2.1.2",
@ -107,7 +108,7 @@
"@types/archiver": "^6.0.0", "@types/archiver": "^6.0.0",
"@types/async-lock": "^1.4.2", "@types/async-lock": "^1.4.2",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/cookie-parser": "^1.4.3", "@types/cookie-parser": "^1.4.8",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/fluent-ffmpeg": "^2.1.21", "@types/fluent-ffmpeg": "^2.1.21",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
@ -131,7 +132,7 @@
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^56.0.1", "eslint-plugin-unicorn": "^56.0.1",
"globals": "^16.0.0", "globals": "^16.0.0",
"kysely-codegen": "^0.17.0", "kysely-codegen": "^0.18.0",
"mock-fs": "^5.2.0", "mock-fs": "^5.2.0",
"node-addon-api": "^8.3.0", "node-addon-api": "^8.3.0",
"pngjs": "^7.0.0", "pngjs": "^7.0.0",

View File

@ -4,13 +4,13 @@ import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto, APIKeyUpda
import { AuthDto } from 'src/dtos/auth.dto'; import { AuthDto } from 'src/dtos/auth.dto';
import { Permission } from 'src/enum'; import { Permission } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { APIKeyService } from 'src/services/api-key.service'; import { ApiKeyService } from 'src/services/api-key.service';
import { UUIDParamDto } from 'src/validation'; import { UUIDParamDto } from 'src/validation';
@ApiTags('API Keys') @ApiTags('API Keys')
@Controller('api-keys') @Controller('api-keys')
export class APIKeyController { export class APIKeyController {
constructor(private service: APIKeyService) {} constructor(private service: ApiKeyService) {}
@Post() @Post()
@Authenticated({ permission: Permission.API_KEY_CREATE }) @Authenticated({ permission: Permission.API_KEY_CREATE })

View File

@ -1,5 +1,5 @@
import { sql } from 'kysely'; import { sql } from 'kysely';
import { Permission } from 'src/enum'; import { AssetStatus, AssetType, Permission } from 'src/enum';
export type AuthUser = { export type AuthUser = {
id: string; id: string;
@ -10,11 +10,74 @@ export type AuthUser = {
quotaSizeInBytes: number | null; quotaSizeInBytes: number | null;
}; };
export type Library = {
id: string;
ownerId: string;
createdAt: Date;
updatedAt: Date;
updateId: string;
name: string;
importPaths: string[];
exclusionPatterns: string[];
deletedAt: Date | null;
refreshedAt: Date | null;
assets?: Asset[];
};
export type AuthApiKey = { export type AuthApiKey = {
id: string; id: string;
permissions: Permission[]; permissions: Permission[];
}; };
export type ApiKey = {
id: string;
name: string;
userId: string;
createdAt: Date;
updatedAt: Date;
permissions: Permission[];
};
export type User = {
id: string;
name: string;
email: string;
profileImagePath: string;
profileChangedAt: Date;
};
export type Asset = {
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
id: string;
updateId: string;
status: AssetStatus;
checksum: Buffer<ArrayBufferLike>;
deviceAssetId: string;
deviceId: string;
duplicateId: string | null;
duration: string | null;
encodedVideoPath: string | null;
fileCreatedAt: Date | null;
fileModifiedAt: Date | null;
isArchived: boolean;
isExternal: boolean;
isFavorite: boolean;
isOffline: boolean;
isVisible: boolean;
libraryId: string | null;
livePhotoVideoId: string | null;
localDateTime: Date | null;
originalFileName: string;
originalPath: string;
ownerId: string;
sidecarPath: string | null;
stackId: string | null;
thumbhash: Buffer<ArrayBufferLike> | null;
type: AssetType;
};
export type AuthSharedLink = { export type AuthSharedLink = {
id: string; id: string;
expiresAt: Date | null; expiresAt: Date | null;

7
server/src/db.d.ts vendored
View File

@ -4,7 +4,8 @@
*/ */
import type { ColumnType } from 'kysely'; import type { ColumnType } from 'kysely';
import { AssetType, Permission, SyncEntityType } from 'src/enum'; import { OnThisDayData } from 'src/entities/memory.entity';
import { AssetType, MemoryType, Permission, SyncEntityType } from 'src/enum';
export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[] ? U[] : ArrayTypeImpl<T>; export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[] ? U[] : ArrayTypeImpl<T>;
@ -240,7 +241,7 @@ export interface Libraries {
export interface Memories { export interface Memories {
createdAt: Generated<Timestamp>; createdAt: Generated<Timestamp>;
data: Json; data: OnThisDayData;
deletedAt: Timestamp | null; deletedAt: Timestamp | null;
hideAt: Timestamp | null; hideAt: Timestamp | null;
id: Generated<string>; id: Generated<string>;
@ -249,7 +250,7 @@ export interface Memories {
ownerId: string; ownerId: string;
seenAt: Timestamp | null; seenAt: Timestamp | null;
showAt: Timestamp | null; showAt: Timestamp | null;
type: string; type: MemoryType;
updatedAt: Generated<Timestamp>; updatedAt: Generated<Timestamp>;
updateId: Generated<string>; updateId: Generated<string>;
} }

View File

@ -1,6 +1,6 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { ArrayMaxSize, ArrayUnique, IsNotEmpty, IsString } from 'class-validator'; import { ArrayMaxSize, ArrayUnique, IsNotEmpty, IsString } from 'class-validator';
import { LibraryEntity } from 'src/entities/library.entity'; import { Library } from 'src/database';
import { Optional, ValidateUUID } from 'src/validation'; import { Optional, ValidateUUID } from 'src/validation';
export class CreateLibraryDto { export class CreateLibraryDto {
@ -120,7 +120,7 @@ export class LibraryStatsResponseDto {
usage = 0; usage = 0;
} }
export function mapLibrary(entity: LibraryEntity): LibraryResponseDto { export function mapLibrary(entity: Library): LibraryResponseDto {
let assetCount = 0; let assetCount = 0;
if (entity.assets) { if (entity.assets) {
assetCount = entity.assets.length; assetCount = entity.assets.length;

View File

@ -7,6 +7,7 @@ import { AuthDto } from 'src/dtos/auth.dto';
import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetFaceEntity } from 'src/entities/asset-face.entity';
import { PersonEntity } from 'src/entities/person.entity'; import { PersonEntity } from 'src/entities/person.entity';
import { SourceType } from 'src/enum'; import { SourceType } from 'src/enum';
import { asDateString } from 'src/utils/date';
import { import {
IsDateStringFormat, IsDateStringFormat,
MaxDateString, MaxDateString,
@ -32,7 +33,7 @@ export class PersonCreateDto {
@MaxDateString(() => DateTime.now(), { message: 'Birth date cannot be in the future' }) @MaxDateString(() => DateTime.now(), { message: 'Birth date cannot be in the future' })
@IsDateStringFormat('yyyy-MM-dd') @IsDateStringFormat('yyyy-MM-dd')
@Optional({ nullable: true }) @Optional({ nullable: true })
birthDate?: string | null; birthDate?: Date | null;
/** /**
* Person visibility * Person visibility
@ -222,7 +223,7 @@ export function mapPerson(person: PersonEntity): PersonResponseDto {
return { return {
id: person.id, id: person.id,
name: person.name, name: person.name,
birthDate: person.birthDate, birthDate: asDateString(person.birthDate),
thumbnailPath: person.thumbnailPath, thumbnailPath: person.thumbnailPath,
isHidden: person.isHidden, isHidden: person.isHidden,
isFavorite: person.isFavorite, isFavorite: person.isFavorite,

View File

@ -26,7 +26,7 @@ export class LibraryEntity {
assets!: AssetEntity[]; assets!: AssetEntity[];
@ManyToOne(() => UserEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) @ManyToOne(() => UserEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
owner!: UserEntity; owner?: UserEntity;
@Column() @Column()
ownerId!: string; ownerId!: string;

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