Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3760782d7f | |||
| a8a63b24d0 | |||
| ab0ed11778 | |||
| 5ec407b57c | |||
| fdf0b16fe3 | |||
| c924f6c27c | |||
| df45ef0e35 | |||
| 81c813a882 | |||
| b014162088 | |||
| 276101ee82 | |||
| 9837d60074 | |||
| 28b7443b92 | |||
| 5acdc958b6 | |||
| e384692025 | |||
| 54b276c984 | |||
| 7eb004bd00 | |||
| c2965c4408 | |||
| b749a68349 | |||
| 30aa2c9b82 | |||
| 9ed04588b8 | |||
| 7d320217b9 | |||
| efdf8bbca9 | |||
| 34c4fbf730 | |||
| ca775ab3e9 | |||
| 2dd5514043 | |||
| f33dbdfe9a | |||
| b1587a5dee | |||
| 501485d0b1 | |||
| ed7f857975 | |||
| d346985457 | |||
| a144a1bec3 | |||
| 9f318a9338 | |||
| 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 |
+12
-24
@@ -1,41 +1,29 @@
|
|||||||
changelog:
|
changelog:
|
||||||
categories:
|
categories:
|
||||||
- title: ⚠️ Breaking Changes
|
- title: 🚨 Breaking Changes
|
||||||
labels:
|
labels:
|
||||||
- breaking-change
|
- breaking-change
|
||||||
|
|
||||||
- title: 🗄️ Server
|
- title: 🔒 Security
|
||||||
labels:
|
labels:
|
||||||
- 🗄️server
|
- security
|
||||||
|
|
||||||
- title: 📱 Mobile
|
- title: 🚀 Features
|
||||||
labels:
|
labels:
|
||||||
- 📱mobile
|
- feature
|
||||||
|
|
||||||
- title: 🖥️ Web
|
- title: 🌟 Enhancements
|
||||||
labels:
|
labels:
|
||||||
- 🖥️web
|
- enhancement
|
||||||
|
|
||||||
- title: 🧠 Machine Learning
|
- title: 🐛 Bug fixes
|
||||||
labels:
|
labels:
|
||||||
- 🧠machine-learning
|
- bugfix
|
||||||
|
|
||||||
- title: ⚡ CLI
|
- title: 📚 Documentation
|
||||||
labels:
|
|
||||||
- cli
|
|
||||||
|
|
||||||
- title: 📓 Documentation
|
|
||||||
labels:
|
labels:
|
||||||
- documentation
|
- documentation
|
||||||
|
|
||||||
- title: 🔨 Maintenance
|
- title: 🌐 Translations
|
||||||
labels:
|
labels:
|
||||||
- deployment
|
- translation
|
||||||
- dependencies
|
|
||||||
- renovate
|
|
||||||
- maintenance
|
|
||||||
- tech-debt
|
|
||||||
|
|
||||||
- title: Other changes
|
|
||||||
labels:
|
|
||||||
- "*"
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
-1
@@ -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 ./
|
||||||
|
|||||||
@@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
Generated
+748
-804
File diff suppressed because it is too large
Load Diff
+12
-9
@@ -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.14",
|
||||||
"@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": "^5.0.0",
|
||||||
"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
|
||||||
@@ -75,7 +79,7 @@ services:
|
|||||||
container_name: immich_prometheus
|
container_name: immich_prometheus
|
||||||
ports:
|
ports:
|
||||||
- 9090:9090
|
- 9090:9090
|
||||||
image: prom/prometheus@sha256:f20d3127bf2876f4a1df76246fca576b41ddf1125ed1c546fbd8b16ea55117e6
|
image: prom/prometheus@sha256:cafe963e591c872d38f3ea41ff8eb22cee97917b7c97b5c0ccd43a419f11f613
|
||||||
volumes:
|
volumes:
|
||||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||||
- prometheus-data:/prometheus
|
- prometheus-data:/prometheus
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ DB_DATA_LOCATION=./postgres
|
|||||||
IMMICH_VERSION=release
|
IMMICH_VERSION=release
|
||||||
|
|
||||||
# Connection secret for postgres. You should change it to a random password
|
# Connection secret for postgres. You should change it to a random password
|
||||||
|
# Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
||||||
DB_PASSWORD=postgres
|
DB_PASSWORD=postgres
|
||||||
|
|
||||||
# The values below this line do not need to be changed
|
# The values below this line do not need to be changed
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
### Unit tests
|
### Unit tests
|
||||||
|
|
||||||
Unit are run by calling `npm run test` from the `server` directory.
|
Unit are run by calling `npm run test` from the `server/` directory.
|
||||||
|
You need to run `npm install` (in `server/`) before _once_.
|
||||||
|
|
||||||
### End to end tests
|
### End to end tests
|
||||||
|
|
||||||
@@ -14,6 +15,11 @@ The e2e tests can be run by first starting up a test production environment via:
|
|||||||
make e2e
|
make e2e
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Before you can run the tests, you need to run the following commands _once_:
|
||||||
|
|
||||||
|
- `npm install` (in `e2e/`)
|
||||||
|
- `make open-api` (in the project root `/`)
|
||||||
|
|
||||||
Once the test environment is running, the e2e tests can be run via:
|
Once the test environment is running, the e2e tests can be run via:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -1,8 +1,22 @@
|
|||||||
# Create Custom Map Styles for Immich Using Maptiler
|
# Custom Map Styles
|
||||||
|
|
||||||
You may decide that you'd like to modify the style document which is used to draw the maps in Immich. This can be done easily using Maptiler, if you do not want to write an entire JSON document by hand.
|
You may decide that you'd like to modify the style document which is used to
|
||||||
|
draw the maps in Immich. In addition to visual customization, this also allows
|
||||||
|
you to pick your own map tile provider instead of the default one. The default
|
||||||
|
`style.json` for [light theme](https://github.com/immich-app/immich/tree/main/server/resources/style-light.json)
|
||||||
|
and [dark theme](https://github.com/immich-app/immich/blob/main/server/resources/style-dark.json)
|
||||||
|
can be used as a basis for creating your own style.
|
||||||
|
|
||||||
## Steps
|
There are several sources for already-made `style.json` map themes, as well as
|
||||||
|
online generators you can use.
|
||||||
|
|
||||||
|
1. In **Immich**, navigate to **Administration --> Settings --> Map & GPS Settings** and expand the **Map Settings** subsection.
|
||||||
|
2. Paste the link to your JSON style in either the **Light Style** or **Dark Style**. (You can add different styles which will help make the map style more appropriate depending on whether you set **Immich** to Light or Dark mode.)
|
||||||
|
3. Save your selections. Reload the map, and enjoy your custom map style!
|
||||||
|
|
||||||
|
## Use Maptiler to build a custom style
|
||||||
|
|
||||||
|
Customizing the map style can be done easily using Maptiler, if you do not want to write an entire JSON document by hand.
|
||||||
|
|
||||||
1. Create a free account at https://cloud.maptiler.com
|
1. Create a free account at https://cloud.maptiler.com
|
||||||
2. Once logged in, you can either create a brand new map by clicking on **New Map**, selecting a starter map, and then clicking **Customize**, OR by selecting a **Standard Map** and customizing it from there.
|
2. Once logged in, you can either create a brand new map by clicking on **New Map**, selecting a starter map, and then clicking **Customize**, OR by selecting a **Standard Map** and customizing it from there.
|
||||||
@@ -11,6 +25,3 @@ You may decide that you'd like to modify the style document which is used to dra
|
|||||||
5. Next, **Publish** your style using the **Publish** button at the top right. This will deploy it to production, which means it is able to be exposed over the Internet. Maptiler will present an interactive side-by-side map with the original and your changes prior to publication.<br/>
|
5. Next, **Publish** your style using the **Publish** button at the top right. This will deploy it to production, which means it is able to be exposed over the Internet. Maptiler will present an interactive side-by-side map with the original and your changes prior to publication.<br/>
|
||||||
6. Maptiler will warn you that changing the map will change it across all apps using the map. Since no apps are using the map yet, this is okay.
|
6. Maptiler will warn you that changing the map will change it across all apps using the map. Since no apps are using the map yet, this is okay.
|
||||||
7. Clicking on the name of your new map at the top left will bring you to the item's **details** page. From here, copy the link to the JSON style under **Use vector style**. This link will automatically contain your personal API key to Maptiler.
|
7. Clicking on the name of your new map at the top left will bring you to the item's **details** page. From here, copy the link to the JSON style under **Use vector style**. This link will automatically contain your personal API key to Maptiler.
|
||||||
8. In **Immich**, navigate to **Administration --> Settings --> Map & GPS Settings** and expand the **Map Settings** subsection.
|
|
||||||
9. Paste the link to your JSON style in either the **Light Style** or **Dark Style**. (You can add different styles which will help make the map style more appropriate depending on whether you set **Immich** to Light or Dark mode.
|
|
||||||
10. Save your selections. Reload the map, and enjoy your custom map style!
|
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ Optionally, you can enable hardware acceleration for machine learning and transc
|
|||||||
|
|
||||||
- Populate custom database information if necessary.
|
- Populate custom database information if necessary.
|
||||||
- Populate `UPLOAD_LOCATION` with your preferred location for storing backup assets.
|
- Populate `UPLOAD_LOCATION` with your preferred location for storing backup assets.
|
||||||
- Consider changing `DB_PASSWORD` to something randomly generated
|
- Consider changing `DB_PASSWORD` to a custom value. Postgres is not publically exposed, so this password is only used for local authentication.
|
||||||
|
To avoid issues with Docker parsing this value, it is best to use only the characters `A-Za-z0-9`.
|
||||||
|
|
||||||
### Step 3 - Start the containers
|
### Step 3 - Start the containers
|
||||||
|
|
||||||
|
|||||||
@@ -38,21 +38,23 @@ Regardless of filesystem, it is not recommended to use a network share for your
|
|||||||
|
|
||||||
## General
|
## General
|
||||||
|
|
||||||
| Variable | Description | Default | Containers | Workers |
|
| Variable | Description | Default | Containers | Workers |
|
||||||
| :---------------------------------- | :-------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
|
| :---------------------------------- | :---------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
|
||||||
| `TZ` | Timezone | | server | microservices |
|
| `TZ` | Timezone | | server | microservices |
|
||||||
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
|
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
|
||||||
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
|
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
|
||||||
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload`<sup>\*1</sup> | server | api, microservices |
|
| `IMMICH_MEDIA_LOCATION` | Media Location inside the container ⚠️**You probably shouldn't set this**<sup>\*1</sup>⚠️ | `./upload`<sup>\*2</sup> | server | api, microservices |
|
||||||
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
|
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
|
||||||
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
|
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
|
||||||
| `CPU_CORES` | Amount of cores available to the immich server | auto-detected cpu core count | server | |
|
| `CPU_CORES` | Amount of cores available to the immich server | auto-detected cpu core count | server | |
|
||||||
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
|
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
|
||||||
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
|
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
|
||||||
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
|
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
|
||||||
| `IMMICH_TRUSTED_PROXIES` | List of comma separated IPs set as trusted proxies | | server | api |
|
| `IMMICH_TRUSTED_PROXIES` | List of comma separated IPs set as trusted proxies | | server | api |
|
||||||
|
|
||||||
\*1: With the default `WORKDIR` of `/usr/src/app`, this path will resolve to `/usr/src/app/upload`.
|
\*1: This path is where the Immich code looks for the files, which is internal to the docker container. Setting it to a path on your host will certainly break things, you should use the `UPLOAD_LOCATION` variable instead.
|
||||||
|
|
||||||
|
\*2: With the default `WORKDIR` of `/usr/src/app`, this path will resolve to `/usr/src/app/upload`.
|
||||||
It only need to be set if the Immich deployment method is changing.
|
It only need to be set if the Immich deployment method is changing.
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
|
|||||||
@@ -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/',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
Generated
+60
-48
@@ -4237,9 +4237,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/autoprefixer": {
|
"node_modules/autoprefixer": {
|
||||||
"version": "10.4.19",
|
"version": "10.4.20",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
|
||||||
"integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
|
"integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -4254,12 +4254,13 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"browserslist": "^4.23.0",
|
"browserslist": "^4.23.3",
|
||||||
"caniuse-lite": "^1.0.30001599",
|
"caniuse-lite": "^1.0.30001646",
|
||||||
"fraction.js": "^4.3.7",
|
"fraction.js": "^4.3.7",
|
||||||
"normalize-range": "^0.1.2",
|
"normalize-range": "^0.1.2",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.1",
|
||||||
"postcss-value-parser": "^4.2.0"
|
"postcss-value-parser": "^4.2.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -4531,9 +4532,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/browserslist": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.23.0",
|
"version": "4.23.3",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
|
||||||
"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
|
"integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -4548,11 +4549,12 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001587",
|
"caniuse-lite": "^1.0.30001646",
|
||||||
"electron-to-chromium": "^1.4.668",
|
"electron-to-chromium": "^1.5.4",
|
||||||
"node-releases": "^2.0.14",
|
"node-releases": "^2.0.18",
|
||||||
"update-browserslist-db": "^1.0.13"
|
"update-browserslist-db": "^1.1.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"browserslist": "cli.js"
|
"browserslist": "cli.js"
|
||||||
@@ -4699,9 +4701,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001614",
|
"version": "1.0.30001651",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001614.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
|
||||||
"integrity": "sha512-jmZQ1VpmlRwHgdP1/uiKzgiAuGOfLEJsYFP4+GBou/QQ4U6IOJCB4NP1c+1p9RGLpwObcT94jA5/uO+F1vBbog==",
|
"integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -4715,7 +4717,8 @@
|
|||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
"node_modules/ccount": {
|
"node_modules/ccount": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
@@ -6342,9 +6345,10 @@
|
|||||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.4.751",
|
"version": "1.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.751.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.6.tgz",
|
||||||
"integrity": "sha512-2DEPi++qa89SMGRhufWTiLmzqyuGmNF3SK4+PQetW1JKiZdEpF4XQonJXJCzyuYSA6mauiMhbyVhqYAP45Hvfw=="
|
"integrity": "sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw==",
|
||||||
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "9.2.2",
|
"version": "9.2.2",
|
||||||
@@ -11958,9 +11962,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.14",
|
"version": "2.0.18",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||||
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
|
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nopt": {
|
"node_modules/nopt": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
@@ -12754,9 +12759,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 +13605,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 +13752,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 +16020,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.4",
|
"version": "3.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.9.tgz",
|
||||||
"integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==",
|
"integrity": "sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
@@ -16376,9 +16382,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",
|
||||||
@@ -16607,9 +16613,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.0.13",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
|
||||||
"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
|
"integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -16624,9 +16630,10 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"escalade": "^3.1.1",
|
"escalade": "^3.1.2",
|
||||||
"picocolors": "^1.0.0"
|
"picocolors": "^1.0.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"update-browserslist-db": "cli.js"
|
"update-browserslist-db": "cli.js"
|
||||||
@@ -16714,12 +16721,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 +16794,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',
|
||||||
|
|||||||
@@ -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',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
Generated
+943
-801
File diff suppressed because it is too large
Load Diff
+10
-7
@@ -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.14",
|
||||||
"@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"
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const makeUploadDto = (options?: { omit: string }): Record<string, any> => {
|
|||||||
const TEN_TIMES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
const TEN_TIMES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||||
|
|
||||||
const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
|
const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
|
||||||
|
const ratingAssetFilepath = `${testAssetDir}/metadata/rating/mongolels.jpg`;
|
||||||
|
|
||||||
const readTags = async (bytes: Buffer, filename: string) => {
|
const readTags = async (bytes: Buffer, filename: string) => {
|
||||||
const filepath = join(tempDir, filename);
|
const filepath = join(tempDir, filename);
|
||||||
@@ -72,6 +73,7 @@ describe('/asset', () => {
|
|||||||
let user2Assets: AssetMediaResponseDto[];
|
let user2Assets: AssetMediaResponseDto[];
|
||||||
let stackAssets: AssetMediaResponseDto[];
|
let stackAssets: AssetMediaResponseDto[];
|
||||||
let locationAsset: AssetMediaResponseDto;
|
let locationAsset: AssetMediaResponseDto;
|
||||||
|
let ratingAsset: AssetMediaResponseDto;
|
||||||
|
|
||||||
const setupTests = async () => {
|
const setupTests = async () => {
|
||||||
await utils.resetDatabase();
|
await utils.resetDatabase();
|
||||||
@@ -99,6 +101,16 @@ describe('/asset', () => {
|
|||||||
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: locationAsset.id });
|
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: locationAsset.id });
|
||||||
|
|
||||||
|
// asset rating
|
||||||
|
ratingAsset = await utils.createAsset(admin.accessToken, {
|
||||||
|
assetData: {
|
||||||
|
filename: 'mongolels.jpg',
|
||||||
|
bytes: await readFile(ratingAssetFilepath),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: ratingAsset.id });
|
||||||
|
|
||||||
user1Assets = await Promise.all([
|
user1Assets = await Promise.all([
|
||||||
utils.createAsset(user1.accessToken),
|
utils.createAsset(user1.accessToken),
|
||||||
utils.createAsset(user1.accessToken),
|
utils.createAsset(user1.accessToken),
|
||||||
@@ -214,6 +226,22 @@ describe('/asset', () => {
|
|||||||
expect(body).toMatchObject({ id: user1Assets[0].id });
|
expect(body).toMatchObject({ id: user1Assets[0].id });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should get the asset rating', async () => {
|
||||||
|
await utils.waitForWebsocketEvent({
|
||||||
|
event: 'assetUpload',
|
||||||
|
id: ratingAsset.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/assets/${ratingAsset.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
id: ratingAsset.id,
|
||||||
|
exifInfo: expect.objectContaining({ rating: 3 }),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should work with a shared link', async () => {
|
it('should work with a shared link', async () => {
|
||||||
const sharedLink = await utils.createSharedLink(user1.accessToken, {
|
const sharedLink = await utils.createSharedLink(user1.accessToken, {
|
||||||
type: SharedLinkType.Individual,
|
type: SharedLinkType.Individual,
|
||||||
@@ -575,6 +603,31 @@ describe('/asset', () => {
|
|||||||
expect(status).toEqual(200);
|
expect(status).toEqual(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set the rating', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/assets/${user1Assets[0].id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ rating: 2 });
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
id: user1Assets[0].id,
|
||||||
|
exifInfo: expect.objectContaining({
|
||||||
|
rating: 2,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid rating', async () => {
|
||||||
|
for (const test of [{ rating: 7 }, { rating: 3.5 }, { rating: null }]) {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/assets/${user1Assets[0].id}`)
|
||||||
|
.send(test)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('should return tagged people', async () => {
|
it('should return tagged people', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.put(`/assets/${user1Assets[0].id}`)
|
.put(`/assets/${user1Assets[0].id}`)
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
+4
-4
@@ -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,
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import { AssetMediaResponseDto, LoginResponseDto, SharedLinkType } from '@immich/sdk';
|
import { AssetMediaResponseDto, LoginResponseDto, SharedLinkType } from '@immich/sdk';
|
||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
import type { Socket } from 'socket.io-client';
|
||||||
import { utils } from 'src/utils';
|
import { utils } from 'src/utils';
|
||||||
|
|
||||||
test.describe('Detail Panel', () => {
|
test.describe('Detail Panel', () => {
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
let asset: AssetMediaResponseDto;
|
let asset: AssetMediaResponseDto;
|
||||||
|
let websocket: Socket;
|
||||||
|
|
||||||
test.beforeAll(async () => {
|
test.beforeAll(async () => {
|
||||||
utils.initSdk();
|
utils.initSdk();
|
||||||
await utils.resetDatabase();
|
await utils.resetDatabase();
|
||||||
admin = await utils.adminSetup();
|
admin = await utils.adminSetup();
|
||||||
asset = await utils.createAsset(admin.accessToken);
|
asset = await utils.createAsset(admin.accessToken);
|
||||||
|
websocket = await utils.connectWebsocket(admin.accessToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(() => {
|
||||||
|
utils.disconnectWebsocket(websocket);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can be opened for shared links', async ({ page }) => {
|
test('can be opened for shared links', async ({ page }) => {
|
||||||
@@ -57,4 +64,23 @@ test.describe('Detail Panel', () => {
|
|||||||
await expect(textarea).toBeVisible();
|
await expect(textarea).toBeVisible();
|
||||||
await expect(textarea).not.toBeDisabled();
|
await expect(textarea).not.toBeDisabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('description changes are visible after reopening', async ({ context, page }) => {
|
||||||
|
await utils.setAuthCookies(context, admin.accessToken);
|
||||||
|
await page.goto(`/photos/${asset.id}`);
|
||||||
|
await page.waitForSelector('#immich-asset-viewer');
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Info' }).click();
|
||||||
|
const textarea = page.getByRole('textbox', { name: 'Add a description' });
|
||||||
|
await textarea.fill('new description');
|
||||||
|
await expect(textarea).toHaveValue('new description');
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Info' }).click();
|
||||||
|
await expect(textarea).not.toBeVisible();
|
||||||
|
await page.getByRole('button', { name: 'Info' }).click();
|
||||||
|
await expect(textarea).toBeVisible();
|
||||||
|
|
||||||
|
await utils.waitForWebsocketEvent({ event: 'assetUpdate', id: asset.id });
|
||||||
|
await expect(textarea).toHaveValue('new description');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -33,6 +33,7 @@ test.describe('Registration', () => {
|
|||||||
// onboarding
|
// onboarding
|
||||||
await expect(page).toHaveURL('/auth/onboarding');
|
await expect(page).toHaveURL('/auth/onboarding');
|
||||||
await page.getByRole('button', { name: 'Theme' }).click();
|
await page.getByRole('button', { name: 'Theme' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Privacy' }).click();
|
||||||
await page.getByRole('button', { name: 'Storage Template' }).click();
|
await page.getByRole('button', { name: 'Storage Template' }).click();
|
||||||
await page.getByRole('button', { name: 'Done' }).click();
|
await page.getByRole('button', { name: 'Done' }).click();
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
+1
-1
Submodule e2e/test-assets updated: 898069e47f...39f25a96f1
@@ -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,
|
||||||
|
|||||||
+10
-10
@@ -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
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
|||||||
from socket import socket
|
from socket import socket
|
||||||
|
|
||||||
from gunicorn.arbiter import Arbiter
|
from gunicorn.arbiter import Arbiter
|
||||||
from pydantic import BaseModel, BaseSettings
|
from pydantic.v1 import BaseModel, BaseSettings
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.logging import RichHandler
|
from rich.logging import RichHandler
|
||||||
from uvicorn import Server
|
from uvicorn import Server
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from fastapi import Depends, FastAPI, File, Form, HTTPException
|
|||||||
from fastapi.responses import ORJSONResponse
|
from fastapi.responses import ORJSONResponse
|
||||||
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile
|
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile
|
||||||
from PIL.Image import Image
|
from PIL.Image import Image
|
||||||
from pydantic import ValidationError
|
from pydantic.v1 import ValidationError
|
||||||
from starlette.formparsers import MultiPartParser
|
from starlette.formparsers import MultiPartParser
|
||||||
|
|
||||||
from app.models import get_model_deps
|
from app.models import get_model_deps
|
||||||
|
|||||||
@@ -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",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import Any, Literal, Protocol, TypedDict, TypeGuard, TypeVar
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import numpy.typing as npt
|
import numpy.typing as npt
|
||||||
from pydantic import BaseModel
|
from pydantic.v1 import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class StrEnum(str, Enum):
|
class StrEnum(str, Enum):
|
||||||
|
|||||||
@@ -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 \
|
||||||
|
|||||||
+3617
-2809
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)
|
||||||
|
|||||||
Generated
+207
-120
@@ -40,6 +40,17 @@ develop = ["imgaug (>=0.4.0)", "pytest"]
|
|||||||
imgaug = ["imgaug (>=0.4.0)"]
|
imgaug = ["imgaug (>=0.4.0)"]
|
||||||
tests = ["pytest"]
|
tests = ["pytest"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.7.0"
|
||||||
|
description = "Reusable constraint types to use with typing.Annotated"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
|
||||||
|
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
version = "4.2.0"
|
version = "4.2.0"
|
||||||
@@ -1236,13 +1247,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 +1541,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 +1559,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.32.2", markers = "python_full_version > \"3.11.0\""},
|
||||||
{version = ">=2.26.0", markers = "python_version <= \"3.11\""},
|
{version = ">=2.26.0", 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 +1805,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]
|
||||||
@@ -2117,6 +2128,8 @@ files = [
|
|||||||
{file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34"},
|
{file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34"},
|
||||||
{file = "orjson-3.10.6-cp312-none-win32.whl", hash = "sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5"},
|
{file = "orjson-3.10.6-cp312-none-win32.whl", hash = "sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5"},
|
||||||
{file = "orjson-3.10.6-cp312-none-win_amd64.whl", hash = "sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc"},
|
{file = "orjson-3.10.6-cp312-none-win_amd64.whl", hash = "sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc"},
|
||||||
|
{file = "orjson-3.10.6-cp313-none-win32.whl", hash = "sha256:efdf2c5cde290ae6b83095f03119bdc00303d7a03b42b16c54517baa3c4ca3d0"},
|
||||||
|
{file = "orjson-3.10.6-cp313-none-win_amd64.whl", hash = "sha256:8e190fe7888e2e4392f52cafb9626113ba135ef53aacc65cd13109eb9746c43e"},
|
||||||
{file = "orjson-3.10.6-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:66680eae4c4e7fc193d91cfc1353ad6d01b4801ae9b5314f17e11ba55e934183"},
|
{file = "orjson-3.10.6-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:66680eae4c4e7fc193d91cfc1353ad6d01b4801ae9b5314f17e11ba55e934183"},
|
||||||
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caff75b425db5ef8e8f23af93c80f072f97b4fb3afd4af44482905c9f588da28"},
|
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caff75b425db5ef8e8f23af93c80f072f97b4fb3afd4af44482905c9f588da28"},
|
||||||
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3722fddb821b6036fd2a3c814f6bd9b57a89dc6337b9924ecd614ebce3271394"},
|
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3722fddb821b6036fd2a3c814f6bd9b57a89dc6337b9924ecd614ebce3271394"},
|
||||||
@@ -2367,62 +2380,126 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "1.10.17"
|
version = "2.8.2"
|
||||||
description = "Data validation and settings management using python type hints"
|
description = "Data validation using Python type hints"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "pydantic-1.10.17-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fa51175313cc30097660b10eec8ca55ed08bfa07acbfe02f7a42f6c242e9a4b"},
|
{file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"},
|
||||||
{file = "pydantic-1.10.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7e8988bb16988890c985bd2093df9dd731bfb9d5e0860db054c23034fab8f7a"},
|
{file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"},
|
||||||
{file = "pydantic-1.10.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:371dcf1831f87c9e217e2b6a0c66842879a14873114ebb9d0861ab22e3b5bb1e"},
|
|
||||||
{file = "pydantic-1.10.17-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4866a1579c0c3ca2c40575398a24d805d4db6cb353ee74df75ddeee3c657f9a7"},
|
|
||||||
{file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:543da3c6914795b37785703ffc74ba4d660418620cc273490d42c53949eeeca6"},
|
|
||||||
{file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7623b59876f49e61c2e283551cc3647616d2fbdc0b4d36d3d638aae8547ea681"},
|
|
||||||
{file = "pydantic-1.10.17-cp310-cp310-win_amd64.whl", hash = "sha256:409b2b36d7d7d19cd8310b97a4ce6b1755ef8bd45b9a2ec5ec2b124db0a0d8f3"},
|
|
||||||
{file = "pydantic-1.10.17-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fa43f362b46741df8f201bf3e7dff3569fa92069bcc7b4a740dea3602e27ab7a"},
|
|
||||||
{file = "pydantic-1.10.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a72d2a5ff86a3075ed81ca031eac86923d44bc5d42e719d585a8eb547bf0c9b"},
|
|
||||||
{file = "pydantic-1.10.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4ad32aed3bf5eea5ca5decc3d1bbc3d0ec5d4fbcd72a03cdad849458decbc63"},
|
|
||||||
{file = "pydantic-1.10.17-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb4e741782e236ee7dc1fb11ad94dc56aabaf02d21df0e79e0c21fe07c95741"},
|
|
||||||
{file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d2f89a719411cb234105735a520b7c077158a81e0fe1cb05a79c01fc5eb59d3c"},
|
|
||||||
{file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db3b48d9283d80a314f7a682f7acae8422386de659fffaba454b77a083c3937d"},
|
|
||||||
{file = "pydantic-1.10.17-cp311-cp311-win_amd64.whl", hash = "sha256:9c803a5113cfab7bbb912f75faa4fc1e4acff43e452c82560349fff64f852e1b"},
|
|
||||||
{file = "pydantic-1.10.17-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:820ae12a390c9cbb26bb44913c87fa2ff431a029a785642c1ff11fed0a095fcb"},
|
|
||||||
{file = "pydantic-1.10.17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1e51d1af306641b7d1574d6d3307eaa10a4991542ca324f0feb134fee259815"},
|
|
||||||
{file = "pydantic-1.10.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e53fb834aae96e7b0dadd6e92c66e7dd9cdf08965340ed04c16813102a47fab"},
|
|
||||||
{file = "pydantic-1.10.17-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e2495309b1266e81d259a570dd199916ff34f7f51f1b549a0d37a6d9b17b4dc"},
|
|
||||||
{file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:098ad8de840c92ea586bf8efd9e2e90c6339d33ab5c1cfbb85be66e4ecf8213f"},
|
|
||||||
{file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:525bbef620dac93c430d5d6bdbc91bdb5521698d434adf4434a7ef6ffd5c4b7f"},
|
|
||||||
{file = "pydantic-1.10.17-cp312-cp312-win_amd64.whl", hash = "sha256:6654028d1144df451e1da69a670083c27117d493f16cf83da81e1e50edce72ad"},
|
|
||||||
{file = "pydantic-1.10.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c87cedb4680d1614f1d59d13fea353faf3afd41ba5c906a266f3f2e8c245d655"},
|
|
||||||
{file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11289fa895bcbc8f18704efa1d8020bb9a86314da435348f59745473eb042e6b"},
|
|
||||||
{file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94833612d6fd18b57c359a127cbfd932d9150c1b72fea7c86ab58c2a77edd7c7"},
|
|
||||||
{file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d4ecb515fa7cb0e46e163ecd9d52f9147ba57bc3633dca0e586cdb7a232db9e3"},
|
|
||||||
{file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7017971ffa7fd7808146880aa41b266e06c1e6e12261768a28b8b41ba55c8076"},
|
|
||||||
{file = "pydantic-1.10.17-cp37-cp37m-win_amd64.whl", hash = "sha256:e840e6b2026920fc3f250ea8ebfdedf6ea7a25b77bf04c6576178e681942ae0f"},
|
|
||||||
{file = "pydantic-1.10.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bfbb18b616abc4df70591b8c1ff1b3eabd234ddcddb86b7cac82657ab9017e33"},
|
|
||||||
{file = "pydantic-1.10.17-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebb249096d873593e014535ab07145498957091aa6ae92759a32d40cb9998e2e"},
|
|
||||||
{file = "pydantic-1.10.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c209af63ccd7b22fba94b9024e8b7fd07feffee0001efae50dd99316b27768"},
|
|
||||||
{file = "pydantic-1.10.17-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b40c9e13a0b61583e5599e7950490c700297b4a375b55b2b592774332798b7"},
|
|
||||||
{file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c31d281c7485223caf6474fc2b7cf21456289dbaa31401844069b77160cab9c7"},
|
|
||||||
{file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae5184e99a060a5c80010a2d53c99aee76a3b0ad683d493e5f0620b5d86eeb75"},
|
|
||||||
{file = "pydantic-1.10.17-cp38-cp38-win_amd64.whl", hash = "sha256:ad1e33dc6b9787a6f0f3fd132859aa75626528b49cc1f9e429cdacb2608ad5f0"},
|
|
||||||
{file = "pydantic-1.10.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e17c0ee7192e54a10943f245dc79e36d9fe282418ea05b886e1c666063a7b54"},
|
|
||||||
{file = "pydantic-1.10.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cafb9c938f61d1b182dfc7d44a7021326547b7b9cf695db5b68ec7b590214773"},
|
|
||||||
{file = "pydantic-1.10.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95ef534e3c22e5abbdbdd6f66b6ea9dac3ca3e34c5c632894f8625d13d084cbe"},
|
|
||||||
{file = "pydantic-1.10.17-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d96b8799ae3d782df7ec9615cb59fc32c32e1ed6afa1b231b0595f6516e8ab"},
|
|
||||||
{file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ab2f976336808fd5d539fdc26eb51f9aafc1f4b638e212ef6b6f05e753c8011d"},
|
|
||||||
{file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8ad363330557beac73159acfbeed220d5f1bfcd6b930302a987a375e02f74fd"},
|
|
||||||
{file = "pydantic-1.10.17-cp39-cp39-win_amd64.whl", hash = "sha256:48db882e48575ce4b39659558b2f9f37c25b8d348e37a2b4e32971dd5a7d6227"},
|
|
||||||
{file = "pydantic-1.10.17-py3-none-any.whl", hash = "sha256:e41b5b973e5c64f674b3b4720286ded184dcc26a691dd55f34391c62c6934688"},
|
|
||||||
{file = "pydantic-1.10.17.tar.gz", hash = "sha256:f434160fb14b353caf634149baaf847206406471ba70e64657c1e8330277a991"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
typing-extensions = ">=4.2.0"
|
annotated-types = ">=0.4.0"
|
||||||
|
pydantic-core = "2.20.1"
|
||||||
|
typing-extensions = [
|
||||||
|
{version = ">=4.12.2", markers = "python_version >= \"3.13\""},
|
||||||
|
{version = ">=4.6.1", markers = "python_version < \"3.13\""},
|
||||||
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dotenv = ["python-dotenv (>=0.10.4)"]
|
email = ["email-validator (>=2.0.0)"]
|
||||||
email = ["email-validator (>=1.0.3)"]
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.20.1"
|
||||||
|
description = "Core functionality for Pydantic validation and serialization"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"},
|
||||||
|
{file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
@@ -2466,13 +2543,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 +2557,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 +2904,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 +3068,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"
|
||||||
@@ -3245,6 +3321,17 @@ files = [
|
|||||||
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
|
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.12.2"
|
||||||
|
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||||
|
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "urllib3"
|
name = "urllib3"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@@ -3263,13 +3350,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 +3688,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 = "187485f19267f2d0a01e38fc0c1f8911c07a29aee11080179a96a127abb9c11b"
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ opencv-python-headless = ">=4.7.0.72,<5.0"
|
|||||||
pillow = ">=9.5.0,<11.0"
|
pillow = ">=9.5.0,<11.0"
|
||||||
fastapi-slim = ">=0.95.2,<1.0"
|
fastapi-slim = ">=0.95.2,<1.0"
|
||||||
uvicorn = {extras = ["standard"], version = ">=0.22.0,<1.0"}
|
uvicorn = {extras = ["standard"], version = ">=0.22.0,<1.0"}
|
||||||
pydantic = "^1.10.8"
|
pydantic = "^2.8.2"
|
||||||
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"
|
||||||
|
|||||||
@@ -201,7 +201,7 @@
|
|||||||
"delete_shared_link_dialog_title": "Delete Shared Link",
|
"delete_shared_link_dialog_title": "Delete Shared Link",
|
||||||
"description_input_hint_text": "Add description...",
|
"description_input_hint_text": "Add description...",
|
||||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||||
"edit_date_time_dialog_date_time": "Date and Time",
|
"edit_date_time_dialog_date_time": "Edit date and time",
|
||||||
"edit_date_time_dialog_timezone": "Timezone",
|
"edit_date_time_dialog_timezone": "Timezone",
|
||||||
"edit_location_dialog_title": "Location",
|
"edit_location_dialog_title": "Location",
|
||||||
"exif_bottom_sheet_description": "Add Description...",
|
"exif_bottom_sheet_description": "Add Description...",
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
|
extensions:
|
||||||
+17
-22
@@ -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]!;
|
||||||
|
}
|
||||||
|
|||||||
@@ -170,6 +170,30 @@ class ExifInfo {
|
|||||||
state.hashCode ^
|
state.hashCode ^
|
||||||
country.hashCode ^
|
country.hashCode ^
|
||||||
description.hashCode;
|
description.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
id: $id,
|
||||||
|
fileSize: $fileSize,
|
||||||
|
dateTimeOriginal: $dateTimeOriginal,
|
||||||
|
timeZone: $timeZone,
|
||||||
|
make: $make,
|
||||||
|
model: $model,
|
||||||
|
lens: $lens,
|
||||||
|
f: $f,
|
||||||
|
mm: $mm,
|
||||||
|
iso: $iso,
|
||||||
|
exposureSeconds: $exposureSeconds,
|
||||||
|
lat: $lat,
|
||||||
|
long: $long,
|
||||||
|
city: $city,
|
||||||
|
state: $state,
|
||||||
|
country: $country,
|
||||||
|
description: $description,
|
||||||
|
}""";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double? _exposureTimeToSeconds(String? s) {
|
double? _exposureTimeToSeconds(String? s) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
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 +20,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 {
|
||||||
@@ -27,6 +31,9 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
BackUpState backupState = ref.watch(backupProvider);
|
BackUpState backupState = ref.watch(backupProvider);
|
||||||
final hasAnyAlbum = backupState.selectedBackupAlbums.isNotEmpty;
|
final hasAnyAlbum = backupState.selectedBackupAlbums.isNotEmpty;
|
||||||
final didGetBackupInfo = useState(false);
|
final didGetBackupInfo = useState(false);
|
||||||
|
final isScreenDarkened = useState(false);
|
||||||
|
final darkenScreenTimer = useRef<Timer?>(null);
|
||||||
|
|
||||||
bool hasExclusiveAccess =
|
bool hasExclusiveAccess =
|
||||||
backupState.backupProgress != BackUpProgressEnum.inBackground;
|
backupState.backupProgress != BackUpProgressEnum.inBackground;
|
||||||
bool shouldBackup = backupState.allUniqueAssets.length -
|
bool shouldBackup = backupState.allUniqueAssets.length -
|
||||||
@@ -36,6 +43,25 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
? false
|
? false
|
||||||
: true;
|
: true;
|
||||||
|
|
||||||
|
void startScreenDarkenTimer() {
|
||||||
|
darkenScreenTimer.value = Timer(const Duration(seconds: 30), () {
|
||||||
|
isScreenDarkened.value = true;
|
||||||
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopScreenDarkenTimer() {
|
||||||
|
isScreenDarkened.value = false;
|
||||||
|
darkenScreenTimer.value?.cancel();
|
||||||
|
SystemChrome.setEnabledSystemUIMode(
|
||||||
|
SystemUiMode.manual,
|
||||||
|
overlays: [
|
||||||
|
SystemUiOverlay.top,
|
||||||
|
SystemUiOverlay.bottom,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
// Update the background settings information just to make sure we
|
// Update the background settings information just to make sure we
|
||||||
@@ -48,7 +74,14 @@ 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();
|
||||||
|
darkenScreenTimer.value?.cancel();
|
||||||
|
isScreenDarkened.value = false;
|
||||||
|
};
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@@ -65,6 +98,19 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
[backupState.backupProgress],
|
[backupState.backupProgress],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() {
|
||||||
|
if (backupState.backupProgress == BackUpProgressEnum.inProgress) {
|
||||||
|
startScreenDarkenTimer();
|
||||||
|
} else {
|
||||||
|
stopScreenDarkenTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
[backupState.backupProgress],
|
||||||
|
);
|
||||||
|
|
||||||
Widget buildSelectedAlbumName() {
|
Widget buildSelectedAlbumName() {
|
||||||
var text = "backup_controller_page_backup_selected".tr();
|
var text = "backup_controller_page_backup_selected".tr();
|
||||||
var albums = ref.watch(backupProvider).selectedBackupAlbums;
|
var albums = ref.watch(backupProvider).selectedBackupAlbums;
|
||||||
@@ -130,9 +176,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 +195,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(),
|
||||||
@@ -251,72 +297,102 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return GestureDetector(
|
||||||
appBar: AppBar(
|
onTap: () {
|
||||||
elevation: 0,
|
if (isScreenDarkened.value) {
|
||||||
title: const Text(
|
stopScreenDarkenTimer();
|
||||||
"backup_controller_page_backup",
|
}
|
||||||
).tr(),
|
if (backupState.backupProgress == BackUpProgressEnum.inProgress) {
|
||||||
leading: IconButton(
|
startScreenDarkenTimer();
|
||||||
onPressed: () {
|
}
|
||||||
ref.watch(websocketProvider.notifier).listenUploadEvent();
|
},
|
||||||
context.maybePop(true);
|
child: AnimatedOpacity(
|
||||||
},
|
opacity: isScreenDarkened.value ? 0.1 : 1.0,
|
||||||
splashRadius: 24,
|
duration: const Duration(seconds: 1),
|
||||||
icon: const Icon(
|
child: Scaffold(
|
||||||
Icons.arrow_back_ios_rounded,
|
appBar: AppBar(
|
||||||
),
|
elevation: 0,
|
||||||
),
|
title: const Text(
|
||||||
actions: [
|
"backup_controller_page_backup",
|
||||||
Padding(
|
).tr(),
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
leading: IconButton(
|
||||||
child: IconButton(
|
onPressed: () {
|
||||||
onPressed: () => context.pushRoute(const BackupOptionsRoute()),
|
ref.watch(websocketProvider.notifier).listenUploadEvent();
|
||||||
|
context.maybePop(true);
|
||||||
|
},
|
||||||
splashRadius: 24,
|
splashRadius: 24,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.settings_outlined,
|
Icons.arrow_back_ios_rounded,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
actions: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () =>
|
||||||
|
context.pushRoute(const BackupOptionsRoute()),
|
||||||
|
splashRadius: 24,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.settings_outlined,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.only(left: 16.0, right: 16, bottom: 32),
|
||||||
|
child: ListView(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: hasAnyAlbum
|
||||||
|
? [
|
||||||
|
buildFolderSelectionTile(),
|
||||||
|
BackupInfoCard(
|
||||||
|
title: "backup_controller_page_total".tr(),
|
||||||
|
subtitle: "backup_controller_page_total_sub".tr(),
|
||||||
|
info: ref
|
||||||
|
.watch(backupProvider)
|
||||||
|
.availableAlbums
|
||||||
|
.isEmpty
|
||||||
|
? "..."
|
||||||
|
: "${backupState.allUniqueAssets.length}",
|
||||||
|
),
|
||||||
|
BackupInfoCard(
|
||||||
|
title: "backup_controller_page_backup".tr(),
|
||||||
|
subtitle: "backup_controller_page_backup_sub".tr(),
|
||||||
|
info: ref
|
||||||
|
.watch(backupProvider)
|
||||||
|
.availableAlbums
|
||||||
|
.isEmpty
|
||||||
|
? "..."
|
||||||
|
: "${backupState.selectedAlbumsBackupAssetsIds.length}",
|
||||||
|
),
|
||||||
|
BackupInfoCard(
|
||||||
|
title: "backup_controller_page_remainder".tr(),
|
||||||
|
subtitle:
|
||||||
|
"backup_controller_page_remainder_sub".tr(),
|
||||||
|
info: ref
|
||||||
|
.watch(backupProvider)
|
||||||
|
.availableAlbums
|
||||||
|
.isEmpty
|
||||||
|
? "..."
|
||||||
|
: "${max(0, backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length)}",
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
const CurrentUploadingAssetInfoBox(),
|
||||||
|
if (!hasExclusiveAccess) buildBackgroundBackupInfo(),
|
||||||
|
buildBackupButton(),
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
buildFolderSelectionTile(),
|
||||||
|
if (!didGetBackupInfo.value) buildLoadingIndicator(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 32),
|
|
||||||
child: ListView(
|
|
||||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: hasAnyAlbum
|
|
||||||
? [
|
|
||||||
buildFolderSelectionTile(),
|
|
||||||
BackupInfoCard(
|
|
||||||
title: "backup_controller_page_total".tr(),
|
|
||||||
subtitle: "backup_controller_page_total_sub".tr(),
|
|
||||||
info: ref.watch(backupProvider).availableAlbums.isEmpty
|
|
||||||
? "..."
|
|
||||||
: "${backupState.allUniqueAssets.length}",
|
|
||||||
),
|
|
||||||
BackupInfoCard(
|
|
||||||
title: "backup_controller_page_backup".tr(),
|
|
||||||
subtitle: "backup_controller_page_backup_sub".tr(),
|
|
||||||
info: ref.watch(backupProvider).availableAlbums.isEmpty
|
|
||||||
? "..."
|
|
||||||
: "${backupState.selectedAlbumsBackupAssetsIds.length}",
|
|
||||||
),
|
|
||||||
BackupInfoCard(
|
|
||||||
title: "backup_controller_page_remainder".tr(),
|
|
||||||
subtitle: "backup_controller_page_remainder_sub".tr(),
|
|
||||||
info: ref.watch(backupProvider).availableAlbums.isEmpty
|
|
||||||
? "..."
|
|
||||||
: "${max(0, backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length)}",
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
const CurrentUploadingAssetInfoBox(),
|
|
||||||
if (!hasExclusiveAccess) buildBackgroundBackupInfo(),
|
|
||||||
buildBackupButton(),
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
buildFolderSelectionTile(),
|
|
||||||
if (!didGetBackupInfo.value) buildLoadingIndicator(),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import 'package:immich_mobile/services/app_settings.service.dart';
|
|||||||
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/advanced_bottom_sheet.dart';
|
import 'package:immich_mobile/widgets/asset_viewer/advanced_bottom_sheet.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/bottom_gallery_bar.dart';
|
import 'package:immich_mobile/widgets/asset_viewer/bottom_gallery_bar.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/exif_sheet/exif_bottom_sheet.dart';
|
import 'package:immich_mobile/widgets/asset_viewer/detail_panel/detail_panel.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/gallery_app_bar.dart';
|
import 'package:immich_mobile/widgets/asset_viewer/gallery_app_bar.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
|
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
|
||||||
@@ -152,7 +152,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
.watch(appSettingsServiceProvider)
|
.watch(appSettingsServiceProvider)
|
||||||
.getSetting<bool>(AppSettingsEnum.advancedTroubleshooting)
|
.getSetting<bool>(AppSettingsEnum.advancedTroubleshooting)
|
||||||
? AdvancedBottomSheet(assetDetail: asset)
|
? AdvancedBottomSheet(assetDetail: asset)
|
||||||
: ExifBottomSheet(asset: asset),
|
: DetailPanel(asset: asset),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -233,14 +234,6 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
|||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
expiryAfter.value = value!;
|
expiryAfter.value = value!;
|
||||||
},
|
},
|
||||||
inputDecorationTheme: themeData.inputDecorationTheme.copyWith(
|
|
||||||
disabledBorder: OutlineInputBorder(
|
|
||||||
borderSide: BorderSide(color: Colors.grey.withOpacity(0.5)),
|
|
||||||
),
|
|
||||||
enabledBorder: const OutlineInputBorder(
|
|
||||||
borderSide: BorderSide(color: Colors.grey),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
dropdownMenuEntries: [
|
dropdownMenuEntries: [
|
||||||
DropdownMenuEntry(
|
DropdownMenuEntry(
|
||||||
value: 0,
|
value: 0,
|
||||||
|
|||||||
@@ -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 =
|
||||||
|
|||||||
+1
-1
@@ -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;
|
||||||
|
|||||||
+363
-436
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),
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ class AssetService {
|
|||||||
final dto = await _apiService.assetsApi.getAssetInfo(a.remoteId!);
|
final dto = await _apiService.assetsApi.getAssetInfo(a.remoteId!);
|
||||||
if (dto != null && dto.exifInfo != null) {
|
if (dto != null && dto.exifInfo != null) {
|
||||||
final newExif = Asset.remote(dto).exifInfo!.copyWith(id: a.id);
|
final newExif = Asset.remote(dto).exifInfo!.copyWith(id: a.id);
|
||||||
|
a.exifInfo = newExif;
|
||||||
if (newExif != a.exifInfo) {
|
if (newExif != a.exifInfo) {
|
||||||
if (a.isInDb) {
|
if (a.isInDb) {
|
||||||
_db.writeTxn(() => a.put(_db));
|
_db.writeTxn(() => a.put(_db));
|
||||||
|
|||||||
@@ -43,6 +43,19 @@ class AssetDescriptionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getAssetDescription(Asset asset) {
|
||||||
|
final localExifId = asset.exifInfo?.id;
|
||||||
|
|
||||||
|
// Guard [remoteAssetId] and [localExifId] null
|
||||||
|
if (localExifId == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
final exifInfo = _db.exifInfos.getSync(localExifId);
|
||||||
|
|
||||||
|
return exifInfo?.description ?? "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final assetDescriptionServiceProvider = Provider(
|
final assetDescriptionServiceProvider = Provider(
|
||||||
|
|||||||
@@ -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,272 @@ 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,
|
||||||
|
),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: colorScheme.outlineVariant,
|
||||||
|
),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||||
|
),
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
hintStyle: const TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
textSelectionTheme: TextSelectionThemeData(
|
||||||
|
cursorColor: primaryColor,
|
||||||
|
),
|
||||||
|
dropdownMenuTheme: DropdownMenuThemeData(
|
||||||
|
menuStyle: MenuStyle(
|
||||||
|
shape: WidgetStatePropertyAll<OutlinedBorder>(
|
||||||
|
RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: colorScheme.outlineVariant,
|
||||||
|
),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||||
|
),
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
hintStyle: const TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user