mirror of
https://github.com/immich-app/immich.git
synced 2025-03-11 15:09:45 +02:00
Merge branch 'main' into new-asset-sync
This commit is contained in:
commit
d3de6475bd
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@ -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 written tests for new code (if applicable)
|
||||
- [ ] 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/repositories/` is pretty basic/simple and does not have any immich specific logic (that belongs in `src/services`)
|
||||
- [ ] 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/`)
|
||||
|
6
.github/workflows/prepare-release.yml
vendored
6
.github/workflows/prepare-release.yml
vendored
@ -41,8 +41,8 @@ jobs:
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
- name: Install Poetry
|
||||
run: pipx install poetry
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
|
||||
- name: Bump version
|
||||
run: misc/release/pump-version.sh -s "${{ inputs.serverBump }}" -m "${{ inputs.mobileBump }}"
|
||||
@ -74,7 +74,7 @@ jobs:
|
||||
with:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
|
21
.github/workflows/test.yml
vendored
21
.github/workflows/test.yml
vendored
@ -380,27 +380,28 @@ jobs:
|
||||
working-directory: ./machine-learning
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install poetry
|
||||
run: pipx install poetry
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.11
|
||||
cache: 'poetry'
|
||||
# TODO: add caching when supported (https://github.com/actions/setup-python/pull/818)
|
||||
# with:
|
||||
# python-version: 3.11
|
||||
# cache: 'uv'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
poetry install --with dev --with cpu
|
||||
uv sync --extra cpu
|
||||
- name: Lint with ruff
|
||||
run: |
|
||||
poetry run ruff check --output-format=github app export
|
||||
uv run ruff check --output-format=github app export
|
||||
- name: Check black formatting
|
||||
run: |
|
||||
poetry run black --check app export
|
||||
uv run black --check app export
|
||||
- name: Run mypy type checking
|
||||
run: |
|
||||
poetry run mypy --install-types --non-interactive --strict app/
|
||||
uv run mypy --strict app/
|
||||
- name: Run tests and coverage
|
||||
run: |
|
||||
poetry run pytest app --cov=app --cov-report term-missing
|
||||
uv run pytest app --cov=app --cov-report term-missing
|
||||
|
||||
shellcheck:
|
||||
name: ShellCheck
|
||||
|
@ -1,11 +1,11 @@
|
||||
<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://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"/>
|
||||
</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</p>
|
||||
|
||||
<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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -25,7 +25,7 @@ services:
|
||||
context: ../
|
||||
dockerfile: server/Dockerfile
|
||||
target: dev
|
||||
restart: always
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ../server:/usr/src/app
|
||||
- ../open-api:/usr/src/open-api
|
||||
|
@ -117,7 +117,7 @@ See [Backup and Restore](/docs/administration/backup-and-restore.md).
|
||||
|
||||
### 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?
|
||||
|
||||
|
@ -53,7 +53,7 @@ docker compose create # Create Docker containers for Immich apps witho
|
||||
docker start immich_postgres # Start Postgres server
|
||||
sleep 10 # Wait for Postgres server to start up
|
||||
# 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" \
|
||||
| docker exec -i immich_postgres psql --dbname=postgres --username=<DB_USERNAME> # Restore Backup
|
||||
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
|
||||
sleep 10 # Wait for Postgres server to start up
|
||||
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`
|
||||
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>
|
||||
# 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>
|
||||
exit # Exit the Docker shell
|
||||
docker compose up -d # Start remainder of Immich apps
|
||||
```
|
||||
|
@ -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:
|
||||
|
||||
- `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
|
||||
src={require('./img/unraid05.webp').default}
|
||||
|
6
docs/package-lock.json
generated
6
docs/package-lock.json
generated
@ -18377,9 +18377,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
|
||||
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
|
@ -201,7 +201,7 @@ describe('/people', () => {
|
||||
expect(body).toMatchObject({
|
||||
id: expect.any(String),
|
||||
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}`)
|
||||
.send({ birthDate: '1990-01-01' });
|
||||
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 () => {
|
||||
|
@ -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_migration": "Storage template migration",
|
||||
"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_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>.",
|
||||
@ -987,6 +987,7 @@
|
||||
"permanently_deleted_asset": "Permanently deleted asset",
|
||||
"permanently_deleted_assets_count": "Permanently deleted {count, plural, one {# asset} other {# assets}}",
|
||||
"person": "Person",
|
||||
"person_birthdate": "Born on {date}",
|
||||
"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.",
|
||||
"photos": "Photos",
|
||||
|
@ -19,20 +19,16 @@ FROM builder-${DEVICE} AS builder
|
||||
|
||||
ARG DEVICE
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PIP_NO_CACHE_DIR=true \
|
||||
VIRTUAL_ENV="/opt/venv" \
|
||||
PATH="/opt/venv/bin:${PATH}"
|
||||
PYTHONUNBUFFERED=1
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends g++
|
||||
|
||||
RUN pip install --upgrade pip && pip install poetry
|
||||
RUN poetry config installer.max-workers 10 && \
|
||||
poetry config virtualenvs.create false
|
||||
RUN python3 -m venv /opt/venv
|
||||
|
||||
COPY poetry.lock pyproject.toml ./
|
||||
RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||
--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
|
||||
|
||||
FROM python:3.11-slim-bookworm@sha256:614c8691ab74150465ec9123378cd4dde7a6e57be9e558c3108df40664667a4c AS prod-cpu
|
||||
|
||||
@ -93,7 +89,7 @@ WORKDIR /usr/src/app
|
||||
ENV TRANSFORMERS_CACHE=/cache \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PATH="/opt/venv/bin:$PATH" \
|
||||
PATH="/usr/src/app/.venv/bin:$PATH" \
|
||||
PYTHONPATH=/usr/src \
|
||||
DEVICE=${DEVICE}
|
||||
|
||||
@ -102,7 +98,7 @@ RUN echo "hard core 0" >> /etc/security/limits.conf && \
|
||||
echo "fs.suid_dumpable 0" >> /etc/sysctl.conf && \
|
||||
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 start.sh log_conf.json gunicorn_conf.py ./
|
||||
COPY app .
|
||||
|
@ -5,13 +5,12 @@
|
||||
|
||||
# Setup
|
||||
|
||||
This project uses [Poetry](https://python-poetry.org/docs/#installation), so be sure to install it first.
|
||||
Running `poetry install --no-root --with dev --with cpu` will install everything you need in an isolated virtual environment.
|
||||
CUDA and OpenVINO are supported as acceleration APIs. To use them, you can replace `--with cpu` with either of `--with cuda` or `--with openvino`. In the case of CUDA, a [compute capability](https://developer.nvidia.com/cuda-gpus) of 5.2 or higher is required.
|
||||
|
||||
To add or remove dependencies, you can use the commands `poetry add $PACKAGE_NAME` and `poetry remove $PACKAGE_NAME`, respectively.
|
||||
Be sure to commit the `poetry.lock` and `pyproject.toml` files with `poetry lock --no-update` to reflect any changes in dependencies.
|
||||
This project uses [uv](https://docs.astral.sh/uv/getting-started/installation/), so be sure to install it first.
|
||||
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 `--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 `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
|
||||
|
||||
@ -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.
|
||||
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.
|
||||
|
||||
# Facial Recognition
|
||||
|
||||
## 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.
|
||||
|
||||
### Used Models
|
||||
* antelopev2
|
||||
* buffalo_l
|
||||
* buffalo_m
|
||||
* buffalo_s
|
||||
|
||||
- antelopev2
|
||||
- buffalo_l
|
||||
- buffalo_m
|
||||
- buffalo_s
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
3738
machine-learning/poetry.lock
generated
3738
machine-learning/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,72 +1,77 @@
|
||||
[tool.poetry]
|
||||
[project]
|
||||
name = "machine-learning"
|
||||
version = "1.129.0"
|
||||
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"
|
||||
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]
|
||||
python = ">=3.10,<4.0"
|
||||
insightface = ">=0.7.3,<1.0"
|
||||
opencv-python-headless = ">=4.7.0.72,<5.0"
|
||||
pillow = ">=9.5.0,<11.0"
|
||||
fastapi = ">=0.95.2,<1.0"
|
||||
uvicorn = {extras = ["standard"], version = ">=0.22.0,<1.0"}
|
||||
pydantic = "^2.0.0"
|
||||
pydantic-settings = "^2.5.2"
|
||||
aiocache = ">=0.12.1,<1.0"
|
||||
rich = ">=13.4.2"
|
||||
ftfy = ">=6.1.1"
|
||||
python-multipart = ">=0.0.6,<1.0"
|
||||
orjson = ">=3.9.5"
|
||||
gunicorn = ">=21.1.0"
|
||||
huggingface-hub = ">=0.20.1,<1.0"
|
||||
tokenizers = ">=0.15.0,<1.0"
|
||||
[dependency-groups]
|
||||
test = [
|
||||
"httpx>=0.24.1",
|
||||
"pytest>=7.3.1",
|
||||
"pytest-asyncio>=0.21.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
"pytest-mock>=3.11.1",
|
||||
]
|
||||
types = [
|
||||
"types-pyyaml>=6.0.12.20241230",
|
||||
"types-requests>=2.32.0.20250306",
|
||||
"types-setuptools>=75.8.2.20250305",
|
||||
"types-simplejson>=3.20.0.20250218",
|
||||
"types-ujson>=5.10.0.20240515",
|
||||
]
|
||||
lint = [
|
||||
"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]
|
||||
mypy = ">=1.3.0"
|
||||
black = ">=23.3.0"
|
||||
pytest = ">=7.3.1"
|
||||
locust = ">=2.15.1"
|
||||
httpx = ">=0.24.1"
|
||||
pytest-asyncio = ">=0.21.0"
|
||||
pytest-cov = ">=4.1.0"
|
||||
ruff = ">=0.0.272"
|
||||
pytest-mock = ">=3.11.1"
|
||||
[project.optional-dependencies]
|
||||
cpu = ["onnxruntime>=1.15.0,<2"]
|
||||
cuda = ["onnxruntime-gpu>=1.17.0,<2"]
|
||||
openvino = ["onnxruntime-openvino>=1.17.1,<1.19.0"]
|
||||
armnn = ["onnxruntime>=1.15.0,<2"]
|
||||
|
||||
[tool.poetry.group.cpu]
|
||||
optional = true
|
||||
[tool.uv]
|
||||
compile-bytecode = true
|
||||
|
||||
[tool.poetry.group.cpu.dependencies]
|
||||
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]]
|
||||
[[tool.uv.index]]
|
||||
name = "cuda12"
|
||||
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]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
|
2648
machine-learning/uv.lock
generated
Normal file
2648
machine-learning/uv.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -73,7 +73,7 @@ if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
|
||||
npm --prefix web i --package-lock-only
|
||||
npm --prefix e2e version "$SERVER_PUMP"
|
||||
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
|
||||
|
||||
if [ "$CURRENT_MOBILE" != "$NEXT_MOBILE" ]; then
|
||||
|
@ -93,9 +93,9 @@
|
||||
|
||||
|
||||
<activity
|
||||
android:name="com.linusu.flutter_web_auth.CallbackActivity"
|
||||
android:name="com.linusu.flutter_web_auth_2.CallbackActivity"
|
||||
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" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
@ -221,7 +221,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
|
||||
.setContentTitle(title)
|
||||
.setTicker(title)
|
||||
.setContentText(content)
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setSmallIcon(R.drawable.notification_icon)
|
||||
.build()
|
||||
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
|
||||
if (builder == null) {
|
||||
builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setSmallIcon(R.drawable.notification_icon)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setOngoing(true)
|
||||
if (isDetail) {
|
||||
|
@ -264,6 +264,7 @@
|
||||
"exif_bottom_sheet_location_add": "Add a location",
|
||||
"exif_bottom_sheet_people": "PEOPLE",
|
||||
"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_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
|
@ -48,7 +48,7 @@ PODS:
|
||||
- flutter_udid (0.0.1):
|
||||
- Flutter
|
||||
- SAMKeychain
|
||||
- flutter_web_auth (0.6.0):
|
||||
- flutter_web_auth_2 (3.0.0):
|
||||
- Flutter
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
@ -117,7 +117,7 @@ DEPENDENCIES:
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/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`)
|
||||
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
@ -166,8 +166,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||
flutter_udid:
|
||||
:path: ".symlinks/plugins/flutter_udid/ios"
|
||||
flutter_web_auth:
|
||||
:path: ".symlinks/plugins/flutter_web_auth/ios"
|
||||
flutter_web_auth_2:
|
||||
:path: ".symlinks/plugins/flutter_web_auth_2/ios"
|
||||
fluttertoast:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
geolocator_apple:
|
||||
@ -220,7 +220,7 @@ SPEC CHECKSUMS:
|
||||
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
||||
flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04
|
||||
flutter_web_auth: acc15a8fd7bba796a933c724a6dffc3d00f07c27
|
||||
flutter_web_auth_2: 06d500582775790a0d4c323222fcb6d7990f9603
|
||||
fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
|
||||
geolocator_apple: 6cbaf322953988e009e5ecb481f07efece75c450
|
||||
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||
|
@ -6,7 +6,7 @@ import 'package:immich_mobile/models/map/map_marker.model.dart';
|
||||
import 'package:immich_mobile/utils/map_utils.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
|
||||
extension MapMarkers on MaplibreMapController {
|
||||
extension MapMarkers on MapLibreMapController {
|
||||
static var _completer = Completer()..complete();
|
||||
|
||||
Future<void> addGeoJSONSourceForMarkers(List<MapMarker> markers) async {
|
||||
@ -40,12 +40,26 @@ extension MapMarkers on MaplibreMapController {
|
||||
|
||||
await addGeoJSONSourceForMarkers(markers);
|
||||
|
||||
await addHeatmapLayer(
|
||||
await addCircleLayer(
|
||||
MapUtils.defaultSourceId,
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -8,20 +8,26 @@ class SearchCuratedContent {
|
||||
/// The label to show associated with this curated object
|
||||
final String label;
|
||||
|
||||
/// The subtitle to show below the label
|
||||
final String? subtitle;
|
||||
|
||||
/// The id to lookup the asset from the server
|
||||
final String id;
|
||||
|
||||
SearchCuratedContent({
|
||||
required this.label,
|
||||
required this.id,
|
||||
this.subtitle,
|
||||
});
|
||||
|
||||
SearchCuratedContent copyWith({
|
||||
String? label,
|
||||
String? subtitle,
|
||||
String? id,
|
||||
}) {
|
||||
return SearchCuratedContent(
|
||||
label: label ?? this.label,
|
||||
subtitle: subtitle ?? this.subtitle,
|
||||
id: id ?? this.id,
|
||||
);
|
||||
}
|
||||
@ -29,6 +35,7 @@ class SearchCuratedContent {
|
||||
Map<String, dynamic> toMap() {
|
||||
return <String, dynamic>{
|
||||
'label': label,
|
||||
'subtitle': subtitle,
|
||||
'id': id,
|
||||
};
|
||||
}
|
||||
@ -36,6 +43,7 @@ class SearchCuratedContent {
|
||||
factory SearchCuratedContent.fromMap(Map<String, dynamic> map) {
|
||||
return SearchCuratedContent(
|
||||
label: map['label'] as String,
|
||||
subtitle: map['subtitle'] as String?,
|
||||
id: map['id'] as String,
|
||||
);
|
||||
}
|
||||
@ -46,13 +54,14 @@ class SearchCuratedContent {
|
||||
SearchCuratedContent.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||
|
||||
@override
|
||||
String toString() => 'CuratedContent(label: $label, id: $id)';
|
||||
String toString() =>
|
||||
'CuratedContent(label: $label, subtitle: $subtitle, id: $id)';
|
||||
|
||||
@override
|
||||
bool operator ==(covariant SearchCuratedContent other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other.label == label && other.id == id;
|
||||
return other.label == label && other.subtitle == subtitle && other.id == id;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1,8 +1,10 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/routing/router.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
@ -16,6 +18,8 @@ class PeopleCollectionPage extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final people = ref.watch(getAllPeopleProvider);
|
||||
final headers = ApiService.getRequestHeaders();
|
||||
final formFocus = useFocusNode();
|
||||
final ValueNotifier<String?> search = useState(null);
|
||||
|
||||
showNameEditModel(
|
||||
String personId,
|
||||
@ -36,10 +40,70 @@ class PeopleCollectionPage extends HookConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
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(
|
||||
data: (people) {
|
||||
if (search.value != null) {
|
||||
people = people.where((person) {
|
||||
return person.name
|
||||
.toLowerCase()
|
||||
.contains(search.value!.toLowerCase());
|
||||
}).toList();
|
||||
}
|
||||
return GridView.builder(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isTablet ? 6 : 3,
|
||||
|
@ -11,7 +11,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/extensions/asyncvalue_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/models/map/map_event.model.dart';
|
||||
import 'package:immich_mobile/models/map/map_marker.model.dart';
|
||||
@ -39,7 +38,7 @@ class MapPage extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final mapController = useRef<MaplibreMapController?>(null);
|
||||
final mapController = useRef<MapLibreMapController?>(null);
|
||||
final markers = useRef<List<MapMarker>>([]);
|
||||
final markersInBounds = useRef<List<MapMarker>>([]);
|
||||
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;
|
||||
controller.addListener(() {
|
||||
if (controller.isCameraMoving && selectedMarker.value != null) {
|
||||
@ -389,7 +388,7 @@ class _MapWithMarker extends StatelessWidget {
|
||||
child: Stack(
|
||||
children: [
|
||||
style.widgetWhen(
|
||||
onData: (style) => MaplibreMap(
|
||||
onData: (style) => MapLibreMap(
|
||||
initialCameraPosition:
|
||||
const CameraPosition(target: LatLng(0, 0)),
|
||||
styleString: style,
|
||||
@ -403,7 +402,7 @@ class _MapWithMarker extends StatelessWidget {
|
||||
tiltGesturesEnabled: false,
|
||||
dragEnabled: false,
|
||||
myLocationEnabled: false,
|
||||
attributionButtonPosition: AttributionButtonPosition.TopRight,
|
||||
attributionButtonPosition: AttributionButtonPosition.topRight,
|
||||
rotateGesturesEnabled: false,
|
||||
),
|
||||
),
|
||||
|
@ -24,7 +24,7 @@ class MapLocationPickerPage extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final selectedLatLng = useValueNotifier<LatLng>(initialLatLng);
|
||||
final controller = useRef<MaplibreMapController?>(null);
|
||||
final controller = useRef<MapLibreMapController?>(null);
|
||||
final marker = useRef<Symbol?>(null);
|
||||
|
||||
Future<void> onStyleLoaded() async {
|
||||
@ -74,7 +74,7 @@ class MapLocationPickerPage extends HookConsumerWidget {
|
||||
bottomRight: Radius.circular(40),
|
||||
),
|
||||
),
|
||||
child: MaplibreMap(
|
||||
child: MapLibreMap(
|
||||
initialCameraPosition:
|
||||
CameraPosition(target: initialLatLng, zoom: 12),
|
||||
styleString: style,
|
||||
|
4
mobile/lib/providers/activity.provider.g.dart
generated
4
mobile/lib/providers/activity.provider.g.dart
generated
@ -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>> {
|
||||
/// The parameter `albumId` of this provider.
|
||||
String get albumId;
|
||||
@ -206,4 +208,4 @@ class _AlbumActivityProviderElement
|
||||
String? get assetId => (origin as AlbumActivityProvider).assetId;
|
||||
}
|
||||
// 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
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/repositories/activity_api.repository.dart';
|
||||
import 'package:immich_mobile/services/activity.service.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';
|
||||
|
||||
@riverpod
|
||||
ActivityService activityService(ActivityServiceRef ref) =>
|
||||
ActivityService activityService(Ref ref) =>
|
||||
ActivityService(ref.watch(activityApiRepositoryProvider));
|
||||
|
@ -20,6 +20,8 @@ final activityServiceProvider = AutoDisposeProvider<ActivityService>.internal(
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef ActivityServiceRef = AutoDisposeProviderRef<ActivityService>;
|
||||
// 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
|
||||
|
@ -186,6 +186,8 @@ class ActivityStatisticsProvider
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin ActivityStatisticsRef on AutoDisposeNotifierProviderRef<int> {
|
||||
/// The parameter `albumId` of this provider.
|
||||
String get albumId;
|
||||
@ -205,4 +207,4 @@ class _ActivityStatisticsProviderElement
|
||||
String? get assetId => (origin as ActivityStatisticsProvider).assetId;
|
||||
}
|
||||
// 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
|
||||
|
@ -40,4 +40,4 @@ final albumSortOrderProvider =
|
||||
|
||||
typedef _$AlbumSortOrder = AutoDisposeNotifier<bool>;
|
||||
// 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
|
||||
|
@ -22,4 +22,4 @@ final currentAlbumProvider =
|
||||
|
||||
typedef _$CurrentAlbum = AutoDisposeNotifier<Album?>;
|
||||
// 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
|
||||
|
@ -1,7 +1,8 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'api.provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
ApiService apiService(ApiServiceRef ref) => ApiService();
|
||||
ApiService apiService(Ref ref) => ApiService();
|
||||
|
4
mobile/lib/providers/api.provider.g.dart
generated
4
mobile/lib/providers/api.provider.g.dart
generated
@ -19,6 +19,8 @@ final apiServiceProvider = Provider<ApiService>.internal(
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef ApiServiceRef = ProviderRef<ApiService>;
|
||||
// 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
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'app_settings.provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
AppSettingsService appSettingsService(AppSettingsServiceRef ref) =>
|
||||
AppSettingsService();
|
||||
AppSettingsService appSettingsService(Ref ref) => AppSettingsService();
|
||||
|
@ -21,6 +21,8 @@ final appSettingsServiceProvider = Provider<AppSettingsService>.internal(
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef AppSettingsServiceRef = ProviderRef<AppSettingsService>;
|
||||
// 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
|
||||
|
@ -31,7 +31,7 @@ class AssetNotifier extends StateNotifier<bool> {
|
||||
final SyncService _syncService;
|
||||
final ETagService _etagService;
|
||||
final ExifService _exifService;
|
||||
final StateNotifierProviderRef _ref;
|
||||
final Ref _ref;
|
||||
final log = Logger('AssetNotifier');
|
||||
bool _getAllAssetInProgress = false;
|
||||
bool _deleteInProgress = false;
|
||||
|
@ -171,6 +171,8 @@ class AssetPeopleNotifierProvider extends AutoDisposeAsyncNotifierProviderImpl<
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin AssetPeopleNotifierRef
|
||||
on AutoDisposeAsyncNotifierProviderRef<List<PersonWithFacesResponseDto>> {
|
||||
/// The parameter `asset` of this provider.
|
||||
@ -186,4 +188,4 @@ class _AssetPeopleNotifierProviderElement
|
||||
Asset get asset => (origin as AssetPeopleNotifierProvider).asset;
|
||||
}
|
||||
// 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
|
||||
|
@ -39,6 +39,6 @@ final assetStackStateProvider = StateNotifierProvider.autoDispose
|
||||
);
|
||||
|
||||
@riverpod
|
||||
int assetStackIndex(AssetStackIndexRef ref, Asset asset) {
|
||||
int assetStackIndex(Ref ref, Asset asset) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -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> {
|
||||
/// The parameter `asset` of this provider.
|
||||
Asset get asset;
|
||||
@ -155,4 +157,4 @@ class _AssetStackIndexProviderElement extends AutoDisposeProviderElement<int>
|
||||
Asset get asset => (origin as AssetStackIndexProvider).asset;
|
||||
}
|
||||
// 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
|
||||
|
@ -22,4 +22,4 @@ final currentAssetProvider =
|
||||
|
||||
typedef _$CurrentAsset = AutoDisposeNotifier<Asset?>;
|
||||
// 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
|
||||
|
@ -24,4 +24,4 @@ final backupVerificationProvider =
|
||||
|
||||
typedef _$BackupVerification = AutoDisposeNotifier<bool>;
|
||||
// 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
|
||||
|
@ -1,12 +1,13 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'immich_logo_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<Uint8List> immichLogo(ImmichLogoRef ref) async {
|
||||
Future<Uint8List> immichLogo(Ref ref) async {
|
||||
final json = await rootBundle.loadString('assets/immich-logo.json');
|
||||
final j = jsonDecode(json);
|
||||
return base64Decode(j['content']);
|
||||
|
4
mobile/lib/providers/immich_logo_provider.g.dart
generated
4
mobile/lib/providers/immich_logo_provider.g.dart
generated
@ -19,6 +19,8 @@ final immichLogoProvider = AutoDisposeFutureProvider<Uint8List>.internal(
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef ImmichLogoRef = AutoDisposeFutureProviderRef<Uint8List>;
|
||||
// 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
|
||||
|
@ -1,7 +1,8 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'db.provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Isar isar(IsarRef ref) => throw UnimplementedError('isar');
|
||||
Isar isar(Ref ref) => throw UnimplementedError('isar');
|
||||
|
@ -19,6 +19,8 @@ final isarProvider = Provider<Isar>.internal(
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef IsarRef = ProviderRef<Isar>;
|
||||
// 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
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/exif.repository.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';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
IExifInfoRepository exifRepository(ExifRepositoryRef ref) =>
|
||||
IExifInfoRepository exifRepository(Ref ref) =>
|
||||
IsarExifRepository(ref.watch(isarProvider));
|
||||
|
@ -20,6 +20,8 @@ final exifRepositoryProvider = Provider<IExifInfoRepository>.internal(
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef ExifRepositoryRef = ProviderRef<IExifInfoRepository>;
|
||||
// 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
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.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';
|
||||
|
||||
@riverpod
|
||||
IStoreRepository storeRepository(StoreRepositoryRef ref) =>
|
||||
IStoreRepository storeRepository(Ref ref) =>
|
||||
IsarStoreRepository(ref.watch(isarProvider));
|
||||
|
@ -20,6 +20,8 @@ final storeRepositoryProvider = AutoDisposeProvider<IStoreRepository>.internal(
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef StoreRepositoryRef = AutoDisposeProviderRef<IStoreRepository>;
|
||||
// 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
|
||||
|
@ -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/providers/map/map_service.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';
|
||||
|
||||
@riverpod
|
||||
Future<List<MapMarker>> mapMarkers(MapMarkersRef ref) async {
|
||||
Future<List<MapMarker>> mapMarkers(Ref ref) async {
|
||||
final service = ref.read(mapServiceProvider);
|
||||
final mapState = ref.read(mapStateNotifierProvider);
|
||||
DateTime? fileCreatedAfter;
|
||||
|
@ -19,6 +19,8 @@ final mapMarkersProvider = AutoDisposeFutureProvider<List<MapMarker>>.internal(
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef MapMarkersRef = AutoDisposeFutureProviderRef<List<MapMarker>>;
|
||||
// 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
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/services/map.service.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.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';
|
||||
|
||||
@riverpod
|
||||
MapSerivce mapService(MapServiceRef ref) =>
|
||||
MapSerivce(ref.watch(apiServiceProvider));
|
||||
MapSerivce mapService(Ref ref) => MapSerivce(ref.watch(apiServiceProvider));
|
||||
|
@ -19,6 +19,8 @@ final mapServiceProvider = AutoDisposeProvider<MapSerivce>.internal(
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef MapServiceRef = AutoDisposeProviderRef<MapSerivce>;
|
||||
// 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
|
||||
|
@ -23,4 +23,4 @@ final mapStateNotifierProvider =
|
||||
|
||||
typedef _$MapStateNotifier = Notifier<MapState>;
|
||||
// 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
|
||||
|
@ -45,7 +45,7 @@ class PaginatedSearchNotifier extends StateNotifier<SearchResult> {
|
||||
|
||||
@riverpod
|
||||
Future<RenderList> paginatedSearchRenderList(
|
||||
PaginatedSearchRenderListRef ref,
|
||||
Ref ref,
|
||||
) {
|
||||
final result = ref.watch(paginatedSearchProvider);
|
||||
final timelineService = ref.watch(timelineServiceProvider);
|
||||
|
@ -22,6 +22,8 @@ final paginatedSearchRenderListProvider =
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef PaginatedSearchRenderListRef = AutoDisposeFutureProviderRef<RenderList>;
|
||||
// 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
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/services/person.service.dart';
|
||||
@ -9,7 +10,7 @@ part 'people.provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<List<Person>> getAllPeople(
|
||||
GetAllPeopleRef ref,
|
||||
Ref ref,
|
||||
) async {
|
||||
final PersonService personService = ref.read(personServiceProvider);
|
||||
|
||||
@ -19,7 +20,7 @@ Future<List<Person>> getAllPeople(
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<RenderList> personAssets(PersonAssetsRef ref, String personId) async {
|
||||
Future<RenderList> personAssets(Ref ref, String personId) async {
|
||||
final PersonService personService = ref.read(personServiceProvider);
|
||||
final assets = await personService.getPersonAssets(personId);
|
||||
|
||||
@ -31,7 +32,7 @@ Future<RenderList> personAssets(PersonAssetsRef ref, String personId) async {
|
||||
|
||||
@riverpod
|
||||
Future<bool> updatePersonName(
|
||||
UpdatePersonNameRef ref,
|
||||
Ref ref,
|
||||
String personId,
|
||||
String updatedName,
|
||||
) async {
|
||||
|
@ -19,6 +19,8 @@ final getAllPeopleProvider = AutoDisposeFutureProvider<List<Person>>.internal(
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef GetAllPeopleRef = AutoDisposeFutureProviderRef<List<Person>>;
|
||||
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> {
|
||||
/// The parameter `personId` of this provider.
|
||||
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> {
|
||||
/// The parameter `personId` of this provider.
|
||||
String get personId;
|
||||
@ -314,4 +320,4 @@ class _UpdatePersonNameProviderElement
|
||||
String get updatedName => (origin as UpdatePersonNameProvider).updatedName;
|
||||
}
|
||||
// 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
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/services/search.service.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@ -6,7 +7,7 @@ part 'search_filter.provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<List<String>> getSearchSuggestions(
|
||||
GetSearchSuggestionsRef ref,
|
||||
Ref ref,
|
||||
SearchSuggestionType type, {
|
||||
String? locationCountry,
|
||||
String? locationState,
|
||||
|
@ -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>> {
|
||||
/// The parameter `type` of this provider.
|
||||
SearchSuggestionType get type;
|
||||
@ -226,4 +228,4 @@ class _GetSearchSuggestionsProviderElement
|
||||
String? get model => (origin as GetSearchSuggestionsProvider).model;
|
||||
}
|
||||
// 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
|
||||
|
@ -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
|
||||
/// [GalleryViewerPage]
|
||||
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
|
||||
/// [PeopleCollectionPage]
|
||||
class PeopleCollectionRoute extends PageRouteInfo<void> {
|
||||
|
@ -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:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:flutter_web_auth/flutter_web_auth.dart';
|
||||
|
||||
// Redirect URL = app.immich:///oauth-callback
|
||||
|
||||
@ -32,7 +32,7 @@ class OAuthService {
|
||||
}
|
||||
|
||||
Future<LoginResponseDto?> oAuthLogin(String oauthUrl) async {
|
||||
String result = await FlutterWebAuth.authenticate(
|
||||
String result = await FlutterWebAuth2.authenticate(
|
||||
url: oauthUrl,
|
||||
callbackUrlScheme: callbackUrlScheme,
|
||||
);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/interfaces/asset.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';
|
||||
|
||||
@riverpod
|
||||
PersonService personService(PersonServiceRef ref) => PersonService(
|
||||
PersonService personService(Ref ref) => PersonService(
|
||||
ref.watch(personApiRepositoryProvider),
|
||||
ref.watch(assetApiRepositoryProvider),
|
||||
ref.read(assetRepositoryProvider),
|
||||
|
4
mobile/lib/services/person.service.g.dart
generated
4
mobile/lib/services/person.service.g.dart
generated
@ -20,6 +20,8 @@ final personServiceProvider = AutoDisposeProvider<PersonService>.internal(
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef PersonServiceRef = AutoDisposeProviderRef<PersonService>;
|
||||
// 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
|
||||
|
@ -58,12 +58,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||
final bool success =
|
||||
await ref.watch(albumProvider.notifier).deleteAlbum(album);
|
||||
|
||||
if (album.shared) {
|
||||
context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]));
|
||||
} else {
|
||||
context
|
||||
.navigateTo(const TabControllerRoute(children: [LibraryRoute()]));
|
||||
}
|
||||
context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]));
|
||||
|
||||
if (!success) {
|
||||
ImmichToast.show(
|
||||
|
@ -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
|
||||
_scrollToIndex(index + 1);
|
||||
} else {
|
||||
|
@ -44,7 +44,19 @@ class PeopleInfo extends ConsumerWidget {
|
||||
}
|
||||
|
||||
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() ??
|
||||
[];
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/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/providers/asset_viewer/download.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);
|
||||
}
|
||||
|
||||
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(
|
||||
ignoring: !showControls,
|
||||
child: AnimatedOpacity(
|
||||
@ -107,6 +119,7 @@ class GalleryAppBar extends ConsumerWidget {
|
||||
isPartner: isPartner,
|
||||
asset: asset,
|
||||
onMoreInfoPressed: showInfo,
|
||||
onLocatePressed: handleLocateAsset,
|
||||
onFavorite: toggleFavorite,
|
||||
onRestorePressed: () => handleRestore(asset),
|
||||
onUploadPressed: asset.isLocal ? () => handleUpload(asset) : null,
|
||||
|
@ -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/entities/asset.entity.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';
|
||||
|
||||
class TopControlAppBar extends HookConsumerWidget {
|
||||
@ -13,6 +14,7 @@ class TopControlAppBar extends HookConsumerWidget {
|
||||
required this.asset,
|
||||
required this.onMoreInfoPressed,
|
||||
required this.onDownloadPressed,
|
||||
required this.onLocatePressed,
|
||||
required this.onAddToAlbumPressed,
|
||||
required this.onRestorePressed,
|
||||
required this.onFavorite,
|
||||
@ -26,6 +28,7 @@ class TopControlAppBar extends HookConsumerWidget {
|
||||
final Function onMoreInfoPressed;
|
||||
final VoidCallback? onUploadPressed;
|
||||
final VoidCallback? onDownloadPressed;
|
||||
final VoidCallback onLocatePressed;
|
||||
final VoidCallback onAddToAlbumPressed;
|
||||
final VoidCallback onRestorePressed;
|
||||
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() {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
@ -159,6 +174,8 @@ class TopControlAppBar extends HookConsumerWidget {
|
||||
shape: const Border(),
|
||||
actions: [
|
||||
if (asset.isRemote && isOwner) buildFavoriteButton(a),
|
||||
if (isOwner && ref.read(tabProvider.notifier).state != TabEnum.home)
|
||||
buildLocateButton(),
|
||||
if (asset.livePhotoVideoId != null) const MotionPhotoButton(),
|
||||
if (asset.isLocal && !asset.isRemote) buildUploadButton(),
|
||||
if (asset.isRemote && !asset.isLocal && isOwner) buildDownloadButton(),
|
||||
|
@ -41,10 +41,10 @@ class MapThumbnail extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
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);
|
||||
|
||||
Future<void> onMapCreated(MaplibreMapController mapController) async {
|
||||
Future<void> onMapCreated(MapLibreMapController mapController) async {
|
||||
controller.value = mapController;
|
||||
if (assetMarkerRemoteId != null) {
|
||||
// The iOS impl returns wrong toScreenLocation without the delay
|
||||
@ -73,7 +73,7 @@ class MapThumbnail extends HookConsumerWidget {
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
style.widgetWhen(
|
||||
onData: (style) => MaplibreMap(
|
||||
onData: (style) => MapLibreMap(
|
||||
initialCameraPosition:
|
||||
CameraPosition(target: offsettedCentre, zoom: zoom),
|
||||
styleString: style,
|
||||
|
@ -86,12 +86,22 @@ class CuratedPeopleRow extends StatelessWidget {
|
||||
).tr(),
|
||||
);
|
||||
}
|
||||
return Text(
|
||||
person.label,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.labelLarge,
|
||||
maxLines: 2,
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
person.label,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.labelLarge,
|
||||
maxLines: 2,
|
||||
),
|
||||
if (person.subtitle != null)
|
||||
Text(
|
||||
person.subtitle!,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -358,6 +358,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -508,10 +516,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_hooks
|
||||
sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70
|
||||
sha256: b772e710d16d7a20c0740c4f855095026b31c7eb5ba3ab67d2bd52021cd9461d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.5"
|
||||
version: "0.21.2"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -577,10 +585,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_riverpod
|
||||
sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d"
|
||||
sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
version: "2.6.1"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -602,14 +610,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
flutter_web_auth:
|
||||
flutter_web_auth_2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_web_auth
|
||||
sha256: "95e4856e24fb6ac1678f5ff334743b63f782d839ab324543d29ccbd295176209"
|
||||
name: flutter_web_auth_2
|
||||
sha256: "561c32d32ed537853de43852c35849cf1d37f3482f41f22b718ab6112f96b333"
|
||||
url: "https://pub.dev"
|
||||
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:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -712,10 +728,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: hooks_riverpod
|
||||
sha256: "45b2030a18bcd6dbd680c2c91bc3b33e3fe7c323e3acb5ecec93a613e2fbaa8a"
|
||||
sha256: "70bba33cfc5670c84b796e6929c54b8bc5be7d0fe15bb28c2560500b9ad06966"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
version: "2.6.1"
|
||||
hotreloader:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -948,26 +964,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: maplibre_gl
|
||||
sha256: "9dd9eebee52f42a45aaa9cdb912afa47845c37007b26a799aa482ecd368804c8"
|
||||
sha256: cd0adf2da87149cab556ac70977783d6dcb3bd73b17a5583cc8366a5aafa46f8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0+2"
|
||||
version: "0.21.0"
|
||||
maplibre_gl_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: maplibre_gl_platform_interface
|
||||
sha256: a95fa38a3532253f32dfe181389adfe9f402773e58ac902d9c4efad3209e0903
|
||||
sha256: "6db8234705e58c09b6fd5a43747a817ba1e6e91a76deb3ed057a36a994d86f22"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0+2"
|
||||
version: "0.21.0"
|
||||
maplibre_gl_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: maplibre_gl_web
|
||||
sha256: "7f1540b384f16f3c9bc8b4ebdfca96fb07f6dab5d9ef4dd0e102985dba238691"
|
||||
sha256: e1cbe04594fdb0d76de7cd448c0048290df8dc69dc37a85d23307dd595779141
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0+2"
|
||||
version: "0.21.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1276,42 +1292,42 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod
|
||||
sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d
|
||||
sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
version: "2.6.1"
|
||||
riverpod_analyzer_utils:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod_analyzer_utils
|
||||
sha256: ee72770090078e6841d51355292335f1bc254907c6694283389dcb8156d99a4d
|
||||
sha256: "0dcb0af32d561f8fa000c6a6d95633c9fb08ea8a8df46e3f9daca59f11218167"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.3"
|
||||
version: "0.5.6"
|
||||
riverpod_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: riverpod_annotation
|
||||
sha256: e5e796c0eba4030c704e9dae1b834a6541814963292839dcf9638d53eba84f5c
|
||||
sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.5"
|
||||
version: "2.6.1"
|
||||
riverpod_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: riverpod_generator
|
||||
sha256: "1ad626afbd8b01d168870b13c0b036f8a5bdb57c14cd426dc5b4595466bd6e2f"
|
||||
sha256: "851aedac7ad52693d12af3bf6d92b1626d516ed6b764eb61bf19e968b5e0b931"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.6.1"
|
||||
riverpod_lint:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: riverpod_lint
|
||||
sha256: b95a8cdc6102397f7d51037131c25ce7e51be900be021af4bf0c2d6f1b8f7aa7
|
||||
sha256: "0684c21a9a4582c28c897d55c7b611fa59a351579061b43f8c92c005804e63a8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.12"
|
||||
version: "2.6.1"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1813,6 +1829,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1846,5 +1870,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.5.3 <4.0.0"
|
||||
dart: ">=3.5.0 <4.0.0"
|
||||
flutter: ">=3.24.5"
|
||||
|
@ -17,16 +17,16 @@ dependencies:
|
||||
path_provider_ios:
|
||||
photo_manager: ^3.6.1
|
||||
photo_manager_image_provider: ^2.2.0
|
||||
flutter_hooks: ^0.20.4
|
||||
hooks_riverpod: ^2.4.9
|
||||
riverpod_annotation: ^2.3.3
|
||||
flutter_hooks: ^0.21.2
|
||||
hooks_riverpod: ^2.6.1
|
||||
riverpod_annotation: ^2.6.1
|
||||
cached_network_image: ^3.3.1
|
||||
flutter_cache_manager: ^3.3.1
|
||||
intl: ^0.19.0
|
||||
auto_route: ^9.2.0
|
||||
fluttertoast: ^8.2.4
|
||||
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
|
||||
flutter_udid: ^3.0.0
|
||||
flutter_svg: ^2.0.9
|
||||
@ -42,7 +42,7 @@ dependencies:
|
||||
path_provider: ^2.1.2
|
||||
collection: ^1.18.0
|
||||
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
|
||||
isar:
|
||||
version: *isar_version
|
||||
@ -108,8 +108,8 @@ dev_dependencies:
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
custom_lint: ^0.6.4
|
||||
riverpod_lint: ^2.3.7
|
||||
riverpod_generator: ^2.3.9
|
||||
riverpod_lint: ^2.6.1
|
||||
riverpod_generator: ^2.6.1
|
||||
mocktail: ^1.0.3
|
||||
immich_mobile_immich_lint:
|
||||
path: './immich_lint'
|
||||
|
6
open-api/typescript-sdk/package-lock.json
generated
6
open-api/typescript-sdk/package-lock.json
generated
@ -33,9 +33,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
|
||||
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
@ -65,7 +65,7 @@ https://immich.app
|
||||
https://demo.immich.app
|
||||
|
||||
بالنسبة لتطبيق الهاتف المحمول، يمكنك استخدام
|
||||
`https://demo.immich.app/api`
|
||||
`https://demo.immich.app`
|
||||
ل `نقطة نهاية الخادم`
|
||||
|
||||
```bash title="Demo Credential"
|
||||
|
@ -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
|
||||
|
||||
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"
|
||||
Les credencials
|
||||
|
@ -64,7 +64,7 @@
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -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>
|
||||
|
||||
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"
|
||||
Credenciales
|
||||
|
@ -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
|
||||
|
||||
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"
|
||||
Les identifiants
|
||||
|
@ -61,7 +61,7 @@ La documentazione ufficiale, inclusa la guida all'installazione, è disponibile
|
||||
|
||||
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"
|
||||
Credenziali di accesso
|
||||
|
@ -60,7 +60,7 @@
|
||||
|
||||
web デモは https://demo.immich.app からアクセスできます
|
||||
|
||||
モバイルアプリの場合、`Server Endpoint URL` には `https://demo.immich.app/api` を使用することができます
|
||||
モバイルアプリの場合、`Server Endpoint URL` には `https://demo.immich.app` を使用することができます
|
||||
|
||||
```bash title="Demo Credential"
|
||||
The credential
|
||||
|
@ -63,7 +63,7 @@
|
||||
|
||||
[이곳](https://demo.immich.app)에서 데모를 체험해보세요. 데모 서버는 2.4Ghz 쿼드 코어 ARM64 CPU 및 24GB 램으로 구성된 Oracle Free-tier VM 암스테르담 리전에서 구동됩니다.
|
||||
|
||||
모바일 앱의 경우, `서버 엔드포인트 URL`에 `https://demo.immich.app/api`를 입력하세요.
|
||||
모바일 앱의 경우, `서버 엔드포인트 URL`에 `https://demo.immich.app`를 입력하세요.
|
||||
|
||||
### 로그인 정보
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -64,7 +64,7 @@
|
||||
|
||||
Вы можете опробовать [Web демонстрационную версию](https://demo.immich.app/)
|
||||
|
||||
В мобильном приложении укажите `https://demo.immich.app/api` в поле `URL-адрес сервера`
|
||||
В мобильном приложении укажите `https://demo.immich.app` в поле `URL-адрес сервера`
|
||||
|
||||
### Данные для входа
|
||||
|
||||
|
@ -62,7 +62,7 @@ Dokumentation och installationsguider hittas på https://imiich.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"
|
||||
Inloggsningsuppgifter
|
||||
|
@ -65,7 +65,7 @@
|
||||
|
||||
เข้าถึงการสาธิตได้ [ที่นี่](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`
|
||||
|
||||
### ข้อมูลการเข้าสู่ระบบ
|
||||
|
||||
|
@ -60,7 +60,7 @@ Kurulum dahil olmak üzere resmi belgeleri https://immich.app/ adresinde bulabil
|
||||
|
||||
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"
|
||||
Giriş bilgileri:
|
||||
|
@ -63,7 +63,7 @@
|
||||
|
||||
Доступ до демо-версії [тут](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`.
|
||||
|
||||
### Облікові дані для входу
|
||||
|
||||
|
@ -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.
|
||||
|
||||
Đố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
|
||||
|
||||
|
@ -67,7 +67,7 @@
|
||||
|
||||
您可以在[此处](https://demo.immich.app)访问在线演示网站。该示例网站运行的机器配置为:甲骨文免费虚拟机套餐——阿姆斯特丹 4核 2.4Ghz ARM64 CPU,24GB RAM。
|
||||
|
||||
在移动端,您可以使用 `https://demo.immich.app/api` 作为 `服务终端链接`
|
||||
在移动端,您可以使用 `https://demo.immich.app` 作为 `服务终端链接`
|
||||
|
||||
### 登录认证信息
|
||||
|
||||
|
3430
server/package-lock.json
generated
3430
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -58,7 +58,8 @@
|
||||
"chokidar": "^3.5.3",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cookie": "^1.0.2",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"exiftool-vendored": "^28.3.1",
|
||||
"fast-glob": "^3.3.2",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
@ -107,7 +108,7 @@
|
||||
"@types/archiver": "^6.0.0",
|
||||
"@types/async-lock": "^1.4.2",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/cookie-parser": "^1.4.8",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/fluent-ffmpeg": "^2.1.21",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
@ -131,7 +132,7 @@
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-unicorn": "^56.0.1",
|
||||
"globals": "^16.0.0",
|
||||
"kysely-codegen": "^0.17.0",
|
||||
"kysely-codegen": "^0.18.0",
|
||||
"mock-fs": "^5.2.0",
|
||||
"node-addon-api": "^8.3.0",
|
||||
"pngjs": "^7.0.0",
|
||||
|
@ -4,13 +4,13 @@ import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto, APIKeyUpda
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { Permission } from 'src/enum';
|
||||
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';
|
||||
|
||||
@ApiTags('API Keys')
|
||||
@Controller('api-keys')
|
||||
export class APIKeyController {
|
||||
constructor(private service: APIKeyService) {}
|
||||
constructor(private service: ApiKeyService) {}
|
||||
|
||||
@Post()
|
||||
@Authenticated({ permission: Permission.API_KEY_CREATE })
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { sql } from 'kysely';
|
||||
import { Permission } from 'src/enum';
|
||||
import { AssetStatus, AssetType, Permission } from 'src/enum';
|
||||
|
||||
export type AuthUser = {
|
||||
id: string;
|
||||
@ -10,11 +10,74 @@ export type AuthUser = {
|
||||
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 = {
|
||||
id: string;
|
||||
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 = {
|
||||
id: string;
|
||||
expiresAt: Date | null;
|
||||
|
7
server/src/db.d.ts
vendored
7
server/src/db.d.ts
vendored
@ -4,7 +4,8 @@
|
||||
*/
|
||||
|
||||
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>;
|
||||
|
||||
@ -240,7 +241,7 @@ export interface Libraries {
|
||||
|
||||
export interface Memories {
|
||||
createdAt: Generated<Timestamp>;
|
||||
data: Json;
|
||||
data: OnThisDayData;
|
||||
deletedAt: Timestamp | null;
|
||||
hideAt: Timestamp | null;
|
||||
id: Generated<string>;
|
||||
@ -249,7 +250,7 @@ export interface Memories {
|
||||
ownerId: string;
|
||||
seenAt: Timestamp | null;
|
||||
showAt: Timestamp | null;
|
||||
type: string;
|
||||
type: MemoryType;
|
||||
updatedAt: Generated<Timestamp>;
|
||||
updateId: Generated<string>;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
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';
|
||||
|
||||
export class CreateLibraryDto {
|
||||
@ -120,7 +120,7 @@ export class LibraryStatsResponseDto {
|
||||
usage = 0;
|
||||
}
|
||||
|
||||
export function mapLibrary(entity: LibraryEntity): LibraryResponseDto {
|
||||
export function mapLibrary(entity: Library): LibraryResponseDto {
|
||||
let assetCount = 0;
|
||||
if (entity.assets) {
|
||||
assetCount = entity.assets.length;
|
||||
|
@ -7,6 +7,7 @@ import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||
import { PersonEntity } from 'src/entities/person.entity';
|
||||
import { SourceType } from 'src/enum';
|
||||
import { asDateString } from 'src/utils/date';
|
||||
import {
|
||||
IsDateStringFormat,
|
||||
MaxDateString,
|
||||
@ -32,7 +33,7 @@ export class PersonCreateDto {
|
||||
@MaxDateString(() => DateTime.now(), { message: 'Birth date cannot be in the future' })
|
||||
@IsDateStringFormat('yyyy-MM-dd')
|
||||
@Optional({ nullable: true })
|
||||
birthDate?: string | null;
|
||||
birthDate?: Date | null;
|
||||
|
||||
/**
|
||||
* Person visibility
|
||||
@ -222,7 +223,7 @@ export function mapPerson(person: PersonEntity): PersonResponseDto {
|
||||
return {
|
||||
id: person.id,
|
||||
name: person.name,
|
||||
birthDate: person.birthDate,
|
||||
birthDate: asDateString(person.birthDate),
|
||||
thumbnailPath: person.thumbnailPath,
|
||||
isHidden: person.isHidden,
|
||||
isFavorite: person.isFavorite,
|
||||
|
@ -26,7 +26,7 @@ export class LibraryEntity {
|
||||
assets!: AssetEntity[];
|
||||
|
||||
@ManyToOne(() => UserEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
|
||||
owner!: UserEntity;
|
||||
owner?: UserEntity;
|
||||
|
||||
@Column()
|
||||
ownerId!: string;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user