Compare commits
67 Commits
v1.111.0
...
feat/serve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82f05e9ca9 | ||
|
|
11f41099c3 | ||
|
|
96481aae5d | ||
|
|
4a42a72bd3 | ||
|
|
66f2ac8ce3 | ||
|
|
6b2de807a7 | ||
|
|
96f8050143 | ||
|
|
14689462f8 | ||
|
|
fb68da2b51 | ||
|
|
720b9a286e | ||
|
|
d93ccb1669 | ||
|
|
c34fc4f2d1 | ||
|
|
905a062a6e | ||
|
|
aeed24b5b4 | ||
|
|
28ba22e8c1 | ||
|
|
5b64456f48 | ||
|
|
02fd6d22b3 | ||
|
|
10ed31d725 | ||
|
|
23d4314eed | ||
|
|
ea135cc310 | ||
|
|
745e1b003d | ||
|
|
1dae622dbc | ||
|
|
8ca24f0ef2 | ||
|
|
f679021f0e | ||
|
|
65f5118bdd | ||
|
|
9f4fad2a0f | ||
|
|
325fb4b5d1 | ||
|
|
f040c9fb38 | ||
|
|
0eacdf93eb | ||
|
|
20262209ce | ||
|
|
dd638ac207 | ||
|
|
d5b23373c7 | ||
|
|
9765ccb5a7 | ||
|
|
82d934d09d | ||
|
|
2821e0bf95 | ||
|
|
bb3d9b6306 | ||
|
|
c83df2686a | ||
|
|
94da5942bd | ||
|
|
54d2c12fff | ||
|
|
64fcb25971 | ||
|
|
7f03bd8440 | ||
|
|
2974cdbbee | ||
|
|
f0677735fd | ||
|
|
bb78eb4c4b | ||
|
|
4ed75f2ac9 | ||
|
|
3f4b783889 | ||
|
|
3968d76a57 | ||
|
|
55b31d1ce2 | ||
|
|
37cc6fbf27 | ||
|
|
899b8a0ce7 | ||
|
|
d3a5490e71 | ||
|
|
3afb5b497f | ||
|
|
1f0f880ecb | ||
|
|
2c05ceaf50 | ||
|
|
01f8b7e458 | ||
|
|
b73f7fe16f | ||
|
|
281cfc95a4 | ||
|
|
3a3ea6135e | ||
|
|
c44271e9b2 | ||
|
|
86904a8382 | ||
|
|
cf54829b3b | ||
|
|
990627e00d | ||
|
|
41580696c7 | ||
|
|
2423bb36c4 | ||
|
|
82b899649d | ||
|
|
8ee8450d18 | ||
|
|
6d47d52b3c |
4
.github/workflows/cli.yml
vendored
4
.github/workflows/cli.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
|||||||
uses: docker/setup-qemu-action@v3.2.0
|
uses: docker/setup-qemu-action@v3.2.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.5.0
|
uses: docker/setup-buildx-action@v3.6.1
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
|
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
uses: docker/build-push-action@v6.5.0
|
uses: docker/build-push-action@v6.6.1
|
||||||
with:
|
with:
|
||||||
file: cli/Dockerfile
|
file: cli/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|||||||
4
.github/workflows/docker-cleanup.yml
vendored
4
.github/workflows/docker-cleanup.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Clean temporary images
|
- name: Clean temporary images
|
||||||
if: "${{ env.TOKEN != '' }}"
|
if: "${{ env.TOKEN != '' }}"
|
||||||
uses: stumpylog/image-cleaner-action/ephemeral@v0.7.0
|
uses: stumpylog/image-cleaner-action/ephemeral@v0.8.0
|
||||||
with:
|
with:
|
||||||
token: "${{ env.TOKEN }}"
|
token: "${{ env.TOKEN }}"
|
||||||
owner: "immich-app"
|
owner: "immich-app"
|
||||||
@@ -64,7 +64,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Clean untagged images
|
- name: Clean untagged images
|
||||||
if: "${{ env.TOKEN != '' }}"
|
if: "${{ env.TOKEN != '' }}"
|
||||||
uses: stumpylog/image-cleaner-action/untagged@v0.7.0
|
uses: stumpylog/image-cleaner-action/untagged@v0.8.0
|
||||||
with:
|
with:
|
||||||
token: "${{ env.TOKEN }}"
|
token: "${{ env.TOKEN }}"
|
||||||
owner: "immich-app"
|
owner: "immich-app"
|
||||||
|
|||||||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@@ -66,7 +66,7 @@ jobs:
|
|||||||
uses: docker/setup-qemu-action@v3.2.0
|
uses: docker/setup-qemu-action@v3.2.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.5.0
|
uses: docker/setup-buildx-action@v3.6.1
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
# Only push to Docker Hub when making a release
|
# Only push to Docker Hub when making a release
|
||||||
@@ -115,7 +115,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
uses: docker/build-push-action@v6.5.0
|
uses: docker/build-push-action@v6.6.1
|
||||||
with:
|
with:
|
||||||
context: ${{ matrix.context }}
|
context: ${{ matrix.context }}
|
||||||
file: ${{ matrix.file }}
|
file: ${{ matrix.file }}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
/dist
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
project: 'tsconfig.json',
|
|
||||||
sourceType: 'module',
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
},
|
|
||||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
|
||||||
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', 'plugin:unicorn/recommended'],
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
ignorePatterns: ['.eslintrc.js'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/interface-name-prefix': 'off',
|
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
'@typescript-eslint/no-floating-promises': 'error',
|
|
||||||
'unicorn/prefer-module': 'off',
|
|
||||||
'unicorn/prevent-abbreviations': 'off',
|
|
||||||
'unicorn/no-process-exit': 'off',
|
|
||||||
'unicorn/import-style': 'off',
|
|
||||||
curly: 2,
|
|
||||||
'prettier/prettier': 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:20.16.0-alpine3.20@sha256:eb8101caae9ac02229bd64c024919fe3d4504ff7f329da79ca60a04db08cef52 as core
|
FROM node:20.16.0-alpine3.20@sha256:eb8101caae9ac02229bd64c024919fe3d4504ff7f329da79ca60a04db08cef52 AS core
|
||||||
|
|
||||||
WORKDIR /usr/src/open-api/typescript-sdk
|
WORKDIR /usr/src/open-api/typescript-sdk
|
||||||
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./
|
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./
|
||||||
|
|||||||
60
cli/eslint.config.mjs
Normal file
60
cli/eslint.config.mjs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { FlatCompat } from '@eslint/eslintrc';
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import typescriptEslint from '@typescript-eslint/eslint-plugin';
|
||||||
|
import tsParser from '@typescript-eslint/parser';
|
||||||
|
import globals from 'globals';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
allConfig: js.configs.all,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: ['eslint.config.mjs', 'dist'],
|
||||||
|
},
|
||||||
|
...compat.extends(
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
'plugin:unicorn/recommended',
|
||||||
|
),
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
'@typescript-eslint': typescriptEslint,
|
||||||
|
},
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
|
||||||
|
parser: tsParser,
|
||||||
|
ecmaVersion: 5,
|
||||||
|
sourceType: 'module',
|
||||||
|
|
||||||
|
parserOptions: {
|
||||||
|
project: 'tsconfig.json',
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/interface-name-prefix': 'off',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-floating-promises': 'error',
|
||||||
|
'unicorn/prefer-module': 'off',
|
||||||
|
'unicorn/prevent-abbreviations': 'off',
|
||||||
|
'unicorn/no-process-exit': 'off',
|
||||||
|
'unicorn/import-style': 'off',
|
||||||
|
curly: 2,
|
||||||
|
'prettier/prettier': 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
1531
cli/package-lock.json
generated
1531
cli/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,30 +13,33 @@
|
|||||||
"cli"
|
"cli"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
|
"@eslint/js": "^9.8.0",
|
||||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||||
"@types/byte-size": "^8.1.0",
|
"@types/byte-size": "^8.1.0",
|
||||||
"@types/cli-progress": "^3.11.0",
|
"@types/cli-progress": "^3.11.0",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "^20.14.12",
|
"@types/node": "^20.14.13",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^7.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"@vitest/coverage-v8": "^1.2.2",
|
"@vitest/coverage-v8": "^2.0.5",
|
||||||
"byte-size": "^9.0.0",
|
"byte-size": "^9.0.0",
|
||||||
"cli-progress": "^3.12.0",
|
"cli-progress": "^3.12.0",
|
||||||
"commander": "^12.0.0",
|
"commander": "^12.0.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-unicorn": "^54.0.0",
|
"eslint-plugin-unicorn": "^55.0.0",
|
||||||
|
"globals": "^15.9.0",
|
||||||
"mock-fs": "^5.2.0",
|
"mock-fs": "^5.2.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prettier-plugin-organize-imports": "^4.0.0",
|
"prettier-plugin-organize-imports": "^4.0.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.0.12",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"vitest": "^1.2.2",
|
"vitest": "^2.0.5",
|
||||||
"vitest-fetch-mock": "^0.2.2",
|
"vitest-fetch-mock": "^0.3.0",
|
||||||
"yaml": "^2.3.1"
|
"yaml": "^2.3.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
- database
|
- database
|
||||||
|
healthcheck:
|
||||||
|
disable: false
|
||||||
|
|
||||||
immich-web:
|
immich-web:
|
||||||
container_name: immich_web
|
container_name: immich_web
|
||||||
@@ -91,6 +93,8 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- database
|
- database
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
disable: false
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ services:
|
|||||||
- redis
|
- redis
|
||||||
- database
|
- database
|
||||||
restart: always
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
disable: false
|
||||||
|
|
||||||
immich-machine-learning:
|
immich-machine-learning:
|
||||||
container_name: immich_machine_learning
|
container_name: immich_machine_learning
|
||||||
@@ -40,6 +42,8 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
restart: always
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
disable: false
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
@@ -67,7 +71,7 @@ services:
|
|||||||
interval: 5m
|
interval: 5m
|
||||||
start_interval: 30s
|
start_interval: 30s
|
||||||
start_period: 5m
|
start_period: 5m
|
||||||
command: ["postgres", "-c" ,"shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
|
command: ["postgres", "-c", "shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
# set IMMICH_METRICS=true in .env to enable metrics
|
# set IMMICH_METRICS=true in .env to enable metrics
|
||||||
@@ -87,7 +91,7 @@ services:
|
|||||||
command: ['./run.sh', '-disable-reporting']
|
command: ['./run.sh', '-disable-reporting']
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
image: grafana/grafana:11.1.0-ubuntu@sha256:c7fc29ec783d5e7fc1bdfaad6f92345a345cffbc5d21c388ca228175006fc107
|
image: grafana/grafana:11.1.3-ubuntu@sha256:e10453733015f31103cb530425f32c994816b50102886fa885dafea2c50a711c
|
||||||
volumes:
|
volumes:
|
||||||
- grafana-data:/var/lib/grafana
|
- grafana-data:/var/lib/grafana
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ services:
|
|||||||
- redis
|
- redis
|
||||||
- database
|
- database
|
||||||
restart: always
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
disable: false
|
||||||
|
|
||||||
immich-machine-learning:
|
immich-machine-learning:
|
||||||
container_name: immich_machine_learning
|
container_name: immich_machine_learning
|
||||||
@@ -41,6 +43,8 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
restart: always
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
disable: false
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
@@ -65,7 +69,7 @@ services:
|
|||||||
interval: 5m
|
interval: 5m
|
||||||
start_interval: 30s
|
start_interval: 30s
|
||||||
start_period: 5m
|
start_period: 5m
|
||||||
command: ["postgres", "-c" ,"shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
|
command: ["postgres", "-c", "shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -294,6 +294,12 @@ You need to enable WebSockets on your reverse proxy.
|
|||||||
|
|
||||||
Immich components are typically deployed using docker. To see logs for deployed docker containers, you can use the [Docker CLI](https://docs.docker.com/engine/reference/commandline/cli/), specifically the `docker logs` command. For examples, see [Docker Help](/docs/guides/docker-help.md).
|
Immich components are typically deployed using docker. To see logs for deployed docker containers, you can use the [Docker CLI](https://docs.docker.com/engine/reference/commandline/cli/), specifically the `docker logs` command. For examples, see [Docker Help](/docs/guides/docker-help.md).
|
||||||
|
|
||||||
|
### How can I reduce the log verbosity of Redis?
|
||||||
|
|
||||||
|
To decrease Redis logs, you can add the following line to the `redis:` section of the `docker-compose.yml`:
|
||||||
|
|
||||||
|
` command: redis-server --loglevel warning`
|
||||||
|
|
||||||
### How can I run Immich as a non-root user?
|
### How can I run Immich as a non-root user?
|
||||||
|
|
||||||
You can change the user in the container by setting the `user` argument in `docker-compose.yml` for each service.
|
You can change the user in the container by setting the `user` argument in `docker-compose.yml` for each service.
|
||||||
|
|||||||
@@ -145,28 +145,36 @@ const config = {
|
|||||||
label: 'Installation',
|
label: 'Installation',
|
||||||
to: '/docs/install/requirements',
|
to: '/docs/install/requirements',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Contributing',
|
||||||
|
to: '/docs/overview/support-the-project',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Privacy Policy',
|
||||||
|
to: '/privacy-policy',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Community',
|
title: 'Documentation',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Discord',
|
label: 'Roadmap',
|
||||||
href: 'https://discord.immich.app',
|
to: '/roadmap',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Reddit',
|
label: 'API',
|
||||||
href: 'https://www.reddit.com/r/immich/',
|
to: '/docs/api',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Cursed Knowledge',
|
||||||
|
to: '/cursed-knowledge',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Links',
|
title: 'Links',
|
||||||
items: [
|
items: [
|
||||||
// {
|
|
||||||
// label: "Blog",
|
|
||||||
// to: "/blog",
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
label: 'GitHub',
|
label: 'GitHub',
|
||||||
href: 'https://github.com/immich-app/immich',
|
href: 'https://github.com/immich-app/immich',
|
||||||
@@ -175,6 +183,14 @@ const config = {
|
|||||||
label: 'YouTube',
|
label: 'YouTube',
|
||||||
href: 'https://www.youtube.com/@immich-app',
|
href: 'https://www.youtube.com/@immich-app',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Discord',
|
||||||
|
href: 'https://discord.immich.app',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Reddit',
|
||||||
|
href: 'https://www.reddit.com/r/immich/',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
46
docs/package-lock.json
generated
46
docs/package-lock.json
generated
@@ -12754,9 +12754,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.39",
|
"version": "8.4.40",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz",
|
||||||
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
|
"integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -13600,9 +13600,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
|
||||||
"integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
|
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -13747,9 +13747,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.12.1",
|
"version": "6.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||||
"integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==",
|
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"side-channel": "^1.0.6"
|
"side-channel": "^1.0.6"
|
||||||
},
|
},
|
||||||
@@ -16014,9 +16015,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.4",
|
"version": "3.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.7.tgz",
|
||||||
"integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==",
|
"integrity": "sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
@@ -16376,9 +16377,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.5.3",
|
"version": "5.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
||||||
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
|
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@@ -16714,12 +16715,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/url": {
|
"node_modules/url": {
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz",
|
"resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz",
|
||||||
"integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==",
|
"integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"punycode": "^1.4.1",
|
"punycode": "^1.4.1",
|
||||||
"qs": "^6.11.2"
|
"qs": "^6.12.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/url-loader": {
|
"node_modules/url-loader": {
|
||||||
@@ -16783,7 +16788,8 @@
|
|||||||
"node_modules/url/node_modules/punycode": {
|
"node_modules/url/node_modules/punycode": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||||
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
|
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/util": {
|
"node_modules/util": {
|
||||||
"version": "0.10.4",
|
"version": "0.10.4",
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ const projects: CommunityProjectProps[] = [
|
|||||||
description: 'Powershell Module for the Immich API',
|
description: 'Powershell Module for the Immich API',
|
||||||
url: 'https://github.com/hanpq/PSImmich',
|
url: 'https://github.com/hanpq/PSImmich',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Immich Distribution',
|
||||||
|
description: 'Snap package for easy install and zero-care auto updates of Immich. Self-hosted photo management.',
|
||||||
|
url: 'https://immich-distribution.nsg.cc',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element {
|
function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element {
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
import { mdiCalendarToday, mdiLeadPencil, mdiLockOutline, mdiSpeedometerSlow, mdiWeb } from '@mdi/js';
|
import {
|
||||||
|
mdiCalendarToday,
|
||||||
|
mdiCrosshairsOff,
|
||||||
|
mdiLeadPencil,
|
||||||
|
mdiLockOff,
|
||||||
|
mdiLockOutline,
|
||||||
|
mdiSpeedometerSlow,
|
||||||
|
mdiWeb,
|
||||||
|
mdiWrap,
|
||||||
|
} from '@mdi/js';
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Item as TimelineItem, Timeline } from '../components/timeline';
|
import { Item as TimelineItem, Timeline } from '../components/timeline';
|
||||||
@@ -8,6 +17,41 @@ const withLanguage = (date: Date) => (language: string) => date.toLocaleDateStri
|
|||||||
type Item = Omit<TimelineItem, 'done' | 'getDateLabel'> & { date: Date };
|
type Item = Omit<TimelineItem, 'done' | 'getDateLabel'> & { date: Date };
|
||||||
|
|
||||||
const items: Item[] = [
|
const items: Item[] = [
|
||||||
|
{
|
||||||
|
icon: mdiWrap,
|
||||||
|
iconColor: 'gray',
|
||||||
|
title: 'Carriage returns in bash scripts are cursed',
|
||||||
|
description: 'Git can be configured to automatically convert LF to CRLF on checkout and CRLF breaks bash scripts.',
|
||||||
|
link: {
|
||||||
|
url: 'https://github.com/immich-app/immich/pull/11613',
|
||||||
|
text: '#11613',
|
||||||
|
},
|
||||||
|
date: new Date(2024, 7, 7),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: mdiLockOff,
|
||||||
|
iconColor: 'red',
|
||||||
|
title: 'Fetch inside Cloudflare Workers is cursed',
|
||||||
|
description:
|
||||||
|
'Fetch requests in Cloudflare Workers use http by default, even if you explicitly specify https, which can often cause redirect loops.',
|
||||||
|
link: {
|
||||||
|
url: 'https://community.cloudflare.com/t/does-cloudflare-worker-allow-secure-https-connection-to-fetch-even-on-flexible-ssl/68051/5',
|
||||||
|
text: 'Cloudflare',
|
||||||
|
},
|
||||||
|
date: new Date(2024, 7, 7),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: mdiCrosshairsOff,
|
||||||
|
iconColor: 'gray',
|
||||||
|
title: 'GPS sharing on mobile is cursed',
|
||||||
|
description:
|
||||||
|
'Some phones will silently strip GPS data from images when apps without location permission try to access them.',
|
||||||
|
link: {
|
||||||
|
url: 'https://github.com/immich-app/immich/discussions/11268',
|
||||||
|
text: '#11268',
|
||||||
|
},
|
||||||
|
date: new Date(2024, 6, 21),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: mdiLeadPencil,
|
icon: mdiLeadPencil,
|
||||||
iconColor: 'gold',
|
iconColor: 'gold',
|
||||||
|
|||||||
114
docs/src/pages/privacy-policy.tsx
Normal file
114
docs/src/pages/privacy-policy.tsx
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Link from '@docusaurus/Link';
|
||||||
|
import Layout from '@theme/Layout';
|
||||||
|
import { useColorMode } from '@docusaurus/theme-common';
|
||||||
|
function HomepageHeader() {
|
||||||
|
const { isDarkTheme } = useColorMode();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header>
|
||||||
|
<section className="max-w-[900px] m-4 p-4 md:p-6 md:m-auto md:my-12 border border-red-400 rounded-2xl bg-slate-200 dark:bg-immich-dark-gray">
|
||||||
|
<section>
|
||||||
|
<h1>Privacy Policy</h1>
|
||||||
|
<p>Last updated: July 31st 2024</p>
|
||||||
|
<p>
|
||||||
|
Welcome to Immich. We are committed to respecting your privacy. This Privacy Policy sets out how we collect,
|
||||||
|
use, and share information when you use our Immich app.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 1. Scope of This Policy */}
|
||||||
|
<section>
|
||||||
|
<h2>1. Scope of This Policy</h2>
|
||||||
|
<p>
|
||||||
|
This Privacy Policy applies to the Immich app ("we", "our", or "us") and covers our collection, use, and
|
||||||
|
disclosure of your information. This Policy does not cover any third-party websites, services, or
|
||||||
|
applications that can be accessed through our app, or third-party services you may access through Immich.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 2. Information We Collect */}
|
||||||
|
<section>
|
||||||
|
<h2>2. Information We Collect</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<strong>Locally Stored Data</strong>: Immich stores all your photos, albums, settings, and locally on your
|
||||||
|
device. We do not have access to this data, nor do we transmit or store it on any of our servers.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<strong>Purchase Information:</strong> When you make a purchase within the{' '}
|
||||||
|
<a href="https://buy.immich.app">https://buy.immich.app</a>, we collect the following information for tax
|
||||||
|
calculation purposes:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Country of origin</li>
|
||||||
|
<li>Postal code (if the user is from Canada or the United States)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 3. Use of Your Information */}
|
||||||
|
<section>
|
||||||
|
<h2>3. Use of Your Information</h2>
|
||||||
|
<p>
|
||||||
|
<strong>Tax Calculation:</strong> The country of origin and postal code (for users from Canada or the United
|
||||||
|
States) are collected solely for determining the applicable tax rates on your purchase.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 4. Sharing of Your Information */}
|
||||||
|
<section>
|
||||||
|
<h2>4. Sharing of Your Information</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Tax Authorities:</strong> The purchase information may be shared with tax authorities as required
|
||||||
|
by law.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Payment Providers:</strong> The purchase information may be shared with payment providers where
|
||||||
|
required.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 5. Changes to This Policy */}
|
||||||
|
<section>
|
||||||
|
<h2>5. Changes to This Policy</h2>
|
||||||
|
<p>
|
||||||
|
We may update our Privacy Policy from time to time. If we make any changes, we will notify you by revising
|
||||||
|
the "Last updated" date at the top of this policy. It's encouraged that users frequently check this page for
|
||||||
|
any changes to stay informed about how we are helping to protect the personal information we collect.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 6. Contact Us */}
|
||||||
|
<section>
|
||||||
|
<h2>6. Contact Us</h2>
|
||||||
|
<p>
|
||||||
|
If you have any questions about this Privacy Policy, please contact us at{' '}
|
||||||
|
<a href="mailto:immich@futo.org">immich@futo.org</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Home(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
title="Home"
|
||||||
|
description="immich Self-hosted photo and video backup solution directly from your mobile phone "
|
||||||
|
noFooter={true}
|
||||||
|
>
|
||||||
|
<HomepageHeader />
|
||||||
|
<div className="flex flex-col place-items-center place-content-center">
|
||||||
|
<p>This project is available under GNU AGPL v3 license.</p>
|
||||||
|
<p className="text-xs">Privacy should not be a luxury</p>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
project: 'tsconfig.json',
|
|
||||||
sourceType: 'module',
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
},
|
|
||||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
|
||||||
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', 'plugin:unicorn/recommended'],
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
ignorePatterns: ['.eslintrc.js'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/interface-name-prefix': 'off',
|
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
'@typescript-eslint/no-floating-promises': 'error',
|
|
||||||
'unicorn/prefer-module': 'off',
|
|
||||||
'unicorn/import-style': 'off',
|
|
||||||
curly: 2,
|
|
||||||
'prettier/prettier': 0,
|
|
||||||
'unicorn/prevent-abbreviations': 'off',
|
|
||||||
'unicorn/filename-case': 'off',
|
|
||||||
'unicorn/no-null': 'off',
|
|
||||||
'unicorn/prefer-top-level-await': 'off',
|
|
||||||
'unicorn/prefer-event-target': 'off',
|
|
||||||
'unicorn/no-thenable': 'off',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
64
e2e/eslint.config.mjs
Normal file
64
e2e/eslint.config.mjs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { FlatCompat } from '@eslint/eslintrc';
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import typescriptEslint from '@typescript-eslint/eslint-plugin';
|
||||||
|
import tsParser from '@typescript-eslint/parser';
|
||||||
|
import globals from 'globals';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
allConfig: js.configs.all,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: ['eslint.config.mjs'],
|
||||||
|
},
|
||||||
|
...compat.extends(
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
'plugin:unicorn/recommended',
|
||||||
|
),
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
'@typescript-eslint': typescriptEslint,
|
||||||
|
},
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
|
||||||
|
parser: tsParser,
|
||||||
|
ecmaVersion: 5,
|
||||||
|
sourceType: 'module',
|
||||||
|
|
||||||
|
parserOptions: {
|
||||||
|
project: 'tsconfig.json',
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/interface-name-prefix': 'off',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-floating-promises': 'error',
|
||||||
|
'unicorn/prefer-module': 'off',
|
||||||
|
'unicorn/import-style': 'off',
|
||||||
|
curly: 2,
|
||||||
|
'prettier/prettier': 0,
|
||||||
|
'unicorn/prevent-abbreviations': 'off',
|
||||||
|
'unicorn/filename-case': 'off',
|
||||||
|
'unicorn/no-null': 'off',
|
||||||
|
'unicorn/prefer-top-level-await': 'off',
|
||||||
|
'unicorn/prefer-event-target': 'off',
|
||||||
|
'unicorn/no-thenable': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
1737
e2e/package-lock.json
generated
1737
e2e/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,23 +19,26 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
|
"@eslint/js": "^9.8.0",
|
||||||
"@immich/cli": "file:../cli",
|
"@immich/cli": "file:../cli",
|
||||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||||
"@playwright/test": "^1.44.1",
|
"@playwright/test": "^1.44.1",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/node": "^20.14.12",
|
"@types/node": "^20.14.13",
|
||||||
"@types/oidc-provider": "^8.5.1",
|
"@types/oidc-provider": "^8.5.1",
|
||||||
"@types/pg": "^8.11.0",
|
"@types/pg": "^8.11.0",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^7.1.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"@vitest/coverage-v8": "^1.3.0",
|
"@vitest/coverage-v8": "^2.0.5",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-unicorn": "^54.0.0",
|
"eslint-plugin-unicorn": "^55.0.0",
|
||||||
"exiftool-vendored": "^28.0.0",
|
"exiftool-vendored": "^28.0.0",
|
||||||
|
"globals": "^15.9.0",
|
||||||
"jose": "^5.6.3",
|
"jose": "^5.6.3",
|
||||||
"luxon": "^3.4.4",
|
"luxon": "^3.4.4",
|
||||||
"oidc-provider": "^8.5.1",
|
"oidc-provider": "^8.5.1",
|
||||||
@@ -47,7 +50,7 @@
|
|||||||
"supertest": "^7.0.0",
|
"supertest": "^7.0.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"utimes": "^5.2.1",
|
"utimes": "^5.2.1",
|
||||||
"vitest": "^1.6.0"
|
"vitest": "^2.0.5"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "20.16.0"
|
"node": "20.16.0"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AssetMediaResponseDto, LoginResponseDto, deleteAssets, getMapMarkers, updateAsset } from '@immich/sdk';
|
import { AssetMediaResponseDto, LoginResponseDto, deleteAssets, updateAsset } from '@immich/sdk';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
@@ -32,9 +32,6 @@ describe('/search', () => {
|
|||||||
let assetOneJpg5: AssetMediaResponseDto;
|
let assetOneJpg5: AssetMediaResponseDto;
|
||||||
let assetSprings: AssetMediaResponseDto;
|
let assetSprings: AssetMediaResponseDto;
|
||||||
let assetLast: AssetMediaResponseDto;
|
let assetLast: AssetMediaResponseDto;
|
||||||
let cities: string[];
|
|
||||||
let states: string[];
|
|
||||||
let countries: string[];
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await utils.resetDatabase();
|
await utils.resetDatabase();
|
||||||
@@ -85,7 +82,7 @@ describe('/search', () => {
|
|||||||
// note: the coordinates here are not the actual coordinates of the images and are random for most of them
|
// note: the coordinates here are not the actual coordinates of the images and are random for most of them
|
||||||
const coordinates = [
|
const coordinates = [
|
||||||
{ latitude: 48.853_41, longitude: 2.3488 }, // paris
|
{ latitude: 48.853_41, longitude: 2.3488 }, // paris
|
||||||
{ latitude: 63.0695, longitude: -151.0074 }, // denali
|
{ latitude: 35.6895, longitude: 139.691_71 }, // tokyo
|
||||||
{ latitude: 52.524_37, longitude: 13.410_53 }, // berlin
|
{ latitude: 52.524_37, longitude: 13.410_53 }, // berlin
|
||||||
{ latitude: 1.314_663_1, longitude: 103.845_409_3 }, // singapore
|
{ latitude: 1.314_663_1, longitude: 103.845_409_3 }, // singapore
|
||||||
{ latitude: 41.013_84, longitude: 28.949_66 }, // istanbul
|
{ latitude: 41.013_84, longitude: 28.949_66 }, // istanbul
|
||||||
@@ -101,16 +98,15 @@ describe('/search', () => {
|
|||||||
{ latitude: 31.634_16, longitude: -7.999_94 }, // marrakesh
|
{ latitude: 31.634_16, longitude: -7.999_94 }, // marrakesh
|
||||||
{ latitude: 38.523_735_4, longitude: -78.488_619_4 }, // tanners ridge
|
{ latitude: 38.523_735_4, longitude: -78.488_619_4 }, // tanners ridge
|
||||||
{ latitude: 59.938_63, longitude: 30.314_13 }, // st. petersburg
|
{ latitude: 59.938_63, longitude: 30.314_13 }, // st. petersburg
|
||||||
{ latitude: 35.6895, longitude: 139.691_71 }, // tokyo
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const updates = assets.map((asset, i) =>
|
const updates = coordinates.map((dto, i) =>
|
||||||
updateAsset({ id: asset.id, updateAssetDto: coordinates[i] }, { headers: asBearerAuth(admin.accessToken) }),
|
updateAsset({ id: assets[i].id, updateAssetDto: dto }, { headers: asBearerAuth(admin.accessToken) }),
|
||||||
);
|
);
|
||||||
|
|
||||||
await Promise.all(updates);
|
await Promise.all(updates);
|
||||||
for (const asset of assets) {
|
for (const [i] of coordinates.entries()) {
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpdate', id: asset.id });
|
await utils.waitForWebsocketEvent({ event: 'assetUpdate', id: assets[i].id });
|
||||||
}
|
}
|
||||||
|
|
||||||
[
|
[
|
||||||
@@ -137,12 +133,6 @@ describe('/search', () => {
|
|||||||
assetLast = assets.at(-1) as AssetMediaResponseDto;
|
assetLast = assets.at(-1) as AssetMediaResponseDto;
|
||||||
|
|
||||||
await deleteAssets({ assetBulkDeleteDto: { ids: [assetSilver.id] } }, { headers: asBearerAuth(admin.accessToken) });
|
await deleteAssets({ assetBulkDeleteDto: { ids: [assetSilver.id] } }, { headers: asBearerAuth(admin.accessToken) });
|
||||||
|
|
||||||
const mapMarkers = await getMapMarkers({}, { headers: asBearerAuth(admin.accessToken) });
|
|
||||||
const nonTrashed = mapMarkers.filter((mark) => mark.id !== assetSilver.id);
|
|
||||||
cities = [...new Set(nonTrashed.map((mark) => mark.city).filter((entry): entry is string => !!entry))].sort();
|
|
||||||
states = [...new Set(nonTrashed.map((mark) => mark.state).filter((entry): entry is string => !!entry))].sort();
|
|
||||||
countries = [...new Set(nonTrashed.map((mark) => mark.country).filter((entry): entry is string => !!entry))].sort();
|
|
||||||
}, 30_000);
|
}, 30_000);
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@@ -321,23 +311,120 @@ describe('/search', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
should: 'should search by city',
|
should: 'should search by city',
|
||||||
deferred: () => ({ dto: { city: 'Accra' }, assets: [assetHeic] }),
|
deferred: () => ({
|
||||||
|
dto: {
|
||||||
|
city: 'Accra',
|
||||||
|
includeNull: true,
|
||||||
|
},
|
||||||
|
assets: [assetHeic],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
should: "should search city ('')",
|
||||||
|
deferred: () => ({
|
||||||
|
dto: {
|
||||||
|
city: '',
|
||||||
|
isVisible: true,
|
||||||
|
includeNull: true,
|
||||||
|
},
|
||||||
|
assets: [assetLast],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
should: 'should search city (null)',
|
||||||
|
deferred: () => ({
|
||||||
|
dto: {
|
||||||
|
city: null,
|
||||||
|
isVisible: true,
|
||||||
|
includeNull: true,
|
||||||
|
},
|
||||||
|
assets: [assetLast],
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
should: 'should search by state',
|
should: 'should search by state',
|
||||||
deferred: () => ({ dto: { state: 'New York' }, assets: [assetDensity] }),
|
deferred: () => ({
|
||||||
|
dto: {
|
||||||
|
state: 'New York',
|
||||||
|
includeNull: true,
|
||||||
|
},
|
||||||
|
assets: [assetDensity],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
should: "should search state ('')",
|
||||||
|
deferred: () => ({
|
||||||
|
dto: {
|
||||||
|
state: '',
|
||||||
|
isVisible: true,
|
||||||
|
withExif: true,
|
||||||
|
includeNull: true,
|
||||||
|
},
|
||||||
|
assets: [assetLast, assetNotocactus],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
should: 'should search state (null)',
|
||||||
|
deferred: () => ({
|
||||||
|
dto: {
|
||||||
|
state: null,
|
||||||
|
isVisible: true,
|
||||||
|
includeNull: true,
|
||||||
|
},
|
||||||
|
assets: [assetLast, assetNotocactus],
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
should: 'should search by country',
|
should: 'should search by country',
|
||||||
deferred: () => ({ dto: { country: 'France' }, assets: [assetFalcon] }),
|
deferred: () => ({
|
||||||
|
dto: {
|
||||||
|
country: 'France',
|
||||||
|
includeNull: true,
|
||||||
|
},
|
||||||
|
assets: [assetFalcon],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
should: "should search country ('')",
|
||||||
|
deferred: () => ({
|
||||||
|
dto: {
|
||||||
|
country: '',
|
||||||
|
isVisible: true,
|
||||||
|
includeNull: true,
|
||||||
|
},
|
||||||
|
assets: [assetLast],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
should: 'should search country (null)',
|
||||||
|
deferred: () => ({
|
||||||
|
dto: {
|
||||||
|
country: null,
|
||||||
|
isVisible: true,
|
||||||
|
includeNull: true,
|
||||||
|
},
|
||||||
|
assets: [assetLast],
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
should: 'should search by make',
|
should: 'should search by make',
|
||||||
deferred: () => ({ dto: { make: 'Canon' }, assets: [assetFalcon, assetDenali] }),
|
deferred: () => ({
|
||||||
|
dto: {
|
||||||
|
make: 'Canon',
|
||||||
|
includeNull: true,
|
||||||
|
},
|
||||||
|
assets: [assetFalcon, assetDenali],
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
should: 'should search by model',
|
should: 'should search by model',
|
||||||
deferred: () => ({ dto: { model: 'Canon EOS 7D' }, assets: [assetDenali] }),
|
deferred: () => ({
|
||||||
|
dto: {
|
||||||
|
model: 'Canon EOS 7D',
|
||||||
|
includeNull: true,
|
||||||
|
},
|
||||||
|
assets: [assetDenali],
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
should: 'should allow searching the upload library (libraryId: null)',
|
should: 'should allow searching the upload library (libraryId: null)',
|
||||||
@@ -450,32 +537,79 @@ describe('/search', () => {
|
|||||||
|
|
||||||
it('should get suggestions for country', async () => {
|
it('should get suggestions for country', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get('/search/suggestions?type=country')
|
.get('/search/suggestions?type=country&includeNull=true')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(body).toEqual(countries);
|
expect(body).toEqual([
|
||||||
|
'Cuba',
|
||||||
|
'France',
|
||||||
|
'Georgia',
|
||||||
|
'Germany',
|
||||||
|
'Ghana',
|
||||||
|
'Japan',
|
||||||
|
'Morocco',
|
||||||
|
"People's Republic of China",
|
||||||
|
'Russian Federation',
|
||||||
|
'Singapore',
|
||||||
|
'Spain',
|
||||||
|
'Switzerland',
|
||||||
|
'United States of America',
|
||||||
|
null,
|
||||||
|
]);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get suggestions for state', async () => {
|
it('should get suggestions for state', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get('/search/suggestions?type=state')
|
.get('/search/suggestions?type=state&includeNull=true')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(body).toHaveLength(states.length);
|
expect(body).toEqual([
|
||||||
expect(body).toEqual(expect.arrayContaining(states));
|
'Andalusia',
|
||||||
|
'Berlin',
|
||||||
|
'Glarus',
|
||||||
|
'Greater Accra',
|
||||||
|
'Havana',
|
||||||
|
'Île-de-France',
|
||||||
|
'Marrakesh-Safi',
|
||||||
|
'Mississippi',
|
||||||
|
'New York',
|
||||||
|
'Shanghai',
|
||||||
|
'St.-Petersburg',
|
||||||
|
'Tbilisi',
|
||||||
|
'Tokyo',
|
||||||
|
'Virginia',
|
||||||
|
null,
|
||||||
|
]);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get suggestions for city', async () => {
|
it('should get suggestions for city', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get('/search/suggestions?type=city')
|
.get('/search/suggestions?type=city&includeNull=true')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(body).toEqual(cities);
|
expect(body).toEqual([
|
||||||
|
'Accra',
|
||||||
|
'Berlin',
|
||||||
|
'Glarus',
|
||||||
|
'Havana',
|
||||||
|
'Marrakesh',
|
||||||
|
'Montalbán de Córdoba',
|
||||||
|
'New York City',
|
||||||
|
'Novena',
|
||||||
|
'Paris',
|
||||||
|
'Philadelphia',
|
||||||
|
'Saint Petersburg',
|
||||||
|
'Shanghai',
|
||||||
|
'Stanley',
|
||||||
|
'Tbilisi',
|
||||||
|
'Tokyo',
|
||||||
|
null,
|
||||||
|
]);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get suggestions for camera make', async () => {
|
it('should get suggestions for camera make', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get('/search/suggestions?type=camera-make')
|
.get('/search/suggestions?type=camera-make&includeNull=true')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
'Apple',
|
'Apple',
|
||||||
@@ -485,13 +619,14 @@ describe('/search', () => {
|
|||||||
'PENTAX Corporation',
|
'PENTAX Corporation',
|
||||||
'samsung',
|
'samsung',
|
||||||
'SONY',
|
'SONY',
|
||||||
|
null,
|
||||||
]);
|
]);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get suggestions for camera model', async () => {
|
it('should get suggestions for camera model', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get('/search/suggestions?type=camera-model')
|
.get('/search/suggestions?type=camera-model&includeNull=true')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
'Canon EOS 7D',
|
'Canon EOS 7D',
|
||||||
@@ -506,6 +641,7 @@ describe('/search', () => {
|
|||||||
'SM-F711N',
|
'SM-F711N',
|
||||||
'SM-S906U',
|
'SM-S906U',
|
||||||
'SM-T970',
|
'SM-T970',
|
||||||
|
null,
|
||||||
]);
|
]);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -424,12 +424,12 @@ export const utils = {
|
|||||||
|
|
||||||
createPartner: (accessToken: string, id: string) => createPartner({ id }, { headers: asBearerAuth(accessToken) }),
|
createPartner: (accessToken: string, id: string) => createPartner({ id }, { headers: asBearerAuth(accessToken) }),
|
||||||
|
|
||||||
setAuthCookies: async (context: BrowserContext, accessToken: string) =>
|
setAuthCookies: async (context: BrowserContext, accessToken: string, domain = '127.0.0.1') =>
|
||||||
await context.addCookies([
|
await context.addCookies([
|
||||||
{
|
{
|
||||||
name: 'immich_access_token',
|
name: 'immich_access_token',
|
||||||
value: accessToken,
|
value: accessToken,
|
||||||
domain: '127.0.0.1',
|
domain,
|
||||||
path: '/',
|
path: '/',
|
||||||
expires: 1_742_402_728,
|
expires: 1_742_402_728,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
@@ -439,7 +439,7 @@ export const utils = {
|
|||||||
{
|
{
|
||||||
name: 'immich_auth_type',
|
name: 'immich_auth_type',
|
||||||
value: 'password',
|
value: 'password',
|
||||||
domain: '127.0.0.1',
|
domain,
|
||||||
path: '/',
|
path: '/',
|
||||||
expires: 1_742_402_728,
|
expires: 1_742_402_728,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
@@ -449,7 +449,7 @@ export const utils = {
|
|||||||
{
|
{
|
||||||
name: 'immich_is_authenticated',
|
name: 'immich_is_authenticated',
|
||||||
value: 'true',
|
value: 'true',
|
||||||
domain: '127.0.0.1',
|
domain,
|
||||||
path: '/',
|
path: '/',
|
||||||
expires: 1_742_402_728,
|
expires: 1_742_402_728,
|
||||||
httpOnly: false,
|
httpOnly: false,
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ test.describe('Asset Viewer Navbar', () => {
|
|||||||
utils.initSdk();
|
utils.initSdk();
|
||||||
await utils.resetDatabase();
|
await utils.resetDatabase();
|
||||||
admin = await utils.adminSetup();
|
admin = await utils.adminSetup();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeEach(async () => {
|
||||||
asset = await utils.createAsset(admin.accessToken);
|
asset = await utils.createAsset(admin.accessToken);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,4 +52,14 @@ test.describe('Asset Viewer Navbar', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('actions', () => {
|
||||||
|
test('favorite asset with shortcut', async ({ context, page }) => {
|
||||||
|
await utils.setAuthCookies(context, admin.accessToken);
|
||||||
|
await page.goto(`/photos/${asset.id}`);
|
||||||
|
await page.waitForSelector('#immich-asset-viewer');
|
||||||
|
await page.keyboard.press('f');
|
||||||
|
await expect(page.locator('#notification-list').getByTestId('message')).toHaveText('Added to favorites');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
56
e2e/src/web/specs/asset-viewer/slideshow.e2e-spec.ts
Normal file
56
e2e/src/web/specs/asset-viewer/slideshow.e2e-spec.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk';
|
||||||
|
import { expect, type Page, test } from '@playwright/test';
|
||||||
|
import { utils } from 'src/utils';
|
||||||
|
|
||||||
|
test.describe('Slideshow', () => {
|
||||||
|
let admin: LoginResponseDto;
|
||||||
|
let asset: AssetMediaResponseDto;
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
utils.initSdk();
|
||||||
|
await utils.resetDatabase();
|
||||||
|
admin = await utils.adminSetup();
|
||||||
|
asset = await utils.createAsset(admin.accessToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
const openSlideshow = async (page: Page) => {
|
||||||
|
await page.goto(`/photos/${asset.id}`);
|
||||||
|
await page.waitForSelector('#immich-asset-viewer');
|
||||||
|
await page.getByRole('button', { name: 'More' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Slideshow' }).click();
|
||||||
|
};
|
||||||
|
|
||||||
|
test('open slideshow', async ({ context, page }) => {
|
||||||
|
await utils.setAuthCookies(context, admin.accessToken);
|
||||||
|
await openSlideshow(page);
|
||||||
|
await expect(page.getByRole('button', { name: 'Exit Slideshow' })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exit slideshow with button', async ({ context, page }) => {
|
||||||
|
await utils.setAuthCookies(context, admin.accessToken);
|
||||||
|
await openSlideshow(page);
|
||||||
|
|
||||||
|
const exitButton = page.getByRole('button', { name: 'Exit Slideshow' });
|
||||||
|
await exitButton.click();
|
||||||
|
await expect(exitButton).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exit slideshow with shortcut', async ({ context, page }) => {
|
||||||
|
await utils.setAuthCookies(context, admin.accessToken);
|
||||||
|
await openSlideshow(page);
|
||||||
|
|
||||||
|
const exitButton = page.getByRole('button', { name: 'Exit Slideshow' });
|
||||||
|
await expect(exitButton).toBeVisible();
|
||||||
|
await page.keyboard.press('Escape');
|
||||||
|
await expect(exitButton).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('favorite shortcut is disabled', async ({ context, page }) => {
|
||||||
|
await utils.setAuthCookies(context, admin.accessToken);
|
||||||
|
await openSlideshow(page);
|
||||||
|
|
||||||
|
await expect(page.getByRole('button', { name: 'Exit Slideshow' })).toBeVisible();
|
||||||
|
await page.keyboard.press('f');
|
||||||
|
await expect(page.locator('#notification-list')).not.toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -25,7 +25,7 @@ test.describe('Photo Viewer', () => {
|
|||||||
|
|
||||||
test('initially shows a loading spinner', async ({ page }) => {
|
test('initially shows a loading spinner', async ({ page }) => {
|
||||||
await page.route(`/api/assets/${asset.id}/thumbnail**`, async (route) => {
|
await page.route(`/api/assets/${asset.id}/thumbnail**`, async (route) => {
|
||||||
// slow down the request for thumbnail, so spiner has chance to show up
|
// slow down the request for thumbnail, so spinner has chance to show up
|
||||||
await new Promise((f) => setTimeout(f, 2000));
|
await new Promise((f) => setTimeout(f, 2000));
|
||||||
await route.continue();
|
await route.continue();
|
||||||
});
|
});
|
||||||
@@ -40,7 +40,7 @@ test.describe('Photo Viewer', () => {
|
|||||||
await page.goto(`/photos/${asset.id}`);
|
await page.goto(`/photos/${asset.id}`);
|
||||||
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
|
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
|
||||||
const box = await imageLocator(page).boundingBox();
|
const box = await imageLocator(page).boundingBox();
|
||||||
expect(box).toBeTruthy;
|
expect(box).toBeTruthy();
|
||||||
const { x, y, width, height } = box!;
|
const { x, y, width, height } = box!;
|
||||||
await page.mouse.move(x + width / 2, y + height / 2);
|
await page.mouse.move(x + width / 2, y + height / 2);
|
||||||
await page.mouse.wheel(0, -1);
|
await page.mouse.wheel(0, -1);
|
||||||
|
|||||||
25
e2e/src/web/specs/websocket.e2e-spec.ts
Normal file
25
e2e/src/web/specs/websocket.e2e-spec.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { LoginResponseDto } from '@immich/sdk';
|
||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
import { utils } from 'src/utils';
|
||||||
|
|
||||||
|
test.describe('Websocket', () => {
|
||||||
|
let admin: LoginResponseDto;
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
utils.initSdk();
|
||||||
|
await utils.resetDatabase();
|
||||||
|
admin = await utils.adminSetup();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('connects using ipv4', async ({ page, context }) => {
|
||||||
|
await utils.setAuthCookies(context, admin.accessToken);
|
||||||
|
await page.goto('http://127.0.0.1:2283/');
|
||||||
|
await expect(page.locator('#sidebar')).toContainText('Server Online');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('connects using ipv6', async ({ page, context }) => {
|
||||||
|
await utils.setAuthCookies(context, admin.accessToken, '[::1]');
|
||||||
|
await page.goto('http://[::1]:2283/');
|
||||||
|
await expect(page.locator('#sidebar')).toContainText('Server Online');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -13,6 +13,7 @@ export default defineConfig({
|
|||||||
include: ['src/{api,cli,immich-admin}/specs/*.e2e-spec.ts'],
|
include: ['src/{api,cli,immich-admin}/specs/*.e2e-spec.ts'],
|
||||||
globalSetup,
|
globalSetup,
|
||||||
testTimeout: 15_000,
|
testTimeout: 15_000,
|
||||||
|
pool: 'threads',
|
||||||
poolOptions: {
|
poolOptions: {
|
||||||
threads: {
|
threads: {
|
||||||
singleThread: true,
|
singleThread: true,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
ARG DEVICE=cpu
|
ARG DEVICE=cpu
|
||||||
|
|
||||||
FROM python:3.11-bookworm@sha256:ef4b550f029a76b94f8e6cc6e4a8ed0e870fc6c5af1c4e9d77faaea50f41f6cd as builder-cpu
|
FROM python:3.11-bookworm@sha256:d0131ce0ff4bdb5e9eae6bc86ebde891c207d5cac1f3f582b5de0f903cc68384 AS builder-cpu
|
||||||
|
|
||||||
FROM builder-cpu as builder-openvino
|
FROM builder-cpu AS builder-openvino
|
||||||
|
|
||||||
FROM builder-cpu as builder-cuda
|
FROM builder-cpu AS builder-cuda
|
||||||
|
|
||||||
FROM builder-cpu as builder-armnn
|
FROM builder-cpu AS builder-armnn
|
||||||
|
|
||||||
ENV ARMNN_PATH=/opt/armnn
|
ENV ARMNN_PATH=/opt/armnn
|
||||||
COPY ann /opt/ann
|
COPY ann /opt/ann
|
||||||
@@ -15,7 +15,7 @@ RUN mkdir /opt/armnn && \
|
|||||||
cd /opt/ann && \
|
cd /opt/ann && \
|
||||||
sh build.sh
|
sh build.sh
|
||||||
|
|
||||||
FROM builder-${DEVICE} as builder
|
FROM builder-${DEVICE} AS builder
|
||||||
|
|
||||||
ARG DEVICE
|
ARG DEVICE
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
@@ -34,9 +34,9 @@ RUN python3 -m venv /opt/venv
|
|||||||
COPY poetry.lock pyproject.toml ./
|
COPY poetry.lock pyproject.toml ./
|
||||||
RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev
|
RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev
|
||||||
|
|
||||||
FROM python:3.11-slim-bookworm@sha256:ee317183d292ee6ed30e90bc325043ca3f7d2e8c79ac5019575490b5256ae244 as prod-cpu
|
FROM python:3.11-slim-bookworm@sha256:a90e299af8a9cd6b59c4aaed2b024c78561476978244a1ab89742a4a5ac8c974 AS prod-cpu
|
||||||
|
|
||||||
FROM prod-cpu as prod-openvino
|
FROM prod-cpu AS prod-openvino
|
||||||
|
|
||||||
COPY scripts/configure-apt.sh ./
|
COPY scripts/configure-apt.sh ./
|
||||||
RUN ./configure-apt.sh && \
|
RUN ./configure-apt.sh && \
|
||||||
@@ -44,13 +44,13 @@ RUN ./configure-apt.sh && \
|
|||||||
apt-get install -t unstable --no-install-recommends -yqq intel-opencl-icd && \
|
apt-get install -t unstable --no-install-recommends -yqq intel-opencl-icd && \
|
||||||
rm configure-apt.sh
|
rm configure-apt.sh
|
||||||
|
|
||||||
FROM nvidia/cuda:12.3.2-cudnn9-runtime-ubuntu22.04@sha256:fa44193567d1908f7ca1f3abf8623ce9c63bc8cba7bcfdb32702eb04d326f7a8 as prod-cuda
|
FROM nvidia/cuda:12.3.2-cudnn9-runtime-ubuntu22.04@sha256:fa44193567d1908f7ca1f3abf8623ce9c63bc8cba7bcfdb32702eb04d326f7a8 AS prod-cuda
|
||||||
|
|
||||||
COPY --from=builder-cuda /usr/local/bin/python3 /usr/local/bin/python3
|
COPY --from=builder-cuda /usr/local/bin/python3 /usr/local/bin/python3
|
||||||
COPY --from=builder-cuda /usr/local/lib/python3.11 /usr/local/lib/python3.11
|
COPY --from=builder-cuda /usr/local/lib/python3.11 /usr/local/lib/python3.11
|
||||||
COPY --from=builder-cuda /usr/local/lib/libpython3.11.so /usr/local/lib/libpython3.11.so
|
COPY --from=builder-cuda /usr/local/lib/libpython3.11.so /usr/local/lib/libpython3.11.so
|
||||||
|
|
||||||
FROM prod-cpu as prod-armnn
|
FROM prod-cpu AS prod-armnn
|
||||||
|
|
||||||
ENV LD_LIBRARY_PATH=/opt/armnn
|
ENV LD_LIBRARY_PATH=/opt/armnn
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ COPY --from=builder-armnn \
|
|||||||
/opt/ann/build.sh \
|
/opt/ann/build.sh \
|
||||||
/opt/armnn/
|
/opt/armnn/
|
||||||
|
|
||||||
FROM prod-${DEVICE} as prod
|
FROM prod-${DEVICE} AS prod
|
||||||
ARG DEVICE
|
ARG DEVICE
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class Ann(metaclass=_Singleton):
|
|||||||
self.input_shapes: dict[int, tuple[tuple[int], ...]] = {}
|
self.input_shapes: dict[int, tuple[tuple[int], ...]] = {}
|
||||||
self.ann: int | None = None
|
self.ann: int | None = None
|
||||||
self.new()
|
self.new()
|
||||||
|
|
||||||
if self.tuning_file is not None:
|
if self.tuning_file is not None:
|
||||||
# make sure tuning file exists (without clearing contents)
|
# make sure tuning file exists (without clearing contents)
|
||||||
# once filled, the tuning file reduces the cost/time of the first
|
# once filled, the tuning file reduces the cost/time of the first
|
||||||
@@ -105,7 +105,7 @@ class Ann(metaclass=_Singleton):
|
|||||||
raise ValueError("model_path must be a file with extension .armnn, .tflite or .onnx")
|
raise ValueError("model_path must be a file with extension .armnn, .tflite or .onnx")
|
||||||
if not exists(model_path):
|
if not exists(model_path):
|
||||||
raise ValueError("model_path must point to an existing file!")
|
raise ValueError("model_path must point to an existing file!")
|
||||||
|
|
||||||
save_cached_network = False
|
save_cached_network = False
|
||||||
if cached_network_path is not None and not exists(cached_network_path):
|
if cached_network_path is not None and not exists(cached_network_path):
|
||||||
save_cached_network = True
|
save_cached_network = True
|
||||||
|
|||||||
@@ -2,53 +2,64 @@ from app.config import clean_name
|
|||||||
from app.schemas import ModelSource
|
from app.schemas import ModelSource
|
||||||
|
|
||||||
_OPENCLIP_MODELS = {
|
_OPENCLIP_MODELS = {
|
||||||
"RN50__openai",
|
|
||||||
"RN50__yfcc15m",
|
|
||||||
"RN50__cc12m",
|
|
||||||
"RN101__openai",
|
"RN101__openai",
|
||||||
"RN101__yfcc15m",
|
"RN101__yfcc15m",
|
||||||
"RN50x4__openai",
|
"RN50__cc12m",
|
||||||
|
"RN50__openai",
|
||||||
|
"RN50__yfcc15m",
|
||||||
"RN50x16__openai",
|
"RN50x16__openai",
|
||||||
|
"RN50x4__openai",
|
||||||
"RN50x64__openai",
|
"RN50x64__openai",
|
||||||
"ViT-B-32__openai",
|
"ViT-B-16-SigLIP-256__webli",
|
||||||
|
"ViT-B-16-SigLIP-384__webli",
|
||||||
|
"ViT-B-16-SigLIP-512__webli",
|
||||||
|
"ViT-B-16-SigLIP-i18n-256__webli",
|
||||||
|
"ViT-B-16-SigLIP__webli",
|
||||||
|
"ViT-B-16-plus-240__laion400m_e31",
|
||||||
|
"ViT-B-16-plus-240__laion400m_e32",
|
||||||
|
"ViT-B-16__laion400m_e31",
|
||||||
|
"ViT-B-16__laion400m_e32",
|
||||||
|
"ViT-B-16__openai",
|
||||||
|
"ViT-B-32__laion2b-s34b-b79k",
|
||||||
"ViT-B-32__laion2b_e16",
|
"ViT-B-32__laion2b_e16",
|
||||||
"ViT-B-32__laion400m_e31",
|
"ViT-B-32__laion400m_e31",
|
||||||
"ViT-B-32__laion400m_e32",
|
"ViT-B-32__laion400m_e32",
|
||||||
"ViT-B-32__laion2b-s34b-b79k",
|
"ViT-B-32__openai",
|
||||||
"ViT-B-16__openai",
|
"ViT-H-14-378-quickgelu__dfn5b",
|
||||||
"ViT-B-16__laion400m_e31",
|
"ViT-H-14-quickgelu__dfn5b",
|
||||||
"ViT-B-16__laion400m_e32",
|
"ViT-H-14__laion2b-s32b-b79k",
|
||||||
"ViT-B-16-plus-240__laion400m_e31",
|
"ViT-L-14-336__openai",
|
||||||
"ViT-B-16-plus-240__laion400m_e32",
|
"ViT-L-14-quickgelu__dfn2b",
|
||||||
"ViT-L-14__openai",
|
"ViT-L-14__laion2b-s32b-b82k",
|
||||||
"ViT-L-14__laion400m_e31",
|
"ViT-L-14__laion400m_e31",
|
||||||
"ViT-L-14__laion400m_e32",
|
"ViT-L-14__laion400m_e32",
|
||||||
"ViT-L-14__laion2b-s32b-b82k",
|
"ViT-L-14__openai",
|
||||||
"ViT-L-14-336__openai",
|
"ViT-L-16-SigLIP-256__webli",
|
||||||
"ViT-H-14__laion2b-s32b-b79k",
|
"ViT-L-16-SigLIP-384__webli",
|
||||||
|
"ViT-SO400M-14-SigLIP-384__webli",
|
||||||
"ViT-g-14__laion2b-s12b-b42k",
|
"ViT-g-14__laion2b-s12b-b42k",
|
||||||
"ViT-L-14-quickgelu__dfn2b",
|
"XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k",
|
||||||
"ViT-H-14-quickgelu__dfn5b",
|
|
||||||
"ViT-H-14-378-quickgelu__dfn5b",
|
|
||||||
"XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k",
|
"XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k",
|
||||||
|
"nllb-clip-base-siglip__mrl",
|
||||||
"nllb-clip-base-siglip__v1",
|
"nllb-clip-base-siglip__v1",
|
||||||
|
"nllb-clip-large-siglip__mrl",
|
||||||
"nllb-clip-large-siglip__v1",
|
"nllb-clip-large-siglip__v1",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_MCLIP_MODELS = {
|
_MCLIP_MODELS = {
|
||||||
"LABSE-Vit-L-14",
|
"LABSE-Vit-L-14",
|
||||||
"XLM-Roberta-Large-Vit-B-32",
|
|
||||||
"XLM-Roberta-Large-Vit-B-16Plus",
|
"XLM-Roberta-Large-Vit-B-16Plus",
|
||||||
|
"XLM-Roberta-Large-Vit-B-32",
|
||||||
"XLM-Roberta-Large-Vit-L-14",
|
"XLM-Roberta-Large-Vit-L-14",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_INSIGHTFACE_MODELS = {
|
_INSIGHTFACE_MODELS = {
|
||||||
"antelopev2",
|
"antelopev2",
|
||||||
"buffalo_l",
|
|
||||||
"buffalo_m",
|
|
||||||
"buffalo_s",
|
"buffalo_s",
|
||||||
|
"buffalo_m",
|
||||||
|
"buffalo_l",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM mambaorg/micromamba:bookworm-slim@sha256:94d6837f023c0fc0bb68782dd2a984ff7fe0e21ea7e533056c9b8ca060e31de2 as builder
|
FROM mambaorg/micromamba:bookworm-slim@sha256:954e438daab0ad0835430ea84acb27dd47d1ea35a7120c3c9dd9d1a5578f4b13 AS builder
|
||||||
|
|
||||||
ENV TRANSFORMERS_CACHE=/cache \
|
ENV TRANSFORMERS_CACHE=/cache \
|
||||||
PYTHONDONTWRITEBYTECODE=1 \
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ name: base
|
|||||||
channels:
|
channels:
|
||||||
- conda-forge
|
- conda-forge
|
||||||
- nvidia
|
- nvidia
|
||||||
- pytorch-nightly
|
- pytorch
|
||||||
platforms:
|
platforms:
|
||||||
- linux-64
|
- linux-64
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -13,7 +13,7 @@ dependencies:
|
|||||||
- orjson==3.*
|
- orjson==3.*
|
||||||
- pip
|
- pip
|
||||||
- python==3.11.*
|
- python==3.11.*
|
||||||
- pytorch
|
- pytorch>=2.3
|
||||||
- rich==13.*
|
- rich==13.*
|
||||||
- safetensors==0.*
|
- safetensors==0.*
|
||||||
- setuptools==68.*
|
- setuptools==68.*
|
||||||
@@ -21,5 +21,5 @@ dependencies:
|
|||||||
- transformers==4.*
|
- transformers==4.*
|
||||||
- pip:
|
- pip:
|
||||||
- multilingual-clip
|
- multilingual-clip
|
||||||
- onnx-simplifier
|
- onnxsim
|
||||||
category: main
|
category: main
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import warnings
|
import warnings
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -8,7 +9,6 @@ from transformers import AutoTokenizer
|
|||||||
|
|
||||||
from .openclip import OpenCLIPModelConfig
|
from .openclip import OpenCLIPModelConfig
|
||||||
from .openclip import to_onnx as openclip_to_onnx
|
from .openclip import to_onnx as openclip_to_onnx
|
||||||
from .optimize import optimize
|
|
||||||
from .util import get_model_path
|
from .util import get_model_path
|
||||||
|
|
||||||
_MCLIP_TO_OPENCLIP = {
|
_MCLIP_TO_OPENCLIP = {
|
||||||
@@ -23,18 +23,20 @@ def to_onnx(
|
|||||||
model_name: str,
|
model_name: str,
|
||||||
output_dir_visual: Path | str,
|
output_dir_visual: Path | str,
|
||||||
output_dir_textual: Path | str,
|
output_dir_textual: Path | str,
|
||||||
) -> None:
|
) -> tuple[Path, Path]:
|
||||||
textual_path = get_model_path(output_dir_textual)
|
textual_path = get_model_path(output_dir_textual)
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
model = MultilingualCLIP.from_pretrained(model_name, cache_dir=tmpdir)
|
model = MultilingualCLIP.from_pretrained(model_name, cache_dir=os.environ.get("CACHE_DIR", tmpdir))
|
||||||
AutoTokenizer.from_pretrained(model_name).save_pretrained(output_dir_textual)
|
AutoTokenizer.from_pretrained(model_name).save_pretrained(output_dir_textual)
|
||||||
|
|
||||||
|
model.eval()
|
||||||
for param in model.parameters():
|
for param in model.parameters():
|
||||||
param.requires_grad_(False)
|
param.requires_grad_(False)
|
||||||
|
|
||||||
export_text_encoder(model, textual_path)
|
export_text_encoder(model, textual_path)
|
||||||
openclip_to_onnx(_MCLIP_TO_OPENCLIP[model_name], output_dir_visual)
|
visual_path, _ = openclip_to_onnx(_MCLIP_TO_OPENCLIP[model_name], output_dir_visual)
|
||||||
optimize(textual_path)
|
assert visual_path is not None, "Visual model export failed"
|
||||||
|
return visual_path, textual_path
|
||||||
|
|
||||||
|
|
||||||
def export_text_encoder(model: MultilingualCLIP, output_path: Path | str) -> None:
|
def export_text_encoder(model: MultilingualCLIP, output_path: Path | str) -> None:
|
||||||
@@ -58,10 +60,10 @@ def export_text_encoder(model: MultilingualCLIP, output_path: Path | str) -> Non
|
|||||||
args,
|
args,
|
||||||
output_path.as_posix(),
|
output_path.as_posix(),
|
||||||
input_names=["input_ids", "attention_mask"],
|
input_names=["input_ids", "attention_mask"],
|
||||||
output_names=["text_embedding"],
|
output_names=["embedding"],
|
||||||
opset_version=17,
|
opset_version=17,
|
||||||
dynamic_axes={
|
# dynamic_axes={
|
||||||
"input_ids": {0: "batch_size", 1: "sequence_length"},
|
# "input_ids": {0: "batch_size", 1: "sequence_length"},
|
||||||
"attention_mask": {0: "batch_size", 1: "sequence_length"},
|
# "attention_mask": {0: "batch_size", 1: "sequence_length"},
|
||||||
},
|
# },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import warnings
|
import warnings
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
@@ -7,7 +8,6 @@ import open_clip
|
|||||||
import torch
|
import torch
|
||||||
from transformers import AutoTokenizer
|
from transformers import AutoTokenizer
|
||||||
|
|
||||||
from .optimize import optimize
|
|
||||||
from .util import get_model_path, save_config
|
from .util import get_model_path, save_config
|
||||||
|
|
||||||
|
|
||||||
@@ -23,25 +23,28 @@ class OpenCLIPModelConfig:
|
|||||||
if open_clip_cfg is None:
|
if open_clip_cfg is None:
|
||||||
raise ValueError(f"Unknown model {self.name}")
|
raise ValueError(f"Unknown model {self.name}")
|
||||||
self.image_size = open_clip_cfg["vision_cfg"]["image_size"]
|
self.image_size = open_clip_cfg["vision_cfg"]["image_size"]
|
||||||
self.sequence_length = open_clip_cfg["text_cfg"]["context_length"]
|
self.sequence_length = open_clip_cfg["text_cfg"].get("context_length", 77)
|
||||||
|
|
||||||
|
|
||||||
def to_onnx(
|
def to_onnx(
|
||||||
model_cfg: OpenCLIPModelConfig,
|
model_cfg: OpenCLIPModelConfig,
|
||||||
output_dir_visual: Path | str | None = None,
|
output_dir_visual: Path | str | None = None,
|
||||||
output_dir_textual: Path | str | None = None,
|
output_dir_textual: Path | str | None = None,
|
||||||
) -> None:
|
) -> tuple[Path | None, Path | None]:
|
||||||
|
visual_path = None
|
||||||
|
textual_path = None
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
model = open_clip.create_model(
|
model = open_clip.create_model(
|
||||||
model_cfg.name,
|
model_cfg.name,
|
||||||
pretrained=model_cfg.pretrained,
|
pretrained=model_cfg.pretrained,
|
||||||
jit=False,
|
jit=False,
|
||||||
cache_dir=tmpdir,
|
cache_dir=os.environ.get("CACHE_DIR", tmpdir),
|
||||||
require_pretrained=True,
|
require_pretrained=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
text_vision_cfg = open_clip.get_model_config(model_cfg.name)
|
text_vision_cfg = open_clip.get_model_config(model_cfg.name)
|
||||||
|
|
||||||
|
model.eval()
|
||||||
for param in model.parameters():
|
for param in model.parameters():
|
||||||
param.requires_grad_(False)
|
param.requires_grad_(False)
|
||||||
|
|
||||||
@@ -53,8 +56,6 @@ def to_onnx(
|
|||||||
save_config(text_vision_cfg, output_dir_visual.parent / "config.json")
|
save_config(text_vision_cfg, output_dir_visual.parent / "config.json")
|
||||||
export_image_encoder(model, model_cfg, visual_path)
|
export_image_encoder(model, model_cfg, visual_path)
|
||||||
|
|
||||||
optimize(visual_path)
|
|
||||||
|
|
||||||
if output_dir_textual is not None:
|
if output_dir_textual is not None:
|
||||||
output_dir_textual = Path(output_dir_textual)
|
output_dir_textual = Path(output_dir_textual)
|
||||||
textual_path = get_model_path(output_dir_textual)
|
textual_path = get_model_path(output_dir_textual)
|
||||||
@@ -62,7 +63,7 @@ def to_onnx(
|
|||||||
tokenizer_name = text_vision_cfg["text_cfg"].get("hf_tokenizer_name", "openai/clip-vit-base-patch32")
|
tokenizer_name = text_vision_cfg["text_cfg"].get("hf_tokenizer_name", "openai/clip-vit-base-patch32")
|
||||||
AutoTokenizer.from_pretrained(tokenizer_name).save_pretrained(output_dir_textual)
|
AutoTokenizer.from_pretrained(tokenizer_name).save_pretrained(output_dir_textual)
|
||||||
export_text_encoder(model, model_cfg, textual_path)
|
export_text_encoder(model, model_cfg, textual_path)
|
||||||
optimize(textual_path)
|
return visual_path, textual_path
|
||||||
|
|
||||||
|
|
||||||
def export_image_encoder(model: open_clip.CLIP, model_cfg: OpenCLIPModelConfig, output_path: Path | str) -> None:
|
def export_image_encoder(model: open_clip.CLIP, model_cfg: OpenCLIPModelConfig, output_path: Path | str) -> None:
|
||||||
@@ -83,9 +84,9 @@ def export_image_encoder(model: open_clip.CLIP, model_cfg: OpenCLIPModelConfig,
|
|||||||
args,
|
args,
|
||||||
output_path.as_posix(),
|
output_path.as_posix(),
|
||||||
input_names=["image"],
|
input_names=["image"],
|
||||||
output_names=["image_embedding"],
|
output_names=["embedding"],
|
||||||
opset_version=17,
|
opset_version=17,
|
||||||
dynamic_axes={"image": {0: "batch_size"}},
|
# dynamic_axes={"image": {0: "batch_size"}},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -107,7 +108,7 @@ def export_text_encoder(model: open_clip.CLIP, model_cfg: OpenCLIPModelConfig, o
|
|||||||
args,
|
args,
|
||||||
output_path.as_posix(),
|
output_path.as_posix(),
|
||||||
input_names=["text"],
|
input_names=["text"],
|
||||||
output_names=["text_embedding"],
|
output_names=["embedding"],
|
||||||
opset_version=17,
|
opset_version=17,
|
||||||
dynamic_axes={"text": {0: "batch_size"}},
|
# dynamic_axes={"text": {0: "batch_size"}},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,13 +5,26 @@ import onnxruntime as ort
|
|||||||
import onnxsim
|
import onnxsim
|
||||||
|
|
||||||
|
|
||||||
|
def save_onnx(model: onnx.ModelProto, output_path: Path | str) -> None:
|
||||||
|
try:
|
||||||
|
onnx.save(model, output_path)
|
||||||
|
except ValueError as e:
|
||||||
|
if "The proto size is larger than the 2 GB limit." in str(e):
|
||||||
|
onnx.save(model, output_path, save_as_external_data=True, size_threshold=1_000_000)
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def optimize_onnxsim(model_path: Path | str, output_path: Path | str) -> None:
|
def optimize_onnxsim(model_path: Path | str, output_path: Path | str) -> None:
|
||||||
model_path = Path(model_path)
|
model_path = Path(model_path)
|
||||||
output_path = Path(output_path)
|
output_path = Path(output_path)
|
||||||
model = onnx.load(model_path.as_posix())
|
model = onnx.load(model_path.as_posix())
|
||||||
model, check = onnxsim.simplify(model, skip_shape_inference=True)
|
model, check = onnxsim.simplify(model)
|
||||||
assert check, "Simplified ONNX model could not be validated"
|
assert check, "Simplified ONNX model could not be validated"
|
||||||
onnx.save(model, output_path.as_posix())
|
for file in model_path.parent.iterdir():
|
||||||
|
if file.name.startswith("Constant") or "onnx" in file.name or file.suffix == ".weight":
|
||||||
|
file.unlink()
|
||||||
|
save_onnx(model, output_path)
|
||||||
|
|
||||||
|
|
||||||
def optimize_ort(
|
def optimize_ort(
|
||||||
@@ -33,6 +46,4 @@ def optimize(model_path: Path | str) -> None:
|
|||||||
model_path = Path(model_path)
|
model_path = Path(model_path)
|
||||||
|
|
||||||
optimize_ort(model_path, model_path)
|
optimize_ort(model_path, model_path)
|
||||||
# onnxsim serializes large models as a blob, which uses much more memory when loading the model at runtime
|
optimize_onnxsim(model_path, model_path)
|
||||||
if not any(file.name.startswith("Constant") for file in model_path.parent.iterdir()):
|
|
||||||
optimize_onnxsim(model_path, model_path)
|
|
||||||
|
|||||||
@@ -3,74 +3,111 @@ import os
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
from huggingface_hub import create_repo, login, upload_folder
|
import torch
|
||||||
|
from huggingface_hub import create_repo, upload_folder
|
||||||
from models import mclip, openclip
|
from models import mclip, openclip
|
||||||
|
from models.optimize import optimize
|
||||||
from rich.progress import Progress
|
from rich.progress import Progress
|
||||||
|
|
||||||
models = [
|
models = [
|
||||||
"RN50::openai",
|
"M-CLIP/LABSE-Vit-L-14",
|
||||||
"RN50::yfcc15m",
|
"M-CLIP/XLM-Roberta-Large-Vit-B-16Plus",
|
||||||
"RN50::cc12m",
|
"M-CLIP/XLM-Roberta-Large-Vit-B-32",
|
||||||
|
"M-CLIP/XLM-Roberta-Large-Vit-L-14",
|
||||||
"RN101::openai",
|
"RN101::openai",
|
||||||
"RN101::yfcc15m",
|
"RN101::yfcc15m",
|
||||||
"RN50x4::openai",
|
"RN50::cc12m",
|
||||||
|
"RN50::openai",
|
||||||
|
"RN50::yfcc15m",
|
||||||
"RN50x16::openai",
|
"RN50x16::openai",
|
||||||
|
"RN50x4::openai",
|
||||||
"RN50x64::openai",
|
"RN50x64::openai",
|
||||||
"ViT-B-32::openai",
|
"ViT-B-16-SigLIP-256::webli",
|
||||||
|
"ViT-B-16-SigLIP-384::webli",
|
||||||
|
"ViT-B-16-SigLIP-512::webli",
|
||||||
|
"ViT-B-16-SigLIP-i18n-256::webli",
|
||||||
|
"ViT-B-16-SigLIP::webli",
|
||||||
|
"ViT-B-16-plus-240::laion400m_e31",
|
||||||
|
"ViT-B-16-plus-240::laion400m_e32",
|
||||||
|
"ViT-B-16::laion400m_e31",
|
||||||
|
"ViT-B-16::laion400m_e32",
|
||||||
|
"ViT-B-16::openai",
|
||||||
|
"ViT-B-32::laion2b-s34b-b79k",
|
||||||
"ViT-B-32::laion2b_e16",
|
"ViT-B-32::laion2b_e16",
|
||||||
"ViT-B-32::laion400m_e31",
|
"ViT-B-32::laion400m_e31",
|
||||||
"ViT-B-32::laion400m_e32",
|
"ViT-B-32::laion400m_e32",
|
||||||
"ViT-B-32::laion2b-s34b-b79k",
|
"ViT-B-32::openai",
|
||||||
"ViT-B-16::openai",
|
"ViT-H-14-378-quickgelu::dfn5b",
|
||||||
"ViT-B-16::laion400m_e31",
|
"ViT-H-14-quickgelu::dfn5b",
|
||||||
"ViT-B-16::laion400m_e32",
|
"ViT-H-14::laion2b-s32b-b79k",
|
||||||
"ViT-B-16-plus-240::laion400m_e31",
|
"ViT-L-14-336::openai",
|
||||||
"ViT-B-16-plus-240::laion400m_e32",
|
"ViT-L-14-quickgelu::dfn2b",
|
||||||
"ViT-L-14::openai",
|
"ViT-L-14::laion2b-s32b-b82k",
|
||||||
"ViT-L-14::laion400m_e31",
|
"ViT-L-14::laion400m_e31",
|
||||||
"ViT-L-14::laion400m_e32",
|
"ViT-L-14::laion400m_e32",
|
||||||
"ViT-L-14::laion2b-s32b-b82k",
|
"ViT-L-14::openai",
|
||||||
"ViT-L-14-336::openai",
|
"ViT-L-16-SigLIP-256::webli",
|
||||||
"ViT-H-14::laion2b-s32b-b79k",
|
"ViT-L-16-SigLIP-384::webli",
|
||||||
|
"ViT-SO400M-14-SigLIP-384::webli",
|
||||||
"ViT-g-14::laion2b-s12b-b42k",
|
"ViT-g-14::laion2b-s12b-b42k",
|
||||||
"M-CLIP/LABSE-Vit-L-14",
|
"nllb-clip-base-siglip::mrl",
|
||||||
"M-CLIP/XLM-Roberta-Large-Vit-B-32",
|
"nllb-clip-base-siglip::v1",
|
||||||
"M-CLIP/XLM-Roberta-Large-Vit-B-16Plus",
|
"nllb-clip-large-siglip::mrl",
|
||||||
"M-CLIP/XLM-Roberta-Large-Vit-L-14",
|
"nllb-clip-large-siglip::v1",
|
||||||
|
"xlm-roberta-base-ViT-B-32::laion5b_s13b_b90k",
|
||||||
|
"xlm-roberta-large-ViT-H-14::frozen_laion5b_s13b_b90k",
|
||||||
]
|
]
|
||||||
|
|
||||||
login(token=os.environ["HF_AUTH_TOKEN"])
|
# glob to delete old UUID blobs when reuploading models
|
||||||
|
uuid_char = "[a-fA-F0-9]"
|
||||||
|
uuid_glob = uuid_char * 8 + "-" + uuid_char * 4 + "-" + uuid_char * 4 + "-" + uuid_char * 4 + "-" + uuid_char * 12
|
||||||
|
|
||||||
|
# remote repo files to be deleted before uploading
|
||||||
|
# deletion is in the same commit as the upload, so it's atomic
|
||||||
|
delete_patterns = ["**/*onnx*", "**/Constant*", "**/*.weight", "**/*.bias", f"**/{uuid_glob}"]
|
||||||
|
|
||||||
with Progress() as progress:
|
with Progress() as progress:
|
||||||
task1 = progress.add_task("[green]Exporting models...", total=len(models))
|
task = progress.add_task("[green]Exporting models...", total=len(models))
|
||||||
task2 = progress.add_task("[yellow]Uploading models...", total=len(models))
|
token = os.environ.get("HF_AUTH_TOKEN")
|
||||||
|
torch.backends.mha.set_fastpath_enabled(False)
|
||||||
with TemporaryDirectory() as tmp:
|
with TemporaryDirectory() as tmp:
|
||||||
tmpdir = Path(tmp)
|
tmpdir = Path(tmp)
|
||||||
for model in models:
|
for model in models:
|
||||||
model_name = model.split("/")[-1].replace("::", "__")
|
model_name = model.split("/")[-1].replace("::", "__")
|
||||||
|
hf_model_name = model_name.replace("xlm-roberta-large", "XLM-Roberta-Large")
|
||||||
|
hf_model_name = model_name.replace("xlm-roberta-base", "XLM-Roberta-Base")
|
||||||
config_path = tmpdir / model_name / "config.json"
|
config_path = tmpdir / model_name / "config.json"
|
||||||
|
|
||||||
def upload() -> None:
|
|
||||||
progress.update(task2, description=f"[yellow]Uploading {model_name}")
|
|
||||||
repo_id = f"immich-app/{model_name}"
|
|
||||||
|
|
||||||
create_repo(repo_id, exist_ok=True)
|
|
||||||
upload_folder(repo_id=repo_id, folder_path=tmpdir / model_name)
|
|
||||||
progress.update(task2, advance=1)
|
|
||||||
|
|
||||||
def export() -> None:
|
def export() -> None:
|
||||||
progress.update(task1, description=f"[green]Exporting {model_name}")
|
progress.update(task, description=f"[green]Exporting {hf_model_name}")
|
||||||
visual_dir = tmpdir / model_name / "visual"
|
visual_dir = tmpdir / hf_model_name / "visual"
|
||||||
textual_dir = tmpdir / model_name / "textual"
|
textual_dir = tmpdir / hf_model_name / "textual"
|
||||||
if model.startswith("M-CLIP"):
|
if model.startswith("M-CLIP"):
|
||||||
mclip.to_onnx(model, visual_dir, textual_dir)
|
visual_path, textual_path = mclip.to_onnx(model, visual_dir, textual_dir)
|
||||||
else:
|
else:
|
||||||
name, _, pretrained = model_name.partition("__")
|
name, _, pretrained = model_name.partition("__")
|
||||||
openclip.to_onnx(openclip.OpenCLIPModelConfig(name, pretrained), visual_dir, textual_dir)
|
config = openclip.OpenCLIPModelConfig(name, pretrained)
|
||||||
|
visual_path, textual_path = openclip.to_onnx(config, visual_dir, textual_dir)
|
||||||
|
progress.update(task, description=f"[green]Optimizing {hf_model_name} (visual)")
|
||||||
|
optimize(visual_path)
|
||||||
|
progress.update(task, description=f"[green]Optimizing {hf_model_name} (textual)")
|
||||||
|
optimize(textual_path)
|
||||||
|
|
||||||
progress.update(task1, advance=1)
|
|
||||||
gc.collect()
|
gc.collect()
|
||||||
|
|
||||||
|
def upload() -> None:
|
||||||
|
progress.update(task, description=f"[yellow]Uploading {hf_model_name}")
|
||||||
|
repo_id = f"immich-app/{hf_model_name}"
|
||||||
|
|
||||||
|
create_repo(repo_id, exist_ok=True)
|
||||||
|
upload_folder(
|
||||||
|
repo_id=repo_id,
|
||||||
|
folder_path=tmpdir / hf_model_name,
|
||||||
|
delete_patterns=delete_patterns,
|
||||||
|
token=token,
|
||||||
|
)
|
||||||
|
|
||||||
export()
|
export()
|
||||||
upload()
|
if token is not None:
|
||||||
|
upload()
|
||||||
|
progress.update(task, advance=1)
|
||||||
|
|||||||
143
machine-learning/poetry.lock
generated
143
machine-learning/poetry.lock
generated
@@ -1236,13 +1236,13 @@ socks = ["socksio (==1.*)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "huggingface-hub"
|
name = "huggingface-hub"
|
||||||
version = "0.24.0"
|
version = "0.24.5"
|
||||||
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
|
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.0"
|
python-versions = ">=3.8.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "huggingface_hub-0.24.0-py3-none-any.whl", hash = "sha256:7ad92edefb93d8145c061f6df8d99df2ff85f8379ba5fac8a95aca0642afa5d7"},
|
{file = "huggingface_hub-0.24.5-py3-none-any.whl", hash = "sha256:d93fb63b1f1a919a22ce91a14518974e81fc4610bf344dfe7572343ce8d3aced"},
|
||||||
{file = "huggingface_hub-0.24.0.tar.gz", hash = "sha256:6c7092736b577d89d57b3cdfea026f1b0dc2234ae783fa0d59caf1bf7d52dfa7"},
|
{file = "huggingface_hub-0.24.5.tar.gz", hash = "sha256:7b45d6744dd53ce9cbf9880957de00e9d10a9ae837f1c9b7255fc8fa4e8264f3"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1530,13 +1530,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "locust"
|
name = "locust"
|
||||||
version = "2.29.1"
|
version = "2.31.1"
|
||||||
description = "Developer-friendly load testing framework"
|
description = "Developer-friendly load testing framework"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "locust-2.29.1-py3-none-any.whl", hash = "sha256:8b15daab44cdf50eef1860a32bb30969423e3795247115e5a37446da3240c6d6"},
|
{file = "locust-2.31.1-py3-none-any.whl", hash = "sha256:20756509939004e95c622ac3042886edab38b736f00534cc03ce2774064e7f71"},
|
||||||
{file = "locust-2.29.1.tar.gz", hash = "sha256:2e0628a59e2689a50cb4735a9a43709e30f2da7ed276c15d877c5325507f44b1"},
|
{file = "locust-2.31.1.tar.gz", hash = "sha256:d26b7333cdef80645f3978d8ff9aabab4d53e41ed82cc8490212aa68e8498fdd"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1548,14 +1548,14 @@ gevent = ">=22.10.2"
|
|||||||
geventhttpclient = ">=2.3.1"
|
geventhttpclient = ">=2.3.1"
|
||||||
msgpack = ">=1.0.0"
|
msgpack = ">=1.0.0"
|
||||||
psutil = ">=5.9.1"
|
psutil = ">=5.9.1"
|
||||||
pywin32 = {version = "*", markers = "platform_system == \"Windows\""}
|
pywin32 = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
pyzmq = ">=25.0.0"
|
pyzmq = ">=25.0.0"
|
||||||
requests = [
|
requests = [
|
||||||
{version = ">=2.32.2", markers = "python_version > \"3.11\""},
|
{version = ">=2.26.0", markers = "python_full_version <= \"3.11.0\""},
|
||||||
{version = ">=2.26.0", markers = "python_version <= \"3.11\""},
|
{version = ">=2.32.2", markers = "python_full_version > \"3.11.0\""},
|
||||||
]
|
]
|
||||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||||
typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.11\""}
|
typing_extensions = {version = ">=4.6.0", markers = "python_version < \"3.11\""}
|
||||||
Werkzeug = ">=2.0.0"
|
Werkzeug = ">=2.0.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1794,38 +1794,38 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy"
|
name = "mypy"
|
||||||
version = "1.11.0"
|
version = "1.11.1"
|
||||||
description = "Optional static typing for Python"
|
description = "Optional static typing for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "mypy-1.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3824187c99b893f90c845bab405a585d1ced4ff55421fdf5c84cb7710995229"},
|
{file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"},
|
||||||
{file = "mypy-1.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:96f8dbc2c85046c81bcddc246232d500ad729cb720da4e20fce3b542cab91287"},
|
{file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"},
|
||||||
{file = "mypy-1.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a5d8d8dd8613a3e2be3eae829ee891b6b2de6302f24766ff06cb2875f5be9c6"},
|
{file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"},
|
||||||
{file = "mypy-1.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72596a79bbfb195fd41405cffa18210af3811beb91ff946dbcb7368240eed6be"},
|
{file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"},
|
||||||
{file = "mypy-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:35ce88b8ed3a759634cb4eb646d002c4cef0a38f20565ee82b5023558eb90c00"},
|
{file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"},
|
||||||
{file = "mypy-1.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:98790025861cb2c3db8c2f5ad10fc8c336ed2a55f4daf1b8b3f877826b6ff2eb"},
|
{file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"},
|
||||||
{file = "mypy-1.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25bcfa75b9b5a5f8d67147a54ea97ed63a653995a82798221cca2a315c0238c1"},
|
{file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"},
|
||||||
{file = "mypy-1.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bea2a0e71c2a375c9fa0ede3d98324214d67b3cbbfcbd55ac8f750f85a414e3"},
|
{file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"},
|
||||||
{file = "mypy-1.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2b3d36baac48e40e3064d2901f2fbd2a2d6880ec6ce6358825c85031d7c0d4d"},
|
{file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"},
|
||||||
{file = "mypy-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8e2e43977f0e09f149ea69fd0556623919f816764e26d74da0c8a7b48f3e18a"},
|
{file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"},
|
||||||
{file = "mypy-1.11.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d44c1e44a8be986b54b09f15f2c1a66368eb43861b4e82573026e04c48a9e20"},
|
{file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"},
|
||||||
{file = "mypy-1.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cea3d0fb69637944dd321f41bc896e11d0fb0b0aa531d887a6da70f6e7473aba"},
|
{file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"},
|
||||||
{file = "mypy-1.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a83ec98ae12d51c252be61521aa5731f5512231d0b738b4cb2498344f0b840cd"},
|
{file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"},
|
||||||
{file = "mypy-1.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c7b73a856522417beb78e0fb6d33ef89474e7a622db2653bc1285af36e2e3e3d"},
|
{file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"},
|
||||||
{file = "mypy-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:f2268d9fcd9686b61ab64f077be7ffbc6fbcdfb4103e5dd0cc5eaab53a8886c2"},
|
{file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"},
|
||||||
{file = "mypy-1.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:940bfff7283c267ae6522ef926a7887305945f716a7704d3344d6d07f02df850"},
|
{file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"},
|
||||||
{file = "mypy-1.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:14f9294528b5f5cf96c721f231c9f5b2733164e02c1c018ed1a0eff8a18005ac"},
|
{file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"},
|
||||||
{file = "mypy-1.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7b54c27783991399046837df5c7c9d325d921394757d09dbcbf96aee4649fe9"},
|
{file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"},
|
||||||
{file = "mypy-1.11.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:65f190a6349dec29c8d1a1cd4aa71284177aee5949e0502e6379b42873eddbe7"},
|
{file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"},
|
||||||
{file = "mypy-1.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbe286303241fea8c2ea5466f6e0e6a046a135a7e7609167b07fd4e7baf151bf"},
|
{file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"},
|
||||||
{file = "mypy-1.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:104e9c1620c2675420abd1f6c44bab7dd33cc85aea751c985006e83dcd001095"},
|
{file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"},
|
||||||
{file = "mypy-1.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f006e955718ecd8d159cee9932b64fba8f86ee6f7728ca3ac66c3a54b0062abe"},
|
{file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"},
|
||||||
{file = "mypy-1.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:becc9111ca572b04e7e77131bc708480cc88a911adf3d0239f974c034b78085c"},
|
{file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"},
|
||||||
{file = "mypy-1.11.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6801319fe76c3f3a3833f2b5af7bd2c17bb93c00026a2a1b924e6762f5b19e13"},
|
{file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"},
|
||||||
{file = "mypy-1.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:c1a184c64521dc549324ec6ef7cbaa6b351912be9cb5edb803c2808a0d7e85ac"},
|
{file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"},
|
||||||
{file = "mypy-1.11.0-py3-none-any.whl", hash = "sha256:56913ec8c7638b0091ef4da6fcc9136896914a9d60d54670a75880c3e5b99ace"},
|
{file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"},
|
||||||
{file = "mypy-1.11.0.tar.gz", hash = "sha256:93743608c7348772fdc717af4aeee1997293a1ad04bc0ea6efa15bf65385c538"},
|
{file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -2074,10 +2074,10 @@ files = [
|
|||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
numpy = [
|
numpy = [
|
||||||
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
|
|
||||||
{version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""},
|
{version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""},
|
||||||
{version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""},
|
{version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""},
|
||||||
{version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""},
|
{version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""},
|
||||||
|
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2466,13 +2466,13 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.2.2"
|
version = "8.3.2"
|
||||||
description = "pytest: simple powerful testing with Python"
|
description = "pytest: simple powerful testing with Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"},
|
{file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"},
|
||||||
{file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"},
|
{file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -2480,7 +2480,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
|||||||
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||||
iniconfig = "*"
|
iniconfig = "*"
|
||||||
packaging = "*"
|
packaging = "*"
|
||||||
pluggy = ">=1.5,<2.0"
|
pluggy = ">=1.5,<2"
|
||||||
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
|
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@@ -2827,29 +2827,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.5.4"
|
version = "0.5.6"
|
||||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "ruff-0.5.4-py3-none-linux_armv6l.whl", hash = "sha256:82acef724fc639699b4d3177ed5cc14c2a5aacd92edd578a9e846d5b5ec18ddf"},
|
{file = "ruff-0.5.6-py3-none-linux_armv6l.whl", hash = "sha256:a0ef5930799a05522985b9cec8290b185952f3fcd86c1772c3bdbd732667fdcd"},
|
||||||
{file = "ruff-0.5.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:da62e87637c8838b325e65beee485f71eb36202ce8e3cdbc24b9fcb8b99a37be"},
|
{file = "ruff-0.5.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b652dc14f6ef5d1552821e006f747802cc32d98d5509349e168f6bf0ee9f8f42"},
|
||||||
{file = "ruff-0.5.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e98ad088edfe2f3b85a925ee96da652028f093d6b9b56b76fc242d8abb8e2059"},
|
{file = "ruff-0.5.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:80521b88d26a45e871f31e4b88938fd87db7011bb961d8afd2664982dfc3641a"},
|
||||||
{file = "ruff-0.5.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c55efbecc3152d614cfe6c2247a3054cfe358cefbf794f8c79c8575456efe19"},
|
{file = "ruff-0.5.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9bc8f328a9f1309ae80e4d392836e7dbc77303b38ed4a7112699e63d3b066ab"},
|
||||||
{file = "ruff-0.5.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9b85eaa1f653abd0a70603b8b7008d9e00c9fa1bbd0bf40dad3f0c0bdd06793"},
|
{file = "ruff-0.5.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d394940f61f7720ad371ddedf14722ee1d6250fd8d020f5ea5a86e7be217daf"},
|
||||||
{file = "ruff-0.5.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cf497a47751be8c883059c4613ba2f50dd06ec672692de2811f039432875278"},
|
{file = "ruff-0.5.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111a99cdb02f69ddb2571e2756e017a1496c2c3a2aeefe7b988ddab38b416d36"},
|
||||||
{file = "ruff-0.5.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:09c14ed6a72af9ccc8d2e313d7acf7037f0faff43cde4b507e66f14e812e37f7"},
|
{file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e395daba77a79f6dc0d07311f94cc0560375ca20c06f354c7c99af3bf4560c5d"},
|
||||||
{file = "ruff-0.5.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:628f6b8f97b8bad2490240aa84f3e68f390e13fabc9af5c0d3b96b485921cd60"},
|
{file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c476acb43c3c51e3c614a2e878ee1589655fa02dab19fe2db0423a06d6a5b1b6"},
|
||||||
{file = "ruff-0.5.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3520a00c0563d7a7a7c324ad7e2cde2355733dafa9592c671fb2e9e3cd8194c1"},
|
{file = "ruff-0.5.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2ff8003f5252fd68425fd53d27c1f08b201d7ed714bb31a55c9ac1d4c13e2eb"},
|
||||||
{file = "ruff-0.5.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93789f14ca2244fb91ed481456f6d0bb8af1f75a330e133b67d08f06ad85b516"},
|
{file = "ruff-0.5.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c94e084ba3eaa80c2172918c2ca2eb2230c3f15925f4ed8b6297260c6ef179ad"},
|
||||||
{file = "ruff-0.5.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:029454e2824eafa25b9df46882f7f7844d36fd8ce51c1b7f6d97e2615a57bbcc"},
|
{file = "ruff-0.5.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f77c1c3aa0669fb230b06fb24ffa3e879391a3ba3f15e3d633a752da5a3e670"},
|
||||||
{file = "ruff-0.5.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9492320eed573a13a0bc09a2957f17aa733fff9ce5bf00e66e6d4a88ec33813f"},
|
{file = "ruff-0.5.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f908148c93c02873210a52cad75a6eda856b2cbb72250370ce3afef6fb99b1ed"},
|
||||||
{file = "ruff-0.5.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a6e1f62a92c645e2919b65c02e79d1f61e78a58eddaebca6c23659e7c7cb4ac7"},
|
{file = "ruff-0.5.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:563a7ae61ad284187d3071d9041c08019975693ff655438d8d4be26e492760bd"},
|
||||||
{file = "ruff-0.5.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:768fa9208df2bec4b2ce61dbc7c2ddd6b1be9fb48f1f8d3b78b3332c7d71c1ff"},
|
{file = "ruff-0.5.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:94fe60869bfbf0521e04fd62b74cbca21cbc5beb67cbb75ab33fe8c174f54414"},
|
||||||
{file = "ruff-0.5.4-py3-none-win32.whl", hash = "sha256:e1e7393e9c56128e870b233c82ceb42164966f25b30f68acbb24ed69ce9c3a4e"},
|
{file = "ruff-0.5.6-py3-none-win32.whl", hash = "sha256:e6a584c1de6f8591c2570e171cc7ce482bb983d49c70ddf014393cd39e9dfaed"},
|
||||||
{file = "ruff-0.5.4-py3-none-win_amd64.whl", hash = "sha256:58b54459221fd3f661a7329f177f091eb35cf7a603f01d9eb3eb11cc348d38c4"},
|
{file = "ruff-0.5.6-py3-none-win_amd64.whl", hash = "sha256:d7fe7dccb1a89dc66785d7aa0ac283b2269712d8ed19c63af908fdccca5ccc1a"},
|
||||||
{file = "ruff-0.5.4-py3-none-win_arm64.whl", hash = "sha256:bd53da65f1085fb5b307c38fd3c0829e76acf7b2a912d8d79cadcdb4875c1eb7"},
|
{file = "ruff-0.5.6-py3-none-win_arm64.whl", hash = "sha256:57c6c0dd997b31b536bff49b9eee5ed3194d60605a4427f735eeb1f9c1b8d264"},
|
||||||
{file = "ruff-0.5.4.tar.gz", hash = "sha256:2795726d5f71c4f4e70653273d1c23a8182f07dd8e48c12de5d867bfb7557eed"},
|
{file = "ruff-0.5.6.tar.gz", hash = "sha256:07c9e3c2a8e1fe377dd460371c3462671a728c981c3205a5217291422209f642"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2991,19 +2991,18 @@ test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeo
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "setuptools"
|
name = "setuptools"
|
||||||
version = "68.2.2"
|
version = "70.3.0"
|
||||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"},
|
{file = "setuptools-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc"},
|
||||||
{file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"},
|
{file = "setuptools-70.3.0.tar.gz", hash = "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||||
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "six"
|
name = "six"
|
||||||
@@ -3263,13 +3262,13 @@ zstd = ["zstandard (>=0.18.0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uvicorn"
|
name = "uvicorn"
|
||||||
version = "0.30.1"
|
version = "0.30.5"
|
||||||
description = "The lightning-fast ASGI server."
|
description = "The lightning-fast ASGI server."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81"},
|
{file = "uvicorn-0.30.5-py3-none-any.whl", hash = "sha256:b2d86de274726e9878188fa07576c9ceeff90a839e2b6e25c917fe05f5a6c835"},
|
||||||
{file = "uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"},
|
{file = "uvicorn-0.30.5.tar.gz", hash = "sha256:ac6fdbd4425c5fd17a9fe39daf4d4d075da6fdc80f653e5894cdc2fd98752bee"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -3601,4 +3600,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.10,<4.0"
|
python-versions = ">=3.10,<4.0"
|
||||||
content-hash = "df9afeda50e05cb62b322a047028a9b0851db197c4f379903c70adab3a98777a"
|
content-hash = "b2b053886ca1dd3a3305c63caf155b1976dfc4066f72f5d1ecfc42099db34aab"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pydantic = "^1.10.8"
|
|||||||
aiocache = ">=0.12.1,<1.0"
|
aiocache = ">=0.12.1,<1.0"
|
||||||
rich = ">=13.4.2"
|
rich = ">=13.4.2"
|
||||||
ftfy = ">=6.1.1"
|
ftfy = ">=6.1.1"
|
||||||
setuptools = "^68.0.0"
|
setuptools = "^70.0.0"
|
||||||
python-multipart = ">=0.0.6,<1.0"
|
python-multipart = ">=0.0.6,<1.0"
|
||||||
orjson = ">=3.9.5"
|
orjson = ">=3.9.5"
|
||||||
gunicorn = ">=21.1.0"
|
gunicorn = ">=21.1.0"
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.alextran.immich"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.alextran.immich"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
<application android:label="Immich" android:name=".ImmichApp" android:usesCleartextTraffic="true"
|
<application android:label="Immich" android:name=".ImmichApp" android:usesCleartextTraffic="true"
|
||||||
android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true" android:largeHeap="true">
|
android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true"
|
||||||
|
android:largeHeap="true">
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.EnableImpeller"
|
android:name="io.flutter.embedding.android.EnableImpeller"
|
||||||
@@ -55,7 +56,8 @@
|
|||||||
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="32" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.MANAGE_MEDIA" />
|
<uses-permission android:name="android.permission.MANAGE_MEDIA" />
|
||||||
@@ -65,6 +67,7 @@
|
|||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
@@ -76,4 +79,4 @@
|
|||||||
<data android:scheme="geo" />
|
<data android:scheme="geo" />
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -8,9 +8,11 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootProject.buildDir = '../build'
|
rootProject.buildDir = '../build'
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
project.evaluationDependsOn(':app')
|
project.evaluationDependsOn(':app')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ pluginManagement {
|
|||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version "7.4.2" apply false
|
id "com.android.application" version "7.4.2" apply false
|
||||||
id "org.jetbrains.kotlin.android" version "1.9.24" apply false
|
id "org.jetbrains.kotlin.android" version "1.9.0" apply false
|
||||||
id "org.jetbrains.kotlin.kapt" version "1.9.24" apply false
|
id "org.jetbrains.kotlin.kapt" version "1.9.0" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include ":app"
|
include ":app"
|
||||||
|
|||||||
@@ -531,6 +531,11 @@
|
|||||||
"theme_setting_dark_mode_switch": "Dark mode",
|
"theme_setting_dark_mode_switch": "Dark mode",
|
||||||
"theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
|
"theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
|
||||||
"theme_setting_image_viewer_quality_title": "Image viewer quality",
|
"theme_setting_image_viewer_quality_title": "Image viewer quality",
|
||||||
|
"theme_setting_primary_color_title": "Primary color",
|
||||||
|
"theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.",
|
||||||
|
"theme_setting_colorful_interface_title": "Colorful interface",
|
||||||
|
"theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.",
|
||||||
|
"theme_setting_system_primary_color_title": "Use system color",
|
||||||
"theme_setting_system_theme_switch": "Automatic (Follow system setting)",
|
"theme_setting_system_theme_switch": "Automatic (Follow system setting)",
|
||||||
"theme_setting_theme_subtitle": "Choose the app's theme setting",
|
"theme_setting_theme_subtitle": "Choose the app's theme setting",
|
||||||
"theme_setting_theme_title": "Theme",
|
"theme_setting_theme_title": "Theme",
|
||||||
@@ -562,4 +567,4 @@
|
|||||||
"viewer_remove_from_stack": "Remove from Stack",
|
"viewer_remove_from_stack": "Remove from Stack",
|
||||||
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
||||||
"viewer_unstack": "Un-Stack"
|
"viewer_unstack": "Un-Stack"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,6 @@ PODS:
|
|||||||
- fluttertoast (0.0.2):
|
- fluttertoast (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Toast
|
- Toast
|
||||||
- FMDB (2.7.5):
|
|
||||||
- FMDB/standard (= 2.7.5)
|
|
||||||
- FMDB/standard (2.7.5)
|
|
||||||
- geolocator_apple (1.2.0):
|
- geolocator_apple (1.2.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- image_picker_ios (0.0.1):
|
- image_picker_ios (0.0.1):
|
||||||
@@ -73,7 +70,7 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- path_provider_ios (0.0.1):
|
- path_provider_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- permission_handler_apple (9.1.1):
|
- permission_handler_apple (9.3.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- photo_manager (2.0.0):
|
- photo_manager (2.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
@@ -90,7 +87,7 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqflite (0.0.3):
|
- sqflite (0.0.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FMDB (>= 2.7.5)
|
- FlutterMacOS
|
||||||
- SwiftyGif (5.4.5)
|
- SwiftyGif (5.4.5)
|
||||||
- Toast (4.0.0)
|
- Toast (4.0.0)
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
@@ -123,7 +120,7 @@ DEPENDENCIES:
|
|||||||
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||||
@@ -132,7 +129,6 @@ SPEC REPOS:
|
|||||||
trunk:
|
trunk:
|
||||||
- DKImagePickerController
|
- DKImagePickerController
|
||||||
- DKPhotoGallery
|
- DKPhotoGallery
|
||||||
- FMDB
|
|
||||||
- MapLibre
|
- MapLibre
|
||||||
- ReachabilitySwift
|
- ReachabilitySwift
|
||||||
- SAMKeychain
|
- SAMKeychain
|
||||||
@@ -184,7 +180,7 @@ EXTERNAL SOURCES:
|
|||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
sqflite:
|
sqflite:
|
||||||
:path: ".symlinks/plugins/sqflite/ios"
|
:path: ".symlinks/plugins/sqflite/darwin"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
video_player_avfoundation:
|
video_player_avfoundation:
|
||||||
@@ -200,33 +196,32 @@ SPEC CHECKSUMS:
|
|||||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
||||||
flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04
|
flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04
|
||||||
flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d
|
flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d
|
||||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
geolocator_apple: 6cbaf322953988e009e5ecb481f07efece75c450
|
||||||
geolocator_apple: 9157311f654584b9bb72686c55fc02a97b73f461
|
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||||
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
|
|
||||||
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
|
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
|
||||||
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
||||||
MapLibre: 620fc933c1d6029b33738c905c1490d024e5d4ef
|
MapLibre: 620fc933c1d6029b33738c905c1490d024e5d4ef
|
||||||
maplibre_gl: a2efec727dd340e4c65e26d2b03b584f14881fd9
|
maplibre_gl: a2efec727dd340e4c65e26d2b03b584f14881fd9
|
||||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||||
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||||
photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a
|
photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||||
SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d
|
SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d
|
||||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
|
||||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||||
video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579
|
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
|
||||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
||||||
|
|
||||||
PODFILE CHECKSUM: 64c9b5291666c0ca3caabdfe9865c141ac40321d
|
PODFILE CHECKSUM: 64c9b5291666c0ca3caabdfe9865c141ac40321d
|
||||||
|
|
||||||
|
|||||||
@@ -155,6 +155,7 @@
|
|||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
D218A34AEE62BC1EF119F5B0 /* [CP] Embed Pods Frameworks */,
|
D218A34AEE62BC1EF119F5B0 /* [CP] Embed Pods Frameworks */,
|
||||||
|
C494C1A226E78FAB736DAB6C /* [CP] Copy Pods Resources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -267,6 +268,23 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
|
||||||
};
|
};
|
||||||
|
C494C1A226E78FAB736DAB6C /* [CP] Copy Pods Resources */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Copy Pods Resources";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
D218A34AEE62BC1EF119F5B0 /* [CP] Embed Pods Frameworks */ = {
|
D218A34AEE62BC1EF119F5B0 /* [CP] Embed Pods Frameworks */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -383,7 +401,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 165;
|
CURRENT_PROJECT_VERSION = 167;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -525,7 +543,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 165;
|
CURRENT_PROJECT_VERSION = 167;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -553,7 +571,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 165;
|
CURRENT_PROJECT_VERSION = 167;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|||||||
@@ -58,11 +58,11 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.110.0</string>
|
<string>1.111.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>165</string>
|
<string>167</string>
|
||||||
<key>FLTEnableImpeller</key>
|
<key>FLTEnableImpeller</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
|||||||
@@ -1,5 +1,108 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:immich_mobile/utils/immich_app_theme.dart';
|
||||||
|
|
||||||
const Color immichBackgroundColor = Color(0xFFf6f8fe);
|
enum ImmichColorPreset {
|
||||||
const Color immichDarkBackgroundColor = Color.fromARGB(255, 0, 0, 0);
|
indigo,
|
||||||
const Color immichDarkThemePrimaryColor = Color.fromARGB(255, 173, 203, 250);
|
deepPurple,
|
||||||
|
pink,
|
||||||
|
red,
|
||||||
|
orange,
|
||||||
|
yellow,
|
||||||
|
lime,
|
||||||
|
green,
|
||||||
|
cyan,
|
||||||
|
slateGray
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImmichColorPreset defaultColorPreset = ImmichColorPreset.indigo;
|
||||||
|
const String defaultColorPresetName = "indigo";
|
||||||
|
|
||||||
|
const Color immichBrandColorLight = Color(0xFF4150AF);
|
||||||
|
const Color immichBrandColorDark = Color(0xFFACCBFA);
|
||||||
|
|
||||||
|
final Map<ImmichColorPreset, ImmichTheme> _themePresetsMap = {
|
||||||
|
ImmichColorPreset.indigo: ImmichTheme(
|
||||||
|
light: ColorScheme.fromSeed(
|
||||||
|
seedColor: immichBrandColorLight,
|
||||||
|
).copyWith(primary: immichBrandColorLight),
|
||||||
|
dark: ColorScheme.fromSeed(
|
||||||
|
seedColor: immichBrandColorDark,
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
).copyWith(primary: immichBrandColorDark),
|
||||||
|
),
|
||||||
|
ImmichColorPreset.deepPurple: ImmichTheme(
|
||||||
|
light: ColorScheme.fromSeed(seedColor: const Color(0xFF6F43C0)),
|
||||||
|
dark: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xFFD3BBFF),
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ImmichColorPreset.pink: ImmichTheme(
|
||||||
|
light: ColorScheme.fromSeed(seedColor: const Color(0xFFED79B5)),
|
||||||
|
dark: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xFFED79B5),
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ImmichColorPreset.red: ImmichTheme(
|
||||||
|
light: ColorScheme.fromSeed(seedColor: const Color(0xFFC51C16)),
|
||||||
|
dark: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xFFD3302F),
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ImmichColorPreset.orange: ImmichTheme(
|
||||||
|
light: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xffff5b01),
|
||||||
|
dynamicSchemeVariant: DynamicSchemeVariant.fidelity,
|
||||||
|
),
|
||||||
|
dark: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xFFCC6D08),
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
dynamicSchemeVariant: DynamicSchemeVariant.fidelity,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ImmichColorPreset.yellow: ImmichTheme(
|
||||||
|
light: ColorScheme.fromSeed(seedColor: const Color(0xFFFFB400)),
|
||||||
|
dark: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xFFFFB400),
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ImmichColorPreset.lime: ImmichTheme(
|
||||||
|
light: ColorScheme.fromSeed(seedColor: const Color(0xFFCDDC39)),
|
||||||
|
dark: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xFFCDDC39),
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ImmichColorPreset.green: ImmichTheme(
|
||||||
|
light: ColorScheme.fromSeed(seedColor: const Color(0xFF18C249)),
|
||||||
|
dark: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xFF18C249),
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ImmichColorPreset.cyan: ImmichTheme(
|
||||||
|
light: ColorScheme.fromSeed(seedColor: const Color(0xFF00BCD4)),
|
||||||
|
dark: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xFF00BCD4),
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ImmichColorPreset.slateGray: ImmichTheme(
|
||||||
|
light: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xFF696969),
|
||||||
|
dynamicSchemeVariant: DynamicSchemeVariant.neutral,
|
||||||
|
),
|
||||||
|
dark: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xff696969),
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
dynamicSchemeVariant: DynamicSchemeVariant.neutral,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
extension ImmichColorModeExtension on ImmichColorPreset {
|
||||||
|
ImmichTheme getTheme() => _themePresetsMap[this]!;
|
||||||
|
}
|
||||||
|
|||||||
@@ -229,6 +229,11 @@ enum StoreKey<T> {
|
|||||||
mapwithPartners<bool>(125, type: bool),
|
mapwithPartners<bool>(125, type: bool),
|
||||||
enableHapticFeedback<bool>(126, type: bool),
|
enableHapticFeedback<bool>(126, type: bool),
|
||||||
customHeaders<String>(127, type: String),
|
customHeaders<String>(127, type: String),
|
||||||
|
|
||||||
|
// theme settings
|
||||||
|
primaryColor<String>(128, type: String),
|
||||||
|
dynamicTheme<bool>(129, type: bool),
|
||||||
|
colorfulInterface<bool>(130, type: bool),
|
||||||
;
|
;
|
||||||
|
|
||||||
const StoreKey(
|
const StoreKey(
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ extension ContextHelper on BuildContext {
|
|||||||
bool get isDarkTheme => themeData.brightness == Brightness.dark;
|
bool get isDarkTheme => themeData.brightness == Brightness.dark;
|
||||||
|
|
||||||
// Returns the current Primary color of the Theme
|
// Returns the current Primary color of the Theme
|
||||||
Color get primaryColor => themeData.primaryColor;
|
Color get primaryColor => themeData.colorScheme.primary;
|
||||||
|
|
||||||
// Returns the Scaffold background color of the Theme
|
// Returns the Scaffold background color of the Theme
|
||||||
Color get scaffoldBackgroundColor => themeData.scaffoldBackgroundColor;
|
Color get scaffoldBackgroundColor => colorScheme.surface;
|
||||||
|
|
||||||
// Returns the current TextTheme
|
// Returns the current TextTheme
|
||||||
TextTheme get textTheme => themeData.textTheme;
|
TextTheme get textTheme => themeData.textTheme;
|
||||||
|
|||||||
24
mobile/lib/extensions/theme_extensions.dart
Normal file
24
mobile/lib/extensions/theme_extensions.dart
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
extension ImmichColorSchemeExtensions on ColorScheme {
|
||||||
|
bool get _isDarkMode => brightness == Brightness.dark;
|
||||||
|
Color get onSurfaceSecondary => _isDarkMode
|
||||||
|
? onSurface.darken(amount: .3)
|
||||||
|
: onSurface.lighten(amount: .3);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ColorExtensions on Color {
|
||||||
|
Color lighten({double amount = 0.1}) {
|
||||||
|
return Color.alphaBlend(
|
||||||
|
Colors.white.withOpacity(amount),
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color darken({double amount = 0.1}) {
|
||||||
|
return Color.alphaBlend(
|
||||||
|
Colors.black.withOpacity(amount),
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,6 +65,8 @@ Future<void> initApp() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await fetchSystemPalette();
|
||||||
|
|
||||||
// Initialize Immich Logger Service
|
// Initialize Immich Logger Service
|
||||||
ImmichLogger();
|
ImmichLogger();
|
||||||
|
|
||||||
@@ -187,6 +189,7 @@ class ImmichAppState extends ConsumerState<ImmichApp>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var router = ref.watch(appRouterProvider);
|
var router = ref.watch(appRouterProvider);
|
||||||
|
var immichTheme = ref.watch(immichThemeProvider);
|
||||||
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
localizationsDelegates: context.localizationDelegates,
|
localizationsDelegates: context.localizationDelegates,
|
||||||
@@ -196,9 +199,9 @@ class ImmichAppState extends ConsumerState<ImmichApp>
|
|||||||
home: MaterialApp.router(
|
home: MaterialApp.router(
|
||||||
title: 'Immich',
|
title: 'Immich',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
themeMode: ref.watch(immichThemeProvider),
|
themeMode: ref.watch(immichThemeModeProvider),
|
||||||
darkTheme: immichDarkTheme,
|
darkTheme: getThemeData(colorScheme: immichTheme.dark),
|
||||||
theme: immichLightTheme,
|
theme: getThemeData(colorScheme: immichTheme.light),
|
||||||
routeInformationParser: router.defaultRouteParser(),
|
routeInformationParser: router.defaultRouteParser(),
|
||||||
routerDelegate: router.delegate(
|
routerDelegate: router.delegate(
|
||||||
navigatorObservers: () => [TabNavigationObserver(ref: ref)],
|
navigatorObservers: () => [TabNavigationObserver(ref: ref)],
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class Activity {
|
|||||||
assetId = dto.assetId,
|
assetId = dto.assetId,
|
||||||
comment = dto.comment,
|
comment = dto.comment,
|
||||||
createdAt = dto.createdAt,
|
createdAt = dto.createdAt,
|
||||||
type = dto.type == ActivityResponseDtoTypeEnum.comment
|
type = dto.type == ReactionType.comment
|
||||||
? ActivityType.comment
|
? ActivityType.comment
|
||||||
: ActivityType.like,
|
: ActivityType.like,
|
||||||
user = User.fromSimpleUserDto(dto.user);
|
user = User.fromSimpleUserDto(dto.user);
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
@@ -46,7 +48,7 @@ class AlbumPreviewPage extends HookConsumerWidget {
|
|||||||
"ID ${album.id}",
|
"ID ${album.id}",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: Colors.grey[600],
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/immich_colors.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/backup/album_info_card.dart';
|
import 'package:immich_mobile/widgets/backup/album_info_card.dart';
|
||||||
@@ -128,13 +127,12 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
album.name,
|
album.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: isDarkTheme ? Colors.black : immichBackgroundColor,
|
color: context.scaffoldBackgroundColor,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.red[300],
|
backgroundColor: Colors.red[300],
|
||||||
deleteIconColor:
|
deleteIconColor: context.scaffoldBackgroundColor,
|
||||||
isDarkTheme ? Colors.black : immichBackgroundColor,
|
|
||||||
deleteIcon: const Icon(
|
deleteIcon: const Icon(
|
||||||
Icons.cancel_rounded,
|
Icons.cancel_rounded,
|
||||||
size: 15,
|
size: 15,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
||||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
@@ -17,6 +18,7 @@ import 'package:immich_mobile/providers/websocket.provider.dart';
|
|||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/widgets/backup/backup_info_card.dart';
|
import 'package:immich_mobile/widgets/backup/backup_info_card.dart';
|
||||||
import 'package:immich_mobile/widgets/backup/current_backup_asset_info_box.dart';
|
import 'package:immich_mobile/widgets/backup/current_backup_asset_info_box.dart';
|
||||||
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class BackupControllerPage extends HookConsumerWidget {
|
class BackupControllerPage extends HookConsumerWidget {
|
||||||
@@ -48,7 +50,11 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
ref
|
ref
|
||||||
.watch(websocketProvider.notifier)
|
.watch(websocketProvider.notifier)
|
||||||
.stopListenToEvent('on_upload_success');
|
.stopListenToEvent('on_upload_success');
|
||||||
return null;
|
|
||||||
|
WakelockPlus.enable();
|
||||||
|
return () {
|
||||||
|
WakelockPlus.disable();
|
||||||
|
};
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@@ -130,9 +136,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
color: context.isDarkTheme
|
color: context.colorScheme.outlineVariant,
|
||||||
? const Color.fromARGB(255, 56, 56, 56)
|
|
||||||
: Colors.black12,
|
|
||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -151,7 +155,9 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"backup_controller_page_to_backup",
|
"backup_controller_page_to_backup",
|
||||||
style: context.textTheme.bodyMedium,
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
|
),
|
||||||
).tr(),
|
).tr(),
|
||||||
buildSelectedAlbumName(),
|
buildSelectedAlbumName(),
|
||||||
buildExcludedAlbumName(),
|
buildExcludedAlbumName(),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import 'package:immich_mobile/entities/album.entity.dart';
|
|||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
||||||
|
|
||||||
@RoutePage<List<String>?>()
|
@RoutePage()
|
||||||
class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
|
class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
|
||||||
final Album album;
|
final Album album;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart';
|
|||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
@RoutePage<AssetSelectionPageResult?>()
|
@RoutePage()
|
||||||
class AlbumAssetSelectionPage extends HookConsumerWidget {
|
class AlbumAssetSelectionPage extends HookConsumerWidget {
|
||||||
const AlbumAssetSelectionPage({
|
const AlbumAssetSelectionPage({
|
||||||
super.key,
|
super.key,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/album/shared_album.provider.dart';
|
import 'package:immich_mobile/providers/album/shared_album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/authentication.provider.dart';
|
import 'package:immich_mobile/providers/authentication.provider.dart';
|
||||||
import 'package:immich_mobile/utils/immich_loading_overlay.dart';
|
import 'package:immich_mobile/utils/immich_loading_overlay.dart';
|
||||||
@@ -102,7 +103,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
backgroundColor: context.scaffoldBackgroundColor,
|
backgroundColor: context.colorScheme.surfaceContainer,
|
||||||
isScrollControlled: false,
|
isScrollControlled: false,
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
@@ -131,7 +132,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
album.owner.value?.email ?? "",
|
album.owner.value?.email ?? "",
|
||||||
style: TextStyle(color: Colors.grey[600]),
|
style: TextStyle(color: context.colorScheme.onSurfaceSecondary),
|
||||||
),
|
),
|
||||||
trailing: Text(
|
trailing: Text(
|
||||||
"shared_album_section_people_owner_label",
|
"shared_album_section_people_owner_label",
|
||||||
@@ -160,7 +161,9 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
user.email,
|
user.email,
|
||||||
style: TextStyle(color: Colors.grey[600]),
|
style: TextStyle(
|
||||||
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
trailing: userId == user.id || isOwner
|
trailing: userId == user.id || isOwner
|
||||||
? const Icon(Icons.more_horiz_rounded)
|
? const Icon(Icons.more_horiz_rounded)
|
||||||
@@ -214,7 +217,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
|||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
"shared_album_activity_setting_subtitle",
|
"shared_album_activity_setting_subtitle",
|
||||||
style: context.textTheme.labelLarge?.copyWith(
|
style: context.textTheme.labelLarge?.copyWith(
|
||||||
color: context.textTheme.labelLarge?.color?.withAlpha(175),
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
),
|
),
|
||||||
).tr(),
|
).tr(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import 'package:immich_mobile/entities/asset.entity.dart';
|
|||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
||||||
|
|
||||||
@RoutePage<List<String>>()
|
@RoutePage()
|
||||||
class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
||||||
const AlbumSharedUserSelectionPage({super.key, required this.assets});
|
const AlbumSharedUserSelectionPage({super.key, required this.assets});
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/album/shared_album.provider.dart';
|
import 'package:immich_mobile/providers/album/shared_album.provider.dart';
|
||||||
import 'package:immich_mobile/utils/immich_loading_overlay.dart';
|
import 'package:immich_mobile/utils/immich_loading_overlay.dart';
|
||||||
import 'package:immich_mobile/services/album.service.dart';
|
import 'package:immich_mobile/services/album.service.dart';
|
||||||
import 'package:immich_mobile/widgets/album/album_action_outlined_button.dart';
|
import 'package:immich_mobile/widgets/album/album_action_filled_button.dart';
|
||||||
import 'package:immich_mobile/widgets/album/album_viewer_editable_title.dart';
|
import 'package:immich_mobile/widgets/album/album_viewer_editable_title.dart';
|
||||||
import 'package:immich_mobile/providers/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/multiselect.provider.dart';
|
||||||
import 'package:immich_mobile/providers/authentication.provider.dart';
|
import 'package:immich_mobile/providers/authentication.provider.dart';
|
||||||
@@ -114,13 +114,13 @@ class AlbumViewerPage extends HookConsumerWidget {
|
|||||||
child: ListView(
|
child: ListView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
children: [
|
children: [
|
||||||
AlbumActionOutlinedButton(
|
AlbumActionFilledButton(
|
||||||
iconData: Icons.add_photo_alternate_outlined,
|
iconData: Icons.add_photo_alternate_outlined,
|
||||||
onPressed: () => onAddPhotosPressed(album),
|
onPressed: () => onAddPhotosPressed(album),
|
||||||
labelText: "share_add_photos".tr(),
|
labelText: "share_add_photos".tr(),
|
||||||
),
|
),
|
||||||
if (userId == album.ownerId)
|
if (userId == album.ownerId)
|
||||||
AlbumActionOutlinedButton(
|
AlbumActionFilledButton(
|
||||||
iconData: Icons.person_add_alt_rounded,
|
iconData: Icons.person_add_alt_rounded,
|
||||||
onPressed: () => onAddUsersPressed(album),
|
onPressed: () => onAddUsersPressed(album),
|
||||||
labelText: "album_viewer_page_share_add_users".tr(),
|
labelText: "album_viewer_page_share_add_users".tr(),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/entities/logger_message.entity.dart';
|
import 'package:immich_mobile/entities/logger_message.entity.dart';
|
||||||
import 'package:immich_mobile/services/immich_logger.service.dart';
|
import 'package:immich_mobile/services/immich_logger.service.dart';
|
||||||
@@ -18,7 +19,6 @@ class AppLogPage extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final immichLogger = ImmichLogger();
|
final immichLogger = ImmichLogger();
|
||||||
final logMessages = useState(immichLogger.messages);
|
final logMessages = useState(immichLogger.messages);
|
||||||
final isDarkTheme = context.isDarkTheme;
|
|
||||||
|
|
||||||
Widget colorStatusIndicator(Color color) {
|
Widget colorStatusIndicator(Color color) {
|
||||||
return Column(
|
return Column(
|
||||||
@@ -55,13 +55,9 @@ class AppLogPage extends HookConsumerWidget {
|
|||||||
case LogLevel.INFO:
|
case LogLevel.INFO:
|
||||||
return Colors.transparent;
|
return Colors.transparent;
|
||||||
case LogLevel.SEVERE:
|
case LogLevel.SEVERE:
|
||||||
return isDarkTheme
|
return Colors.redAccent.withOpacity(0.25);
|
||||||
? Colors.redAccent.withOpacity(0.25)
|
|
||||||
: Colors.redAccent.withOpacity(0.075);
|
|
||||||
case LogLevel.WARNING:
|
case LogLevel.WARNING:
|
||||||
return isDarkTheme
|
return Colors.orangeAccent.withOpacity(0.25);
|
||||||
? Colors.orangeAccent.withOpacity(0.25)
|
|
||||||
: Colors.orangeAccent.withOpacity(0.075);
|
|
||||||
default:
|
default:
|
||||||
return context.primaryColor.withOpacity(0.1);
|
return context.primaryColor.withOpacity(0.1);
|
||||||
}
|
}
|
||||||
@@ -120,10 +116,7 @@ class AppLogPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
body: ListView.separated(
|
body: ListView.separated(
|
||||||
separatorBuilder: (context, index) {
|
separatorBuilder: (context, index) {
|
||||||
return Divider(
|
return const Divider(height: 0);
|
||||||
height: 0,
|
|
||||||
color: isDarkTheme ? Colors.white70 : Colors.grey[600],
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
itemCount: logMessages.value.length,
|
itemCount: logMessages.value.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
@@ -141,8 +134,9 @@ class AppLogPage extends HookConsumerWidget {
|
|||||||
minLeadingWidth: 10,
|
minLeadingWidth: 10,
|
||||||
title: Text(
|
title: Text(
|
||||||
truncateLogMessage(logMessage.message, 4),
|
truncateLogMessage(logMessage.message, 4),
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.0,
|
fontSize: 14.0,
|
||||||
|
color: context.colorScheme.onSurface,
|
||||||
fontFamily: "Inconsolata",
|
fontFamily: "Inconsolata",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -150,7 +144,7 @@ class AppLogPage extends HookConsumerWidget {
|
|||||||
"at ${DateFormat("HH:mm:ss.SSS").format(logMessage.createdAt)} in ${logMessage.context1}",
|
"at ${DateFormat("HH:mm:ss.SSS").format(logMessage.createdAt)} in ${logMessage.context1}",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12.0,
|
fontSize: 12.0,
|
||||||
color: Colors.grey[600],
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
leading: buildLeadingIcon(logMessage.level),
|
leading: buildLeadingIcon(logMessage.level),
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ class AppLogDetailPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
var isDarkTheme = context.isDarkTheme;
|
|
||||||
|
|
||||||
buildTextWithCopyButton(String header, String text) {
|
buildTextWithCopyButton(String header, String text) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
@@ -61,7 +59,7 @@ class AppLogDetailPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isDarkTheme ? Colors.grey[900] : Colors.grey[200],
|
color: context.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(15.0),
|
borderRadius: BorderRadius.circular(15.0),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -100,7 +98,7 @@ class AppLogDetailPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isDarkTheme ? Colors.grey[900] : Colors.grey[200],
|
color: context.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(15.0),
|
borderRadius: BorderRadius.circular(15.0),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import 'package:immich_mobile/providers/album/album.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/album/album_title.provider.dart';
|
import 'package:immich_mobile/providers/album/album_title.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset.provider.dart';
|
import 'package:immich_mobile/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/widgets/album/album_action_outlined_button.dart';
|
import 'package:immich_mobile/widgets/album/album_action_filled_button.dart';
|
||||||
import 'package:immich_mobile/widgets/album/album_title_text_field.dart';
|
import 'package:immich_mobile/widgets/album/album_title_text_field.dart';
|
||||||
import 'package:immich_mobile/widgets/album/shared_album_thumbnail_image.dart';
|
import 'package:immich_mobile/widgets/album/shared_album_thumbnail_image.dart';
|
||||||
|
|
||||||
@@ -109,20 +109,16 @@ class CreateAlbumPage extends HookConsumerWidget {
|
|||||||
if (selectedAssets.value.isEmpty) {
|
if (selectedAssets.value.isEmpty) {
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(top: 16, left: 18, right: 18),
|
padding: const EdgeInsets.only(top: 16, left: 16, right: 16),
|
||||||
child: OutlinedButton.icon(
|
child: FilledButton.icon(
|
||||||
style: OutlinedButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(vertical: 22, horizontal: 16),
|
const EdgeInsets.symmetric(vertical: 24, horizontal: 16),
|
||||||
side: BorderSide(
|
|
||||||
color: context.isDarkTheme
|
|
||||||
? const Color.fromARGB(255, 63, 63, 63)
|
|
||||||
: const Color.fromARGB(255, 129, 129, 129),
|
|
||||||
),
|
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
|
backgroundColor: context.colorScheme.surfaceContainerHigh,
|
||||||
),
|
),
|
||||||
onPressed: onSelectPhotosButtonPressed,
|
onPressed: onSelectPhotosButtonPressed,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
@@ -134,6 +130,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'create_shared_album_page_share_select_photos',
|
'create_shared_album_page_share_select_photos',
|
||||||
style: context.textTheme.titleMedium?.copyWith(
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
color: context.primaryColor,
|
color: context.primaryColor,
|
||||||
),
|
),
|
||||||
).tr(),
|
).tr(),
|
||||||
@@ -150,11 +147,11 @@ class CreateAlbumPage extends HookConsumerWidget {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 12.0, top: 16, bottom: 16),
|
padding: const EdgeInsets.only(left: 12.0, top: 16, bottom: 16),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 30,
|
height: 42,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
children: [
|
children: [
|
||||||
AlbumActionOutlinedButton(
|
AlbumActionFilledButton(
|
||||||
iconData: Icons.add_photo_alternate_outlined,
|
iconData: Icons.add_photo_alternate_outlined,
|
||||||
onPressed: onSelectPhotosButtonPressed,
|
onPressed: onSelectPhotosButtonPressed,
|
||||||
labelText: "share_add_photos".tr(),
|
labelText: "share_add_photos".tr(),
|
||||||
@@ -266,7 +263,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
|||||||
pinned: true,
|
pinned: true,
|
||||||
floating: false,
|
floating: false,
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
preferredSize: const Size.fromHeight(66.0),
|
preferredSize: const Size.fromHeight(96.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
buildTitleInputField(),
|
buildTitleInputField(),
|
||||||
|
|||||||
@@ -49,10 +49,6 @@ class SettingsPage extends StatelessWidget {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
bottom: const PreferredSize(
|
|
||||||
preferredSize: Size.fromHeight(1),
|
|
||||||
child: Divider(height: 1),
|
|
||||||
),
|
|
||||||
title: const Text('setting_pages_app_bar_settings').tr(),
|
title: const Text('setting_pages_app_bar_settings').tr(),
|
||||||
),
|
),
|
||||||
body: context.isMobile ? _MobileLayout() : _TabletLayout(),
|
body: context.isMobile ? _MobileLayout() : _TabletLayout(),
|
||||||
@@ -67,13 +63,18 @@ class _MobileLayout extends StatelessWidget {
|
|||||||
children: SettingSection.values
|
children: SettingSection.values
|
||||||
.map(
|
.map(
|
||||||
(s) => ListTile(
|
(s) => ListTile(
|
||||||
title: Text(
|
contentPadding:
|
||||||
s.title,
|
const EdgeInsets.symmetric(vertical: 2.0, horizontal: 16.0),
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
).tr(),
|
|
||||||
leading: Icon(s.icon),
|
leading: Icon(s.icon),
|
||||||
|
title: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
|
child: Text(
|
||||||
|
s.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
onTap: () => context.pushRoute(SettingsSubRoute(section: s)),
|
onTap: () => context.pushRoute(SettingsSubRoute(section: s)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -102,7 +103,7 @@ class _TabletLayout extends HookWidget {
|
|||||||
leading: Icon(s.icon),
|
leading: Icon(s.icon),
|
||||||
selected: s.index == selectedSection.value.index,
|
selected: s.index == selectedSection.value.index,
|
||||||
selectedColor: context.primaryColor,
|
selectedColor: context.primaryColor,
|
||||||
selectedTileColor: context.primaryColor.withAlpha(50),
|
selectedTileColor: context.themeData.highlightColor,
|
||||||
onTap: () => selectedSection.value = s,
|
onTap: () => selectedSection.value = s,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ class _AspectRatioButton extends StatelessWidget {
|
|||||||
: Theme.of(context).iconTheme.color,
|
: Theme.of(context).iconTheme.color,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
cropController.crop = const Rect.fromLTRB(0.1, 0.1, 0.9, 0.9);
|
||||||
aspectRatio.value = ratio;
|
aspectRatio.value = ratio;
|
||||||
cropController.aspectRatio = ratio;
|
cropController.aspectRatio = ratio;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -97,8 +97,10 @@ class EditImagePage extends ConsumerWidget {
|
|||||||
gravity: ToastGravity.CENTER,
|
gravity: ToastGravity.CENTER,
|
||||||
);
|
);
|
||||||
|
|
||||||
await PhotoManager.editor
|
await PhotoManager.editor.saveImage(
|
||||||
.saveImage(imageData, title: "_edited.jpg");
|
imageData,
|
||||||
|
title: '${asset!.fileName}_edited.jpg',
|
||||||
|
);
|
||||||
await ref.read(albumProvider.notifier).getDeviceAlbums();
|
await ref.read(albumProvider.notifier).getDeviceAlbums();
|
||||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ class LibraryPage extends HookConsumerWidget {
|
|||||||
final trashEnabled =
|
final trashEnabled =
|
||||||
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
||||||
final albums = ref.watch(albumProvider);
|
final albums = ref.watch(albumProvider);
|
||||||
final isDarkTheme = context.isDarkTheme;
|
|
||||||
final albumSortOption = ref.watch(albumSortByOptionsProvider);
|
final albumSortOption = ref.watch(albumSortByOptionsProvider);
|
||||||
final albumSortIsReverse = ref.watch(albumSortOrderProvider);
|
final albumSortIsReverse = ref.watch(albumSortOrderProvider);
|
||||||
|
|
||||||
@@ -116,12 +115,7 @@ class LibraryPage extends HookConsumerWidget {
|
|||||||
width: cardSize,
|
width: cardSize,
|
||||||
height: cardSize,
|
height: cardSize,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
color: context.colorScheme.surfaceContainer,
|
||||||
color: isDarkTheme
|
|
||||||
? const Color.fromARGB(255, 53, 53, 53)
|
|
||||||
: const Color.fromARGB(255, 203, 203, 203),
|
|
||||||
),
|
|
||||||
color: isDarkTheme ? Colors.grey[900] : Colors.grey[50],
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
@@ -139,7 +133,9 @@ class LibraryPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'library_page_new_album',
|
'library_page_new_album',
|
||||||
style: context.textTheme.labelLarge,
|
style: context.textTheme.labelLarge?.copyWith(
|
||||||
|
color: context.colorScheme.onSurface,
|
||||||
|
),
|
||||||
).tr(),
|
).tr(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -156,26 +152,25 @@ class LibraryPage extends HookConsumerWidget {
|
|||||||
Function() onClick,
|
Function() onClick,
|
||||||
) {
|
) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: OutlinedButton.icon(
|
child: FilledButton.icon(
|
||||||
onPressed: onClick,
|
onPressed: onClick,
|
||||||
label: Padding(
|
label: Padding(
|
||||||
padding: const EdgeInsets.only(left: 8.0),
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
label,
|
label,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: context.isDarkTheme
|
color: context.colorScheme.onSurface,
|
||||||
? Colors.white
|
|
||||||
: Colors.black.withAlpha(200),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
style: OutlinedButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
elevation: 0,
|
||||||
backgroundColor: isDarkTheme ? Colors.grey[900] : Colors.grey[50],
|
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||||
side: BorderSide(
|
backgroundColor: context.colorScheme.surfaceContainer,
|
||||||
color: isDarkTheme ? Colors.grey[800]! : Colors.grey[300]!,
|
|
||||||
),
|
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
icon,
|
icon,
|
||||||
@@ -247,6 +242,7 @@ class LibraryPage extends HookConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
'library_page_albums',
|
'library_page_albums',
|
||||||
style: context.textTheme.bodyLarge?.copyWith(
|
style: context.textTheme.bodyLarge?.copyWith(
|
||||||
|
color: context.colorScheme.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
).tr(),
|
).tr(),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/widgets/forms/login/login_form.dart';
|
import 'package:immich_mobile/widgets/forms/login/login_form.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
@@ -39,8 +40,8 @@ class LoginPage extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'v${appVersion.value}',
|
'v${appVersion.value}',
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey,
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontFamily: "Inconsolata",
|
fontFamily: "Inconsolata",
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import 'package:immich_mobile/widgets/map/map_theme_override.dart';
|
|||||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||||
import 'package:immich_mobile/utils/map_utils.dart';
|
import 'package:immich_mobile/utils/map_utils.dart';
|
||||||
|
|
||||||
@RoutePage<LatLng?>()
|
@RoutePage()
|
||||||
class MapLocationPickerPage extends HookConsumerWidget {
|
class MapLocationPickerPage extends HookConsumerWidget {
|
||||||
final LatLng initialLatLng;
|
final LatLng initialLatLng;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/models/search/search_curated_content.model.dart';
|
import 'package:immich_mobile/models/search/search_curated_content.model.dart';
|
||||||
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
||||||
import 'package:immich_mobile/providers/search/people.provider.dart';
|
import 'package:immich_mobile/providers/search/people.provider.dart';
|
||||||
@@ -38,7 +39,7 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
fontSize: 15.0,
|
fontSize: 15.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
Color categoryIconColor = context.isDarkTheme ? Colors.white : Colors.black;
|
Color categoryIconColor = context.colorScheme.onSurface;
|
||||||
|
|
||||||
showNameEditModel(
|
showNameEditModel(
|
||||||
String personId,
|
String personId,
|
||||||
@@ -128,13 +129,9 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
|
color: context.colorScheme.surfaceContainerHigh,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(50),
|
||||||
side: BorderSide(
|
|
||||||
color: context.isDarkTheme
|
|
||||||
? Colors.grey[800]!
|
|
||||||
: const Color.fromARGB(255, 225, 225, 225),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -144,13 +141,15 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.search, color: context.primaryColor),
|
Icon(
|
||||||
|
Icons.search,
|
||||||
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
|
),
|
||||||
const SizedBox(width: 16.0),
|
const SizedBox(width: 16.0),
|
||||||
Text(
|
Text(
|
||||||
"search_bar_hint",
|
"search_bar_hint",
|
||||||
style: context.textTheme.bodyLarge?.copyWith(
|
style: context.textTheme.bodyLarge?.copyWith(
|
||||||
color:
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
context.isDarkTheme ? Colors.white70 : Colors.black54,
|
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
).tr(),
|
).tr(),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
||||||
import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
|
import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
|
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
|
||||||
@@ -509,7 +510,7 @@ class SearchInputPage extends HookConsumerWidget {
|
|||||||
? 'contextual_search'.tr()
|
? 'contextual_search'.tr()
|
||||||
: 'filename_search'.tr(),
|
: 'filename_search'.tr(),
|
||||||
hintStyle: context.textTheme.bodyLarge?.copyWith(
|
hintStyle: context.textTheme.bodyLarge?.copyWith(
|
||||||
color: context.themeData.colorScheme.onSurface.withOpacity(0.75),
|
color: context.themeData.colorScheme.onSurfaceSecondary,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
enabledBorder: const UnderlineInputBorder(
|
enabledBorder: const UnderlineInputBorder(
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
const padding = 20.0;
|
const padding = 20.0;
|
||||||
final themeData = context.themeData;
|
final themeData = context.themeData;
|
||||||
|
final colorScheme = context.colorScheme;
|
||||||
final descriptionController =
|
final descriptionController =
|
||||||
useTextEditingController(text: existingLink?.description ?? "");
|
useTextEditingController(text: existingLink?.description ?? "");
|
||||||
final descriptionFocusNode = useFocusNode();
|
final descriptionFocusNode = useFocusNode();
|
||||||
@@ -58,7 +59,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
existingLink!.title,
|
existingLink!.title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: themeData.primaryColor,
|
color: colorScheme.primary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -81,7 +82,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
existingLink!.description ?? "--",
|
existingLink!.description ?? "--",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: themeData.primaryColor,
|
color: colorScheme.primary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
@@ -109,7 +110,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
|||||||
labelText: 'shared_link_edit_description'.tr(),
|
labelText: 'shared_link_edit_description'.tr(),
|
||||||
labelStyle: TextStyle(
|
labelStyle: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: themeData.primaryColor,
|
color: colorScheme.primary,
|
||||||
),
|
),
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
@@ -135,7 +136,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
|||||||
labelText: 'shared_link_edit_password'.tr(),
|
labelText: 'shared_link_edit_password'.tr(),
|
||||||
labelStyle: TextStyle(
|
labelStyle: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: themeData.primaryColor,
|
color: colorScheme.primary,
|
||||||
),
|
),
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
@@ -157,7 +158,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
|||||||
onChanged: newShareLink.value.isEmpty
|
onChanged: newShareLink.value.isEmpty
|
||||||
? (value) => showMetadata.value = value
|
? (value) => showMetadata.value = value
|
||||||
: null,
|
: null,
|
||||||
activeColor: themeData.primaryColor,
|
activeColor: colorScheme.primary,
|
||||||
dense: true,
|
dense: true,
|
||||||
title: Text(
|
title: Text(
|
||||||
"shared_link_edit_show_meta",
|
"shared_link_edit_show_meta",
|
||||||
@@ -173,7 +174,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
|||||||
onChanged: newShareLink.value.isEmpty
|
onChanged: newShareLink.value.isEmpty
|
||||||
? (value) => allowDownload.value = value
|
? (value) => allowDownload.value = value
|
||||||
: null,
|
: null,
|
||||||
activeColor: themeData.primaryColor,
|
activeColor: colorScheme.primary,
|
||||||
dense: true,
|
dense: true,
|
||||||
title: Text(
|
title: Text(
|
||||||
"shared_link_edit_allow_download",
|
"shared_link_edit_allow_download",
|
||||||
@@ -189,7 +190,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
|||||||
onChanged: newShareLink.value.isEmpty
|
onChanged: newShareLink.value.isEmpty
|
||||||
? (value) => allowUpload.value = value
|
? (value) => allowUpload.value = value
|
||||||
: null,
|
: null,
|
||||||
activeColor: themeData.primaryColor,
|
activeColor: colorScheme.primary,
|
||||||
dense: true,
|
dense: true,
|
||||||
title: Text(
|
title: Text(
|
||||||
"shared_link_edit_allow_upload",
|
"shared_link_edit_allow_upload",
|
||||||
@@ -205,7 +206,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
|||||||
onChanged: newShareLink.value.isEmpty
|
onChanged: newShareLink.value.isEmpty
|
||||||
? (value) => editExpiry.value = value
|
? (value) => editExpiry.value = value
|
||||||
: null,
|
: null,
|
||||||
activeColor: themeData.primaryColor,
|
activeColor: colorScheme.primary,
|
||||||
dense: true,
|
dense: true,
|
||||||
title: Text(
|
title: Text(
|
||||||
"shared_link_edit_change_expiry",
|
"shared_link_edit_change_expiry",
|
||||||
@@ -221,7 +222,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
|||||||
"shared_link_edit_expire_after",
|
"shared_link_edit_expire_after",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: themeData.primaryColor,
|
color: colorScheme.primary,
|
||||||
),
|
),
|
||||||
).tr(),
|
).tr(),
|
||||||
enableSearch: false,
|
enableSearch: false,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||||
import 'package:immich_mobile/providers/album/shared_album.provider.dart';
|
import 'package:immich_mobile/providers/album/shared_album.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
|
import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
|
||||||
@@ -83,20 +84,24 @@ class SharingPage extends HookConsumerWidget {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
color: context.primaryColor,
|
color: context.colorScheme.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: isOwner
|
subtitle: isOwner
|
||||||
? Text(
|
? Text(
|
||||||
'album_thumbnail_owned'.tr(),
|
'album_thumbnail_owned'.tr(),
|
||||||
style: context.textTheme.bodyMedium,
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: album.ownerName != null
|
: album.ownerName != null
|
||||||
? Text(
|
? Text(
|
||||||
'album_thumbnail_shared_by'
|
'album_thumbnail_shared_by'
|
||||||
.tr(args: [album.ownerName!]),
|
.tr(args: [album.ownerName!]),
|
||||||
style: context.textTheme.bodyMedium,
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
onTap: () => context
|
onTap: () => context
|
||||||
@@ -166,11 +171,13 @@ class SharingPage extends HookConsumerWidget {
|
|||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
color: Colors.grey,
|
color: context.isDarkTheme
|
||||||
width: 0.5,
|
? const Color(0xFF383838)
|
||||||
|
: Colors.black12,
|
||||||
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ Future<List<PersonResponseDto>> getAllPeople(
|
|||||||
Future<RenderList> personAssets(PersonAssetsRef ref, String personId) async {
|
Future<RenderList> personAssets(PersonAssetsRef ref, String personId) async {
|
||||||
final PersonService personService = ref.read(personServiceProvider);
|
final PersonService personService = ref.read(personServiceProvider);
|
||||||
final assets = await personService.getPersonAssets(personId);
|
final assets = await personService.getPersonAssets(personId);
|
||||||
if (assets == null) {
|
|
||||||
return RenderList.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
final settings = ref.read(appSettingsServiceProvider);
|
final settings = ref.read(appSettingsServiceProvider);
|
||||||
final groupBy =
|
final groupBy =
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ final getAllPeopleProvider =
|
|||||||
);
|
);
|
||||||
|
|
||||||
typedef GetAllPeopleRef = AutoDisposeFutureProviderRef<List<PersonResponseDto>>;
|
typedef GetAllPeopleRef = AutoDisposeFutureProviderRef<List<PersonResponseDto>>;
|
||||||
String _$personAssetsHash() => r'1d6eff5ca3aa630b58c4dad9516193b21896984d';
|
String _$personAssetsHash() => r'3dfecb67a54d07e4208bcb9581b2625acd2e1832';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:immich_mobile/entities/album.entity.dart';
|
|||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/entities/logger_message.entity.dart';
|
import 'package:immich_mobile/entities/logger_message.entity.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart';
|
|
||||||
import 'package:immich_mobile/models/memories/memory.model.dart';
|
import 'package:immich_mobile/models/memories/memory.model.dart';
|
||||||
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
||||||
import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
|
import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
|
||||||
@@ -69,7 +68,7 @@ import 'package:photo_manager/photo_manager.dart' hide LatLng;
|
|||||||
part 'router.gr.dart';
|
part 'router.gr.dart';
|
||||||
|
|
||||||
@AutoRouterConfig(replaceInRouteName: 'Page,Route')
|
@AutoRouterConfig(replaceInRouteName: 'Page,Route')
|
||||||
class AppRouter extends _$AppRouter {
|
class AppRouter extends RootStackRouter {
|
||||||
late final AuthGuard _authGuard;
|
late final AuthGuard _authGuard;
|
||||||
late final DuplicateGuard _duplicateGuard;
|
late final DuplicateGuard _duplicateGuard;
|
||||||
late final BackupPermissionGuard _backupPermissionGuard;
|
late final BackupPermissionGuard _backupPermissionGuard;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:immich_mobile/constants/immich_colors.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
|
|
||||||
enum AppSettingsEnum<T> {
|
enum AppSettingsEnum<T> {
|
||||||
@@ -8,6 +9,21 @@ enum AppSettingsEnum<T> {
|
|||||||
"themeMode",
|
"themeMode",
|
||||||
"system",
|
"system",
|
||||||
), // "light","dark","system"
|
), // "light","dark","system"
|
||||||
|
primaryColor<String>(
|
||||||
|
StoreKey.primaryColor,
|
||||||
|
"primaryColor",
|
||||||
|
defaultColorPresetName,
|
||||||
|
),
|
||||||
|
dynamicTheme<bool>(
|
||||||
|
StoreKey.dynamicTheme,
|
||||||
|
"dynamicTheme",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
colorfulInterface<bool>(
|
||||||
|
StoreKey.colorfulInterface,
|
||||||
|
"colorfulInterface",
|
||||||
|
true,
|
||||||
|
),
|
||||||
tilesPerRow<int>(StoreKey.tilesPerRow, "tilesPerRow", 4),
|
tilesPerRow<int>(StoreKey.tilesPerRow, "tilesPerRow", 4),
|
||||||
dynamicLayout<bool>(StoreKey.dynamicLayout, "dynamicLayout", false),
|
dynamicLayout<bool>(StoreKey.dynamicLayout, "dynamicLayout", false),
|
||||||
groupAssetsBy<int>(StoreKey.groupAssetsBy, "groupBy", 0),
|
groupAssetsBy<int>(StoreKey.groupAssetsBy, "groupBy", 0),
|
||||||
|
|||||||
@@ -30,15 +30,41 @@ class PersonService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Asset>?> getPersonAssets(String id) async {
|
Future<List<Asset>> getPersonAssets(String id) async {
|
||||||
|
List<Asset> result = [];
|
||||||
|
var hasNext = true;
|
||||||
|
var currentPage = 1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final assets = await _apiService.peopleApi.getPersonAssets(id);
|
while (hasNext) {
|
||||||
if (assets == null) return null;
|
final response = await _apiService.searchApi.searchMetadata(
|
||||||
return await _db.assets.getAllByRemoteId(assets.map((e) => e.id));
|
MetadataSearchDto(
|
||||||
|
personIds: [id],
|
||||||
|
page: currentPage,
|
||||||
|
size: 1000,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.assets.nextPage == null) {
|
||||||
|
hasNext = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final assets = response.assets.items;
|
||||||
|
final mapAssets =
|
||||||
|
await _db.assets.getAllByRemoteId(assets.map((e) => e.id));
|
||||||
|
result.addAll(mapAssets);
|
||||||
|
|
||||||
|
currentPage++;
|
||||||
|
}
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
_log.severe("Error while fetching person assets", error, stack);
|
_log.severe("Error while fetching person assets", error, stack);
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PersonResponseDto?> updateName(String id, String name) async {
|
Future<PersonResponseDto?> updateName(String id, String name) async {
|
||||||
|
|||||||
@@ -1,10 +1,22 @@
|
|||||||
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/immich_colors.dart';
|
import 'package:immich_mobile/constants/immich_colors.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
|
||||||
final immichThemeProvider = StateProvider<ThemeMode>((ref) {
|
class ImmichTheme {
|
||||||
|
ColorScheme light;
|
||||||
|
ColorScheme dark;
|
||||||
|
|
||||||
|
ImmichTheme({required this.light, required this.dark});
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmichTheme? _immichDynamicTheme;
|
||||||
|
bool get isDynamicThemeAvailable => _immichDynamicTheme != null;
|
||||||
|
|
||||||
|
final immichThemeModeProvider = StateProvider<ThemeMode>((ref) {
|
||||||
var themeMode = ref
|
var themeMode = ref
|
||||||
.watch(appSettingsServiceProvider)
|
.watch(appSettingsServiceProvider)
|
||||||
.getSetting(AppSettingsEnum.themeMode);
|
.getSetting(AppSettingsEnum.themeMode);
|
||||||
@@ -20,266 +32,241 @@ final immichThemeProvider = StateProvider<ThemeMode>((ref) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final ThemeData base = ThemeData(
|
final immichThemePresetProvider = StateProvider<ImmichColorPreset>((ref) {
|
||||||
chipTheme: const ChipThemeData(
|
var appSettingsProvider = ref.watch(appSettingsServiceProvider);
|
||||||
side: BorderSide.none,
|
var primaryColorName =
|
||||||
),
|
appSettingsProvider.getSetting(AppSettingsEnum.primaryColor);
|
||||||
sliderTheme: const SliderThemeData(
|
|
||||||
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
|
|
||||||
trackHeight: 2.0,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final ThemeData immichLightTheme = ThemeData(
|
debugPrint("Current theme preset $primaryColorName");
|
||||||
useMaterial3: true,
|
|
||||||
brightness: Brightness.light,
|
|
||||||
colorScheme: ColorScheme.fromSeed(
|
|
||||||
seedColor: Colors.indigo,
|
|
||||||
),
|
|
||||||
primarySwatch: Colors.indigo,
|
|
||||||
primaryColor: Colors.indigo,
|
|
||||||
hintColor: Colors.indigo,
|
|
||||||
focusColor: Colors.indigo,
|
|
||||||
splashColor: Colors.indigo.withOpacity(0.15),
|
|
||||||
fontFamily: 'Overpass',
|
|
||||||
scaffoldBackgroundColor: immichBackgroundColor,
|
|
||||||
snackBarTheme: const SnackBarThemeData(
|
|
||||||
contentTextStyle: TextStyle(
|
|
||||||
fontFamily: 'Overpass',
|
|
||||||
color: Colors.indigo,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
),
|
|
||||||
appBarTheme: const AppBarTheme(
|
|
||||||
titleTextStyle: TextStyle(
|
|
||||||
fontFamily: 'Overpass',
|
|
||||||
color: Colors.indigo,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
backgroundColor: immichBackgroundColor,
|
|
||||||
foregroundColor: Colors.indigo,
|
|
||||||
elevation: 0,
|
|
||||||
scrolledUnderElevation: 0,
|
|
||||||
centerTitle: true,
|
|
||||||
),
|
|
||||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
|
||||||
type: BottomNavigationBarType.fixed,
|
|
||||||
backgroundColor: immichBackgroundColor,
|
|
||||||
selectedItemColor: Colors.indigo,
|
|
||||||
),
|
|
||||||
cardTheme: const CardTheme(
|
|
||||||
surfaceTintColor: Colors.transparent,
|
|
||||||
),
|
|
||||||
drawerTheme: const DrawerThemeData(
|
|
||||||
backgroundColor: immichBackgroundColor,
|
|
||||||
),
|
|
||||||
textTheme: const TextTheme(
|
|
||||||
displayLarge: TextStyle(
|
|
||||||
fontSize: 26,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.indigo,
|
|
||||||
),
|
|
||||||
displayMedium: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
displaySmall: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.indigo,
|
|
||||||
),
|
|
||||||
titleSmall: TextStyle(
|
|
||||||
fontSize: 16.0,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
titleMedium: TextStyle(
|
|
||||||
fontSize: 18.0,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
titleLarge: TextStyle(
|
|
||||||
fontSize: 26.0,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Colors.indigo,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
chipTheme: base.chipTheme,
|
|
||||||
sliderTheme: base.sliderTheme,
|
|
||||||
popupMenuTheme: const PopupMenuThemeData(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
|
||||||
),
|
|
||||||
surfaceTintColor: Colors.transparent,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
navigationBarTheme: NavigationBarThemeData(
|
|
||||||
indicatorColor: Colors.indigo.withOpacity(0.15),
|
|
||||||
iconTheme: WidgetStatePropertyAll(
|
|
||||||
IconThemeData(color: Colors.grey[700]),
|
|
||||||
),
|
|
||||||
backgroundColor: immichBackgroundColor,
|
|
||||||
surfaceTintColor: Colors.transparent,
|
|
||||||
labelTextStyle: WidgetStatePropertyAll(
|
|
||||||
TextStyle(
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Colors.grey[800],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
dialogTheme: const DialogTheme(
|
|
||||||
surfaceTintColor: Colors.transparent,
|
|
||||||
),
|
|
||||||
inputDecorationTheme: const InputDecorationTheme(
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: Colors.indigo,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
labelStyle: TextStyle(
|
|
||||||
color: Colors.indigo,
|
|
||||||
),
|
|
||||||
hintStyle: TextStyle(
|
|
||||||
fontSize: 14.0,
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
textSelectionTheme: const TextSelectionThemeData(
|
|
||||||
cursorColor: Colors.indigo,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final ThemeData immichDarkTheme = ThemeData(
|
try {
|
||||||
useMaterial3: true,
|
return ImmichColorPreset.values
|
||||||
brightness: Brightness.dark,
|
.firstWhere((e) => e.name == primaryColorName);
|
||||||
primarySwatch: Colors.indigo,
|
} catch (e) {
|
||||||
primaryColor: immichDarkThemePrimaryColor,
|
debugPrint(
|
||||||
colorScheme: ColorScheme.fromSeed(
|
"Theme preset $primaryColorName not found. Applying default preset.",
|
||||||
seedColor: immichDarkThemePrimaryColor,
|
);
|
||||||
brightness: Brightness.dark,
|
appSettingsProvider.setSetting(
|
||||||
),
|
AppSettingsEnum.primaryColor,
|
||||||
scaffoldBackgroundColor: immichDarkBackgroundColor,
|
defaultColorPresetName,
|
||||||
hintColor: Colors.grey[600],
|
);
|
||||||
fontFamily: 'Overpass',
|
return defaultColorPreset;
|
||||||
snackBarTheme: SnackBarThemeData(
|
}
|
||||||
contentTextStyle: const TextStyle(
|
});
|
||||||
fontFamily: 'Overpass',
|
|
||||||
color: immichDarkThemePrimaryColor,
|
final dynamicThemeSettingProvider = StateProvider<bool>((ref) {
|
||||||
fontWeight: FontWeight.bold,
|
return ref
|
||||||
|
.watch(appSettingsServiceProvider)
|
||||||
|
.getSetting(AppSettingsEnum.dynamicTheme);
|
||||||
|
});
|
||||||
|
|
||||||
|
final colorfulInterfaceSettingProvider = StateProvider<bool>((ref) {
|
||||||
|
return ref
|
||||||
|
.watch(appSettingsServiceProvider)
|
||||||
|
.getSetting(AppSettingsEnum.colorfulInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Provider for current selected theme
|
||||||
|
final immichThemeProvider = StateProvider<ImmichTheme>((ref) {
|
||||||
|
var primaryColor = ref.read(immichThemePresetProvider);
|
||||||
|
var useSystemColor = ref.watch(dynamicThemeSettingProvider);
|
||||||
|
var useColorfulInterface = ref.watch(colorfulInterfaceSettingProvider);
|
||||||
|
|
||||||
|
var currentTheme = (useSystemColor && _immichDynamicTheme != null)
|
||||||
|
? _immichDynamicTheme!
|
||||||
|
: primaryColor.getTheme();
|
||||||
|
|
||||||
|
return useColorfulInterface
|
||||||
|
? currentTheme
|
||||||
|
: _decolorizeSurfaces(theme: currentTheme);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Method to fetch dynamic system colors
|
||||||
|
Future<void> fetchSystemPalette() async {
|
||||||
|
try {
|
||||||
|
final corePalette = await DynamicColorPlugin.getCorePalette();
|
||||||
|
if (corePalette != null) {
|
||||||
|
final primaryColor = corePalette.toColorScheme().primary;
|
||||||
|
debugPrint('dynamic_color: Core palette detected.');
|
||||||
|
|
||||||
|
// Some palettes do not generate surface container colors accurately,
|
||||||
|
// so we regenerate all colors using the primary color
|
||||||
|
_immichDynamicTheme = ImmichTheme(
|
||||||
|
light: ColorScheme.fromSeed(
|
||||||
|
seedColor: primaryColor,
|
||||||
|
brightness: Brightness.light,
|
||||||
|
),
|
||||||
|
dark: ColorScheme.fromSeed(
|
||||||
|
seedColor: primaryColor,
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('dynamic_color: Failed to obtain core palette.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method replaces all surface shades in ImmichTheme to a static ones
|
||||||
|
// as we are creating the colorscheme through seedColor the default surfaces are
|
||||||
|
// tinted with primary color
|
||||||
|
ImmichTheme _decolorizeSurfaces({
|
||||||
|
required ImmichTheme theme,
|
||||||
|
}) {
|
||||||
|
return ImmichTheme(
|
||||||
|
light: theme.light.copyWith(
|
||||||
|
surface: const Color(0xFFf9f9f9),
|
||||||
|
onSurface: const Color(0xFF1b1b1b),
|
||||||
|
surfaceContainerLowest: const Color(0xFFffffff),
|
||||||
|
surfaceContainerLow: const Color(0xFFf3f3f3),
|
||||||
|
surfaceContainer: const Color(0xFFeeeeee),
|
||||||
|
surfaceContainerHigh: const Color(0xFFe8e8e8),
|
||||||
|
surfaceContainerHighest: const Color(0xFFe2e2e2),
|
||||||
|
surfaceDim: const Color(0xFFdadada),
|
||||||
|
surfaceBright: const Color(0xFFf9f9f9),
|
||||||
|
onSurfaceVariant: const Color(0xFF4c4546),
|
||||||
|
inverseSurface: const Color(0xFF303030),
|
||||||
|
onInverseSurface: const Color(0xFFf1f1f1),
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.grey[900],
|
dark: theme.dark.copyWith(
|
||||||
),
|
surface: const Color(0xFF131313),
|
||||||
textButtonTheme: TextButtonThemeData(
|
onSurface: const Color(0xFFE2E2E2),
|
||||||
style: TextButton.styleFrom(
|
surfaceContainerLowest: const Color(0xFF0E0E0E),
|
||||||
foregroundColor: immichDarkThemePrimaryColor,
|
surfaceContainerLow: const Color(0xFF1B1B1B),
|
||||||
|
surfaceContainer: const Color(0xFF1F1F1F),
|
||||||
|
surfaceContainerHigh: const Color(0xFF242424),
|
||||||
|
surfaceContainerHighest: const Color(0xFF2E2E2E),
|
||||||
|
surfaceDim: const Color(0xFF131313),
|
||||||
|
surfaceBright: const Color(0xFF353535),
|
||||||
|
onSurfaceVariant: const Color(0xFFCfC4C5),
|
||||||
|
inverseSurface: const Color(0xFFE2E2E2),
|
||||||
|
onInverseSurface: const Color(0xFF303030),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
appBarTheme: const AppBarTheme(
|
}
|
||||||
titleTextStyle: TextStyle(
|
|
||||||
fontFamily: 'Overpass',
|
ThemeData getThemeData({required ColorScheme colorScheme}) {
|
||||||
color: immichDarkThemePrimaryColor,
|
var isDark = colorScheme.brightness == Brightness.dark;
|
||||||
fontWeight: FontWeight.bold,
|
var primaryColor = colorScheme.primary;
|
||||||
fontSize: 18,
|
|
||||||
|
return ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
brightness: isDark ? Brightness.dark : Brightness.light,
|
||||||
|
colorScheme: colorScheme,
|
||||||
|
primaryColor: primaryColor,
|
||||||
|
hintColor: colorScheme.onSurfaceSecondary,
|
||||||
|
focusColor: primaryColor,
|
||||||
|
scaffoldBackgroundColor: colorScheme.surface,
|
||||||
|
splashColor: primaryColor.withOpacity(0.1),
|
||||||
|
highlightColor: primaryColor.withOpacity(0.1),
|
||||||
|
dialogBackgroundColor: colorScheme.surfaceContainer,
|
||||||
|
bottomSheetTheme: BottomSheetThemeData(
|
||||||
|
backgroundColor: colorScheme.surfaceContainer,
|
||||||
),
|
),
|
||||||
backgroundColor: Color.fromARGB(255, 32, 33, 35),
|
fontFamily: 'Overpass',
|
||||||
foregroundColor: immichDarkThemePrimaryColor,
|
snackBarTheme: SnackBarThemeData(
|
||||||
elevation: 0,
|
contentTextStyle: TextStyle(
|
||||||
scrolledUnderElevation: 0,
|
fontFamily: 'Overpass',
|
||||||
centerTitle: true,
|
color: primaryColor,
|
||||||
),
|
fontWeight: FontWeight.bold,
|
||||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
),
|
||||||
type: BottomNavigationBarType.fixed,
|
backgroundColor: colorScheme.surfaceContainerHighest,
|
||||||
backgroundColor: Color.fromARGB(255, 35, 36, 37),
|
|
||||||
selectedItemColor: immichDarkThemePrimaryColor,
|
|
||||||
),
|
|
||||||
drawerTheme: DrawerThemeData(
|
|
||||||
backgroundColor: immichDarkBackgroundColor,
|
|
||||||
scrimColor: Colors.white.withOpacity(0.1),
|
|
||||||
),
|
|
||||||
textTheme: const TextTheme(
|
|
||||||
displayLarge: TextStyle(
|
|
||||||
fontSize: 26,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color.fromARGB(255, 255, 255, 255),
|
|
||||||
),
|
),
|
||||||
displayMedium: TextStyle(
|
appBarTheme: AppBarTheme(
|
||||||
fontSize: 14,
|
titleTextStyle: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
color: primaryColor,
|
||||||
color: Color.fromARGB(255, 255, 255, 255),
|
fontFamily: 'Overpass',
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
backgroundColor:
|
||||||
|
isDark ? colorScheme.surfaceContainer : colorScheme.surface,
|
||||||
|
foregroundColor: primaryColor,
|
||||||
|
elevation: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
displaySmall: TextStyle(
|
textTheme: TextTheme(
|
||||||
fontSize: 12,
|
displayLarge: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontSize: 26,
|
||||||
color: immichDarkThemePrimaryColor,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
color: isDark ? Colors.white : primaryColor,
|
||||||
titleSmall: TextStyle(
|
),
|
||||||
fontSize: 16.0,
|
displayMedium: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontSize: 14,
|
||||||
),
|
fontWeight: FontWeight.bold,
|
||||||
titleMedium: TextStyle(
|
color: isDark ? Colors.white : Colors.black87,
|
||||||
fontSize: 18.0,
|
),
|
||||||
fontWeight: FontWeight.bold,
|
displaySmall: TextStyle(
|
||||||
),
|
fontSize: 12,
|
||||||
titleLarge: TextStyle(
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 26.0,
|
color: primaryColor,
|
||||||
fontWeight: FontWeight.bold,
|
),
|
||||||
),
|
titleSmall: const TextStyle(
|
||||||
),
|
fontSize: 16.0,
|
||||||
cardColor: Colors.grey[900],
|
fontWeight: FontWeight.bold,
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
),
|
||||||
style: ElevatedButton.styleFrom(
|
titleMedium: const TextStyle(
|
||||||
foregroundColor: Colors.black87,
|
fontSize: 18.0,
|
||||||
backgroundColor: immichDarkThemePrimaryColor,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
titleLarge: const TextStyle(
|
||||||
chipTheme: base.chipTheme,
|
fontSize: 26.0,
|
||||||
sliderTheme: base.sliderTheme,
|
fontWeight: FontWeight.bold,
|
||||||
popupMenuTheme: const PopupMenuThemeData(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
|
||||||
),
|
|
||||||
surfaceTintColor: Colors.transparent,
|
|
||||||
),
|
|
||||||
navigationBarTheme: NavigationBarThemeData(
|
|
||||||
indicatorColor: immichDarkThemePrimaryColor.withOpacity(0.4),
|
|
||||||
iconTheme: WidgetStatePropertyAll(
|
|
||||||
IconThemeData(color: Colors.grey[500]),
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.grey[900],
|
|
||||||
surfaceTintColor: Colors.transparent,
|
|
||||||
labelTextStyle: WidgetStatePropertyAll(
|
|
||||||
TextStyle(
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Colors.grey[300],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
dialogTheme: const DialogTheme(
|
style: ElevatedButton.styleFrom(
|
||||||
surfaceTintColor: Colors.transparent,
|
backgroundColor: primaryColor,
|
||||||
),
|
foregroundColor: isDark ? Colors.black87 : Colors.white,
|
||||||
inputDecorationTheme: const InputDecorationTheme(
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: immichDarkThemePrimaryColor,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
labelStyle: TextStyle(
|
chipTheme: const ChipThemeData(
|
||||||
color: immichDarkThemePrimaryColor,
|
side: BorderSide.none,
|
||||||
),
|
),
|
||||||
hintStyle: TextStyle(
|
sliderTheme: const SliderThemeData(
|
||||||
fontSize: 14.0,
|
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
|
||||||
fontWeight: FontWeight.normal,
|
trackHeight: 2.0,
|
||||||
),
|
),
|
||||||
),
|
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||||
textSelectionTheme: const TextSelectionThemeData(
|
type: BottomNavigationBarType.fixed,
|
||||||
cursorColor: immichDarkThemePrimaryColor,
|
),
|
||||||
),
|
popupMenuTheme: const PopupMenuThemeData(
|
||||||
);
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
|
backgroundColor:
|
||||||
|
isDark ? colorScheme.surfaceContainer : colorScheme.surface,
|
||||||
|
labelTextStyle: const WidgetStatePropertyAll(
|
||||||
|
TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: colorScheme.outlineVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
hintStyle: const TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
textSelectionTheme: TextSelectionThemeData(
|
||||||
|
cursorColor: primaryColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
|
||||||
class AlbumActionOutlinedButton extends StatelessWidget {
|
class AlbumActionFilledButton extends StatelessWidget {
|
||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
final String labelText;
|
final String labelText;
|
||||||
final IconData iconData;
|
final IconData iconData;
|
||||||
|
|
||||||
const AlbumActionOutlinedButton({
|
const AlbumActionFilledButton({
|
||||||
super.key,
|
super.key,
|
||||||
this.onPressed,
|
this.onPressed,
|
||||||
required this.labelText,
|
required this.labelText,
|
||||||
@@ -17,18 +17,13 @@ class AlbumActionOutlinedButton extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(right: 16.0),
|
padding: const EdgeInsets.only(right: 16.0),
|
||||||
child: OutlinedButton.icon(
|
child: FilledButton.icon(
|
||||||
style: OutlinedButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
|
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(25),
|
borderRadius: BorderRadius.circular(25),
|
||||||
),
|
),
|
||||||
side: BorderSide(
|
backgroundColor: context.colorScheme.surfaceContainerHigh,
|
||||||
width: 1,
|
|
||||||
color: context.isDarkTheme
|
|
||||||
? const Color.fromARGB(255, 63, 63, 63)
|
|
||||||
: const Color.fromARGB(255, 206, 206, 206),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
iconData,
|
iconData,
|
||||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/entities/album.entity.dart';
|
import 'package:immich_mobile/entities/album.entity.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
|
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
|
||||||
|
|
||||||
class AlbumThumbnailCard extends StatelessWidget {
|
class AlbumThumbnailCard extends StatelessWidget {
|
||||||
@@ -23,8 +24,6 @@ class AlbumThumbnailCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var isDarkTheme = context.isDarkTheme;
|
|
||||||
|
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
var cardSize = constraints.maxWidth;
|
var cardSize = constraints.maxWidth;
|
||||||
@@ -34,12 +33,13 @@ class AlbumThumbnailCard extends StatelessWidget {
|
|||||||
height: cardSize,
|
height: cardSize,
|
||||||
width: cardSize,
|
width: cardSize,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isDarkTheme ? Colors.grey[800] : Colors.grey[200],
|
color: context.colorScheme.surfaceContainerHigh,
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.no_photography,
|
Icons.no_photography,
|
||||||
size: cardSize * .15,
|
size: cardSize * .15,
|
||||||
|
color: context.colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -65,6 +65,9 @@ class AlbumThumbnailCard extends StatelessWidget {
|
|||||||
return RichText(
|
return RichText(
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: album.assetCount == 1
|
text: album.assetCount == 1
|
||||||
@@ -72,14 +75,9 @@ class AlbumThumbnailCard extends StatelessWidget {
|
|||||||
.tr(args: ['${album.assetCount}'])
|
.tr(args: ['${album.assetCount}'])
|
||||||
: 'album_thumbnail_card_items'
|
: 'album_thumbnail_card_items'
|
||||||
.tr(args: ['${album.assetCount}']),
|
.tr(args: ['${album.assetCount}']),
|
||||||
style: context.textTheme.bodyMedium,
|
|
||||||
),
|
),
|
||||||
if (owner != null) const TextSpan(text: ' · '),
|
if (owner != null) const TextSpan(text: ' · '),
|
||||||
if (owner != null)
|
if (owner != null) TextSpan(text: owner),
|
||||||
TextSpan(
|
|
||||||
text: owner,
|
|
||||||
style: context.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -112,7 +110,7 @@ class AlbumThumbnailCard extends StatelessWidget {
|
|||||||
album.name,
|
album.name,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
color: context.primaryColor,
|
color: context.colorScheme.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ class AlbumTitleTextField extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final isDarkTheme = context.isDarkTheme;
|
|
||||||
|
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
if (v.isEmpty) {
|
if (v.isEmpty) {
|
||||||
@@ -35,7 +33,7 @@ class AlbumTitleTextField extends ConsumerWidget {
|
|||||||
focusNode: albumTitleTextFieldFocusNode,
|
focusNode: albumTitleTextFieldFocusNode,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
color: isDarkTheme ? Colors.grey[300] : Colors.grey[700],
|
color: context.colorScheme.onSurface,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
controller: albumTitleController,
|
controller: albumTitleController,
|
||||||
@@ -70,15 +68,12 @@ class AlbumTitleTextField extends ConsumerWidget {
|
|||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
hintText: 'share_add_title'.tr(),
|
hintText: 'share_add_title'.tr(),
|
||||||
hintStyle: TextStyle(
|
hintStyle: context.themeData.inputDecorationTheme.hintStyle?.copyWith(
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
color: isDarkTheme ? Colors.grey[300] : Colors.grey[700],
|
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
focusColor: Colors.grey[300],
|
focusColor: Colors.grey[300],
|
||||||
fillColor: isDarkTheme
|
fillColor: context.colorScheme.surfaceContainerHigh,
|
||||||
? const Color.fromARGB(255, 32, 33, 35)
|
|
||||||
: Colors.grey[200],
|
|
||||||
filled: isAlbumTitleTextFieldFocus.value,
|
filled: isAlbumTitleTextFieldFocus.value,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
|||||||
'action_common_confirm',
|
'action_common_confirm',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: !context.isDarkTheme ? Colors.red : Colors.red[300],
|
color: context.colorScheme.error,
|
||||||
),
|
),
|
||||||
).tr(),
|
).tr(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -73,24 +73,18 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
|
|||||||
splashRadius: 10,
|
splashRadius: 10,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: const OutlineInputBorder(
|
||||||
borderSide: const BorderSide(color: Colors.transparent),
|
borderSide: BorderSide(color: Colors.transparent),
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: const OutlineInputBorder(
|
||||||
borderSide: const BorderSide(color: Colors.transparent),
|
borderSide: BorderSide(color: Colors.transparent),
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
),
|
||||||
focusColor: Colors.grey[300],
|
focusColor: Colors.grey[300],
|
||||||
fillColor: context.isDarkTheme
|
fillColor: context.scaffoldBackgroundColor,
|
||||||
? const Color.fromARGB(255, 32, 33, 35)
|
|
||||||
: Colors.grey[200],
|
|
||||||
filled: titleFocusNode.hasFocus,
|
filled: titleFocusNode.hasFocus,
|
||||||
hintText: 'share_add_title'.tr(),
|
hintText: 'share_add_title'.tr(),
|
||||||
hintStyle: TextStyle(
|
hintStyle: context.themeData.inputDecorationTheme.hintStyle?.copyWith(
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
color: context.isDarkTheme ? Colors.grey[300] : Colors.grey[700],
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
|||||||
ScrollController scrollController,
|
ScrollController scrollController,
|
||||||
) {
|
) {
|
||||||
return Card(
|
return Card(
|
||||||
color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[100],
|
color: context.colorScheme.surfaceContainerLow,
|
||||||
surfaceTintColor: Colors.transparent,
|
surfaceTintColor: Colors.transparent,
|
||||||
elevation: 18.0,
|
elevation: 18.0,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
|
|||||||
@@ -22,12 +22,15 @@ class DisableMultiSelectButton extends ConsumerWidget {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: () => onPressed(),
|
onPressed: () => onPressed(),
|
||||||
icon: const Icon(Icons.close_rounded),
|
icon: Icon(
|
||||||
|
Icons.close_rounded,
|
||||||
|
color: context.colorScheme.onPrimary,
|
||||||
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
'$selectedItemCount',
|
'$selectedItemCount',
|
||||||
style: context.textTheme.titleMedium?.copyWith(
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
height: 2.5,
|
height: 2.5,
|
||||||
color: context.isDarkTheme ? Colors.black : Colors.white,
|
color: context.colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
@@ -74,9 +75,9 @@ class GroupDividerTitle extends HookConsumerWidget {
|
|||||||
Icons.check_circle_rounded,
|
Icons.check_circle_rounded,
|
||||||
color: context.primaryColor,
|
color: context.primaryColor,
|
||||||
)
|
)
|
||||||
: const Icon(
|
: Icon(
|
||||||
Icons.check_circle_outline_rounded,
|
Icons.check_circle_outline_rounded,
|
||||||
color: Colors.grey,
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/collection_extensions.dart';
|
import 'package:immich_mobile/extensions/collection_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/scroll_notifier.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/scroll_notifier.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/asset_drag_region.dart';
|
import 'package:immich_mobile/widgets/asset_grid/asset_drag_region.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/thumbnail_image.dart';
|
import 'package:immich_mobile/widgets/asset_grid/thumbnail_image.dart';
|
||||||
@@ -266,7 +267,9 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
|||||||
scrollStateListener: dragScrolling,
|
scrollStateListener: dragScrolling,
|
||||||
itemPositionsListener: _itemPositionsListener,
|
itemPositionsListener: _itemPositionsListener,
|
||||||
controller: _itemScrollController,
|
controller: _itemScrollController,
|
||||||
backgroundColor: context.themeData.hintColor,
|
backgroundColor: context.isDarkTheme
|
||||||
|
? context.colorScheme.primary.darken(amount: .5)
|
||||||
|
: context.colorScheme.primary,
|
||||||
labelTextBuilder: _labelBuilder,
|
labelTextBuilder: _labelBuilder,
|
||||||
padding: appBarOffset()
|
padding: appBarOffset()
|
||||||
? const EdgeInsets.only(top: 60)
|
? const EdgeInsets.only(top: 60)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
|
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
|
||||||
import 'package:immich_mobile/utils/storage_indicator.dart';
|
import 'package:immich_mobile/utils/storage_indicator.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
@@ -42,8 +43,8 @@ class ThumbnailImage extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final assetContainerColor = context.isDarkTheme
|
final assetContainerColor = context.isDarkTheme
|
||||||
? Colors.blueGrey
|
? context.primaryColor.darken(amount: 0.6)
|
||||||
: context.themeData.primaryColorLight;
|
: context.primaryColor.lighten(amount: 0.8);
|
||||||
// Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
|
// Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
|
||||||
final isFromDto = asset.id == Isar.autoIncrement;
|
final isFromDto = asset.id == Isar.autoIncrement;
|
||||||
|
|
||||||
@@ -192,8 +193,8 @@ class ThumbnailImage extends ConsumerWidget {
|
|||||||
bottom: 5,
|
bottom: 5,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
storageIcon(asset),
|
storageIcon(asset),
|
||||||
color: Colors.white,
|
color: Colors.white.withOpacity(.8),
|
||||||
size: 18,
|
size: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (asset.isFavorite)
|
if (asset.isFavorite)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
|
|
||||||
class ThumbnailPlaceholder extends StatelessWidget {
|
class ThumbnailPlaceholder extends StatelessWidget {
|
||||||
final EdgeInsets margin;
|
final EdgeInsets margin;
|
||||||
@@ -13,25 +14,20 @@ class ThumbnailPlaceholder extends StatelessWidget {
|
|||||||
this.height = 250,
|
this.height = 250,
|
||||||
});
|
});
|
||||||
|
|
||||||
static const _brightColors = [
|
|
||||||
Color(0xFFF1F3F4),
|
|
||||||
Color(0xFFB4B6B8),
|
|
||||||
];
|
|
||||||
|
|
||||||
static const _darkColors = [
|
|
||||||
Color(0xFF3B3F42),
|
|
||||||
Color(0xFF2B2F32),
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var gradientColors = [
|
||||||
|
context.colorScheme.surfaceContainer,
|
||||||
|
context.colorScheme.surfaceContainer.darken(amount: .1),
|
||||||
|
];
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
margin: margin,
|
margin: margin,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: context.isDarkTheme ? _darkColors : _brightColors,
|
colors: gradientColors,
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/entities/exif_info.entity.dart';
|
import 'package:immich_mobile/entities/exif_info.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/services/asset_description.service.dart';
|
import 'package:immich_mobile/services/asset_description.service.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
@@ -23,7 +24,6 @@ class DescriptionInput extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final textColor = context.isDarkTheme ? Colors.white : Colors.black;
|
|
||||||
final controller = useTextEditingController();
|
final controller = useTextEditingController();
|
||||||
final focusNode = useFocusNode();
|
final focusNode = useFocusNode();
|
||||||
final isFocus = useState(false);
|
final isFocus = useState(false);
|
||||||
@@ -71,7 +71,7 @@ class DescriptionInput extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.cancel_rounded,
|
Icons.cancel_rounded,
|
||||||
color: Colors.grey[500],
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
),
|
),
|
||||||
splashRadius: 10,
|
splashRadius: 10,
|
||||||
);
|
);
|
||||||
@@ -100,9 +100,6 @@ class DescriptionInput extends HookConsumerWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'description_input_hint_text'.tr(),
|
hintText: 'description_input_hint_text'.tr(),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
hintStyle: context.textTheme.labelLarge?.copyWith(
|
|
||||||
color: textColor.withOpacity(0.5),
|
|
||||||
),
|
|
||||||
suffixIcon: suffixIcon,
|
suffixIcon: suffixIcon,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final assetWithExif = ref.watch(assetDetailProvider(asset));
|
final assetWithExif = ref.watch(assetDetailProvider(asset));
|
||||||
var textColor = context.isDarkTheme ? Colors.white : Colors.black;
|
var textColor = context.colorScheme.onSurface;
|
||||||
final ExifInfo? exifInfo = (assetWithExif.value ?? asset).exifInfo;
|
final ExifInfo? exifInfo = (assetWithExif.value ?? asset).exifInfo;
|
||||||
// Format the date time with the timezone
|
// Format the date time with the timezone
|
||||||
final (dt, timeZone) =
|
final (dt, timeZone) =
|
||||||
|
|||||||
@@ -178,6 +178,7 @@ class TopControlAppBar extends HookConsumerWidget {
|
|||||||
actionsIconTheme: const IconThemeData(
|
actionsIconTheme: const IconThemeData(
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
),
|
),
|
||||||
|
shape: const Border(),
|
||||||
actions: [
|
actions: [
|
||||||
if (asset.isRemote && isOwner) buildFavoriteButton(a),
|
if (asset.isRemote && isOwner) buildFavoriteButton(a),
|
||||||
if (asset.livePhotoVideoId != null) buildLivePhotoButton(),
|
if (asset.livePhotoVideoId != null) buildLivePhotoButton(),
|
||||||
|
|||||||
@@ -47,22 +47,22 @@ class AlbumInfoListTile extends HookConsumerWidget {
|
|||||||
|
|
||||||
buildIcon() {
|
buildIcon() {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
return const Icon(
|
return Icon(
|
||||||
Icons.check_circle_rounded,
|
Icons.check_circle_rounded,
|
||||||
color: Colors.green,
|
color: context.colorScheme.primary,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isExcluded) {
|
if (isExcluded) {
|
||||||
return const Icon(
|
return Icon(
|
||||||
Icons.remove_circle_rounded,
|
Icons.remove_circle_rounded,
|
||||||
color: Colors.red,
|
color: context.colorScheme.error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Icon(
|
return Icon(
|
||||||
Icons.circle,
|
Icons.circle,
|
||||||
color: context.isDarkTheme ? Colors.grey[400] : Colors.black45,
|
color: context.colorScheme.surfaceContainerHighest,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
|
|
||||||
class BackupInfoCard extends StatelessWidget {
|
class BackupInfoCard extends StatelessWidget {
|
||||||
final String title;
|
final String title;
|
||||||
@@ -19,9 +20,7 @@ class BackupInfoCard extends StatelessWidget {
|
|||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(20), // if you need this
|
borderRadius: BorderRadius.circular(20), // if you need this
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
color: context.isDarkTheme
|
color: context.colorScheme.outlineVariant,
|
||||||
? const Color.fromARGB(255, 56, 56, 56)
|
|
||||||
: Colors.black12,
|
|
||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -38,7 +37,9 @@ class BackupInfoCard extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.only(top: 4.0, right: 18.0),
|
padding: const EdgeInsets.only(top: 4.0, right: 18.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
subtitle,
|
subtitle,
|
||||||
style: context.textTheme.bodyMedium,
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
trailing: Column(
|
trailing: Column(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart';
|
import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart';
|
||||||
@@ -82,22 +83,20 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
|
|||||||
Widget buildAssetInfoTable() {
|
Widget buildAssetInfoTable() {
|
||||||
return Table(
|
return Table(
|
||||||
border: TableBorder.all(
|
border: TableBorder.all(
|
||||||
color: context.themeData.primaryColorLight,
|
color: context.colorScheme.outlineVariant,
|
||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
TableRow(
|
TableRow(
|
||||||
decoration: const BoxDecoration(
|
|
||||||
// color: Colors.grey[100],
|
|
||||||
),
|
|
||||||
children: [
|
children: [
|
||||||
TableCell(
|
TableCell(
|
||||||
verticalAlignment: TableCellVerticalAlignment.middle,
|
verticalAlignment: TableCellVerticalAlignment.middle,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(6.0),
|
padding: const EdgeInsets.all(6.0),
|
||||||
child: const Text(
|
child: Text(
|
||||||
'backup_controller_page_filename',
|
'backup_controller_page_filename',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 10.0,
|
fontSize: 10.0,
|
||||||
),
|
),
|
||||||
@@ -109,17 +108,15 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
TableRow(
|
TableRow(
|
||||||
decoration: const BoxDecoration(
|
|
||||||
// color: Colors.grey[200],
|
|
||||||
),
|
|
||||||
children: [
|
children: [
|
||||||
TableCell(
|
TableCell(
|
||||||
verticalAlignment: TableCellVerticalAlignment.middle,
|
verticalAlignment: TableCellVerticalAlignment.middle,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(6.0),
|
padding: const EdgeInsets.all(6.0),
|
||||||
child: const Text(
|
child: Text(
|
||||||
"backup_controller_page_created",
|
"backup_controller_page_created",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 10.0,
|
fontSize: 10.0,
|
||||||
),
|
),
|
||||||
@@ -131,16 +128,14 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
TableRow(
|
TableRow(
|
||||||
decoration: const BoxDecoration(
|
|
||||||
// color: Colors.grey[100],
|
|
||||||
),
|
|
||||||
children: [
|
children: [
|
||||||
TableCell(
|
TableCell(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(6.0),
|
padding: const EdgeInsets.all(6.0),
|
||||||
child: const Text(
|
child: Text(
|
||||||
"backup_controller_page_id",
|
"backup_controller_page_id",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 10.0,
|
fontSize: 10.0,
|
||||||
),
|
),
|
||||||
@@ -181,8 +176,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
|
|||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
minHeight: 10.0,
|
minHeight: 10.0,
|
||||||
value: uploadProgress / 100.0,
|
value: uploadProgress / 100.0,
|
||||||
backgroundColor: Colors.grey,
|
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
|
||||||
color: context.primaryColor,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
@@ -214,8 +208,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
|
|||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
minHeight: 10.0,
|
minHeight: 10.0,
|
||||||
value: uploadProgress / 100.0,
|
value: uploadProgress / 100.0,
|
||||||
backgroundColor: Colors.grey,
|
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
|
||||||
color: context.primaryColor,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
|
|||||||
|
|
||||||
buildSettingButton() {
|
buildSettingButton() {
|
||||||
return buildActionButton(
|
return buildActionButton(
|
||||||
Icons.settings_rounded,
|
Icons.settings_outlined,
|
||||||
"profile_drawer_settings",
|
"profile_drawer_settings",
|
||||||
() => context.pushRoute(const SettingsRoute()),
|
() => context.pushRoute(const SettingsRoute()),
|
||||||
);
|
);
|
||||||
@@ -146,9 +146,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: context.isDarkTheme
|
color: context.colorScheme.surface,
|
||||||
? context.scaffoldBackgroundColor
|
|
||||||
: const Color.fromARGB(255, 225, 229, 240),
|
|
||||||
),
|
),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
minLeadingWidth: 50,
|
minLeadingWidth: 50,
|
||||||
@@ -171,10 +169,10 @@ class ImmichAppBarDialog extends HookConsumerWidget {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
minHeight: 5.0,
|
minHeight: 10.0,
|
||||||
value: percentage,
|
value: percentage,
|
||||||
backgroundColor: Colors.grey,
|
borderRadius:
|
||||||
color: theme.primaryColor,
|
const BorderRadius.all(Radius.circular(10.0)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
@@ -248,7 +246,6 @@ class ImmichAppBarDialog extends HookConsumerWidget {
|
|||||||
right: horizontalPadding,
|
right: horizontalPadding,
|
||||||
bottom: isHorizontal ? 20 : 100,
|
bottom: isHorizontal ? 20 : 100,
|
||||||
),
|
),
|
||||||
backgroundColor: theme.cardColor,
|
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
),
|
),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user