mirror of
https://github.com/immich-app/immich.git
synced 2026-05-31 03:45:19 -04:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dc2c01e473 | |||
| 0a3878cfb8 | |||
| 306df18432 | |||
| 2ce4d9fa61 | |||
| 65611bb860 | |||
| 14aff51da9 | |||
| c42cea5ca9 | |||
| da8505f61d | |||
| 58586483dc | |||
| a838167f11 | |||
| b189fc571c | |||
| 96923f6115 | |||
| 0d6cce4a5b | |||
| 55947cb227 | |||
| 8783180cf3 | |||
| 134c0d4dfb | |||
| aecf8ec88b | |||
| bcff1d42b0 | |||
| 1bd367bd51 | |||
| 725f266b81 | |||
| d08e3de207 | |||
| 26714f6bfe | |||
| a5ce3fc927 | |||
| 3b23f71a3f | |||
| dec33cadd9 | |||
| 80c15a5e27 | |||
| 936c28a40b | |||
| 1a837a28ac | |||
| 8d5d12b108 | |||
| dd7a94135f | |||
| 1acc511b5c | |||
| 452e88267a | |||
| b941108cbd | |||
| e46f2843f7 | |||
| cf991e7b1b | |||
| 748a13104a | |||
| 2dd6b47714 | |||
| 8682be4774 | |||
| dc66892ca1 | |||
| 53a24783f5 | |||
| 0546bc900c | |||
| 7c25bcc0a7 | |||
| 7905853639 | |||
| 073dcc1fbe | |||
| ccdaa4223c | |||
| 5386b62dc4 | |||
| 9733fa4872 | |||
| 3b34c53092 |
@@ -8,6 +8,8 @@ log "Preparing Immich Web Frontend"
|
|||||||
log ""
|
log ""
|
||||||
run_cmd pnpm --filter @immich/sdk install
|
run_cmd pnpm --filter @immich/sdk install
|
||||||
run_cmd pnpm --filter @immich/sdk build
|
run_cmd pnpm --filter @immich/sdk build
|
||||||
|
run_cmd pnpm --filter @immich/plugin-sdk install
|
||||||
|
run_cmd pnpm --filter @immich/plugin-sdk build
|
||||||
run_cmd pnpm --filter immich-web install
|
run_cmd pnpm --filter immich-web install
|
||||||
|
|
||||||
log "Starting Immich Web Frontend"
|
log "Starting Immich Web Frontend"
|
||||||
|
|||||||
@@ -230,8 +230,12 @@ jobs:
|
|||||||
- name: Generate platform APIs
|
- name: Generate platform APIs
|
||||||
run: mise //mobile:codegen:pigeon
|
run: mise //mobile:codegen:pigeon
|
||||||
|
|
||||||
|
- name: Resolve iOS Swift Packages
|
||||||
|
working-directory: ./mobile
|
||||||
|
run: flutter build ios --config-only --no-codesign
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0
|
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
|
||||||
with:
|
with:
|
||||||
ruby-version: '3.3'
|
ruby-version: '3.3'
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
@@ -83,6 +83,6 @@ jobs:
|
|||||||
# ./location_of_script_within_repo/buildscript.sh
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||||
with:
|
with:
|
||||||
category: '/language:${{matrix.language}}'
|
category: '/language:${{matrix.language}}'
|
||||||
|
|||||||
@@ -72,10 +72,6 @@ jobs:
|
|||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
working-directory: ./mobile/packages/ui
|
working-directory: ./mobile/packages/ui
|
||||||
|
|
||||||
- name: Install dependencies for UI Showcase
|
|
||||||
run: flutter pub get
|
|
||||||
working-directory: ./mobile/packages/ui/showcase
|
|
||||||
|
|
||||||
- name: Generate translation files
|
- name: Generate translation files
|
||||||
run: mise //mobile:codegen:translation
|
run: mise //mobile:codegen:translation
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +1,46 @@
|
|||||||
dev:
|
dev:
|
||||||
@trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise dev # or mise //:dev from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
dev-down:
|
dev-down:
|
||||||
docker compose -f ./docker/docker-compose.dev.yml down --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise dev-down # or mise //:dev-down from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
dev-update:
|
dev-update:
|
||||||
@trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise dev-update # or mise //:dev-update from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
dev-scale:
|
dev-scale:
|
||||||
@trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise dev-scale # or mise //:dev-scale from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
dev-docs:
|
dev-docs:
|
||||||
npm --prefix docs run start
|
npm --prefix docs run start
|
||||||
|
|
||||||
.PHONY: e2e
|
.PHONY: e2e
|
||||||
e2e:
|
e2e:
|
||||||
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise e2e # or mise //:e2e from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
e2e-dev:
|
e2e-dev:
|
||||||
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.dev.yml up --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise e2e-dev # or mise //:e2e-dev from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
e2e-update:
|
e2e-update:
|
||||||
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --build -V --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise e2e-update # or mise //:e2e-update from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
e2e-down:
|
e2e-down:
|
||||||
docker compose -f ./e2e/docker-compose.yml down --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise e2e-down # or mise //:e2e-down from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
prod:
|
prod:
|
||||||
@trap 'make prod-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise prod # or mise //:prod from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
prod-down:
|
prod-down:
|
||||||
docker compose -f ./docker/docker-compose.prod.yml down --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise prod-down # or mise //:prod-down from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
prod-scale:
|
prod-scale:
|
||||||
@trap 'make prod-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
|
@printf "This command has been removed. Please use:\n\n mise prod-scale # or mise //:prod-scale from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
.PHONY: open-api
|
.PHONY: open-api
|
||||||
open-api:
|
open-api:
|
||||||
@printf "This command has been removed. Please use:\n\n mise open-api # or mise //:open-api from another directory\n\n"\n\n >&2 && exit 1
|
@printf "This command has been removed. Please use:\n\n mise open-api # or mise //:open-api from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
sql:
|
sql:
|
||||||
@printf "This command has been removed. Please use:\n\n mise sql # or mise //:sql from another directory\n\n"\n\n >&2 && exit 1
|
@printf "This command has been removed. Please use:\n\n mise sql # or mise //:sql from another directory\n\n" >&2 && exit 1
|
||||||
|
|
||||||
|
|
||||||
renovate:
|
renovate:
|
||||||
@@ -52,16 +52,7 @@ renovate:
|
|||||||
MODULES = e2e server web cli sdk docs .github
|
MODULES = e2e server web cli sdk docs .github
|
||||||
|
|
||||||
test-e2e:
|
test-e2e:
|
||||||
docker compose -f ./e2e/docker-compose.yml build
|
@printf "This command has been removed. Please use:\n\n mise //e2e:test # or mise //e2e:test-web for web tests, respectively\n\n" >&2 && exit 1
|
||||||
pnpm --filter immich-e2e run test
|
|
||||||
pnpm --filter immich-e2e run test:web
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
find . -name "node_modules" -type d -prune -exec rm -rf {} +
|
@printf "This command has been removed. Please use:\n\n mise clean # or mise //:clean from another directory\n\n" >&2 && exit 1
|
||||||
find . -name "dist" -type d -prune -exec rm -rf '{}' +
|
|
||||||
find . -name "build" -type d -prune -exec rm -rf '{}' +
|
|
||||||
find . -name ".svelte-kit" -type d -prune -exec rm -rf '{}' +
|
|
||||||
find . -name "coverage" -type d -prune -exec rm -rf '{}' +
|
|
||||||
find . -name ".pnpm-store" -type d -prune -exec rm -rf '{}' +
|
|
||||||
command -v docker >/dev/null 2>&1 && docker compose -f ./docker/docker-compose.dev.yml down -v --remove-orphans || true
|
|
||||||
command -v docker >/dev/null 2>&1 && docker compose -f ./e2e/docker-compose.yml down -v --remove-orphans || true
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ..:/usr/src/app
|
- ..:/usr/src/app
|
||||||
# - ../../ui:/usr/src/ui
|
# - ../../ui:/usr/src/ui
|
||||||
- pnpm_cache:/buildcache/pnpm_cache
|
- build_cache:/buildcache
|
||||||
- server_node_modules:/usr/src/app/server/node_modules
|
- server_node_modules:/usr/src/app/server/node_modules
|
||||||
- web_node_modules:/usr/src/app/web/node_modules
|
- web_node_modules:/usr/src/app/web/node_modules
|
||||||
- github_node_modules:/usr/src/app/.github/node_modules
|
- github_node_modules:/usr/src/app/.github/node_modules
|
||||||
@@ -45,11 +45,11 @@ services:
|
|||||||
target: dev
|
target: dev
|
||||||
command:
|
command:
|
||||||
- |
|
- |
|
||||||
pnpm install
|
mise install
|
||||||
touch /tmp/init-complete
|
touch /tmp/init-complete
|
||||||
exec tail -f /dev/null
|
exec tail -f /dev/null
|
||||||
volumes:
|
volumes:
|
||||||
- pnpm_store_server:/buildcache/pnpm-store
|
- build_cache:/buildcache
|
||||||
restart: 'no'
|
restart: 'no'
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD', 'test', '-f', '/tmp/init-complete']
|
test: ['CMD', 'test', '-f', '/tmp/init-complete']
|
||||||
@@ -73,7 +73,6 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ${UPLOAD_LOCATION}/photos:/data
|
- ${UPLOAD_LOCATION}/photos:/data
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- pnpm_store_server:/buildcache/pnpm-store
|
|
||||||
- ../packages/plugin-core:/build/plugins/immich-plugin-core
|
- ../packages/plugin-core:/build/plugins/immich-plugin-core
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
@@ -122,8 +121,6 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
- 24678:24678
|
- 24678:24678
|
||||||
volumes:
|
|
||||||
- pnpm_store_web:/buildcache/pnpm-store
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
immich-init:
|
immich-init:
|
||||||
@@ -203,9 +200,7 @@ volumes:
|
|||||||
model_cache:
|
model_cache:
|
||||||
prometheus_data:
|
prometheus_data:
|
||||||
grafana_data:
|
grafana_data:
|
||||||
pnpm_cache:
|
build_cache:
|
||||||
pnpm_store_server:
|
|
||||||
pnpm_store_web:
|
|
||||||
server_node_modules:
|
server_node_modules:
|
||||||
web_node_modules:
|
web_node_modules:
|
||||||
github_node_modules:
|
github_node_modules:
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ For organizations seeking to resell Immich, we have established the following gu
|
|||||||
|
|
||||||
When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app
|
When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## User
|
## User
|
||||||
|
|
||||||
### How can I reset the admin password?
|
### How can I reset the admin password?
|
||||||
@@ -36,6 +38,10 @@ The admin password can be reset by running the [reset-admin-password](/administr
|
|||||||
|
|
||||||
You can see the list of all users by running [list-users](/administration/server-commands.md) Command on the Immich-server.
|
You can see the list of all users by running [list-users](/administration/server-commands.md) Command on the Immich-server.
|
||||||
|
|
||||||
|
### How can I change my profile picture?
|
||||||
|
|
||||||
|
View a single photo, press the three dots in the top-right to show context menu, and select "Set as profile picture". In the pop-up, use your mouse scroll wheel to zoom in the picture until it completely fills the circle. Click and drag the picture to align it to your liking. Press "Save" to save your changes.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Mobile App
|
## Mobile App
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ After making any changes in the `server/src/schema`, a database migration need t
|
|||||||
1. Run the command
|
1. Run the command
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run migrations:generate <migration-name>
|
mise //server:migrations generate <migration-name>
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Check if the migration file makes sense.
|
2. Check if the migration file makes sense.
|
||||||
@@ -18,7 +18,7 @@ The server will automatically detect `*.ts` file changes and restart. Part of th
|
|||||||
If you need to undo the most recently applied migration—for example, when developing or testing on schema changes—run:
|
If you need to undo the most recently applied migration—for example, when developing or testing on schema changes—run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run migrations:revert
|
mise //server:migrations revert
|
||||||
```
|
```
|
||||||
|
|
||||||
This command rolls back the latest migration and brings the database schema back to its previous state.
|
This command rolls back the latest migration and brings the database schema back to its previous state.
|
||||||
|
|||||||
@@ -252,44 +252,33 @@ To connect the mobile app to your Dev Container:
|
|||||||
|
|
||||||
The Dev Container supports multiple ways to run tests:
|
The Dev Container supports multiple ways to run tests:
|
||||||
|
|
||||||
#### Using Mise Commands (Recommended)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run tests for specific components
|
# Server
|
||||||
mise run checklist # in `server/`, `web/`, `packages/cli`
|
mise //server:test # unit tests
|
||||||
|
mise //server:test-medium # medium / integration tests
|
||||||
|
|
||||||
|
# Web
|
||||||
|
mise //web:test # unit tests
|
||||||
|
|
||||||
|
# E2E
|
||||||
|
mise //e2e:test # API tests
|
||||||
|
mise //e2e:test-web # web UI tests (Playwright)
|
||||||
|
|
||||||
|
# Run all checks for a component
|
||||||
|
mise //server:checklist
|
||||||
|
mise //web:checklist
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Using PNPM Directly
|
### Additional Commands
|
||||||
|
|
||||||
```bash
|
|
||||||
# Server tests
|
|
||||||
cd /workspaces/immich/server
|
|
||||||
pnpm test # Run all tests
|
|
||||||
pnpm run test:medium # Medium tests (integration tests)
|
|
||||||
pnpm run test:watch # Watch mode
|
|
||||||
pnpm run test:cov # Coverage report
|
|
||||||
|
|
||||||
# Web tests
|
|
||||||
cd /workspaces/immich/web
|
|
||||||
pnpm test # Run all tests
|
|
||||||
pnpm run test:watch # Watch mode
|
|
||||||
|
|
||||||
# E2E tests
|
|
||||||
cd /workspaces/immich/e2e
|
|
||||||
pnpm run test # Run API tests
|
|
||||||
pnpm run test:web # Run web UI tests
|
|
||||||
```
|
|
||||||
|
|
||||||
### Additional Make Commands
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# API generation
|
# API generation
|
||||||
make open-api # Generate OpenAPI specs
|
mise //:open-api # Generate OpenAPI specs
|
||||||
make open-api-typescript # Generate TypeScript SDK
|
mise //:open-api-typescript # Generate TypeScript SDK
|
||||||
make open-api-dart # Generate Dart SDK
|
mise //:open-api-dart # Generate Dart SDK
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
mise sql # Sync database schema
|
mise //server:sql # Sync database schema
|
||||||
```
|
```
|
||||||
|
|
||||||
### Debugging
|
### Debugging
|
||||||
|
|||||||
@@ -8,34 +8,42 @@ When contributing code through a pull request, please check the following:
|
|||||||
|
|
||||||
## Web Checks
|
## Web Checks
|
||||||
|
|
||||||
- [ ] `pnpm run lint` (linting via ESLint)
|
- [ ] `mise //web:lint` (linting via ESLint)
|
||||||
- [ ] `pnpm run format` (formatting via Prettier)
|
- [ ] `mise //web:format` (formatting via Prettier)
|
||||||
- [ ] `pnpm run check:svelte` (Type checking via SvelteKit)
|
- [ ] `mise //web:check-svelte` (type checking via SvelteKit)
|
||||||
- [ ] `pnpm run check:typescript` (check typescript)
|
- [ ] `mise //web:check-typescript` (type checking via `tsc`)
|
||||||
- [ ] `pnpm test` (unit tests)
|
- [ ] `mise //web:test` (unit tests)
|
||||||
|
|
||||||
:::tip AIO
|
:::tip AIO
|
||||||
Run all web checks with `pnpm run check:all`
|
Run all web checks with `mise //web:checklist`
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::tip Auto Fix
|
||||||
|
Use `mise //web:lint-fix` and `mise //web:format-fix` to automatically correct some issues.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [ ] `pnpm run format` (formatting via Prettier)
|
- [ ] `mise //docs:format` (formatting via Prettier)
|
||||||
- [ ] Update the `_redirects` file if you have renamed a page or removed it from the documentation.
|
- [ ] Update the `_redirects` file if you have renamed a page or removed it from the documentation.
|
||||||
|
|
||||||
|
:::tip Auto Fix
|
||||||
|
Use `mise //docs:format-fix` to automatically fix formatting.
|
||||||
|
:::
|
||||||
|
|
||||||
## Server Checks
|
## Server Checks
|
||||||
|
|
||||||
- [ ] `pnpm run lint` (linting via ESLint)
|
- [ ] `mise //server:lint` (linting via ESLint)
|
||||||
- [ ] `pnpm run format` (formatting via Prettier)
|
- [ ] `mise //server:format` (formatting via Prettier)
|
||||||
- [ ] `pnpm run check` (Type checking via `tsc`)
|
- [ ] `mise //server:check` (type checking via `tsc`)
|
||||||
- [ ] `pnpm test` (unit tests)
|
- [ ] `mise //server:test` (unit tests)
|
||||||
|
|
||||||
:::tip AIO
|
:::tip AIO
|
||||||
Run all server checks with `pnpm run check:all`
|
Run all server checks with `mise //server:checklist`
|
||||||
:::
|
:::
|
||||||
|
|
||||||
:::tip Auto Fix
|
:::tip Auto Fix
|
||||||
You can use `pnpm run __:fix` to potentially correct some issues automatically for `pnpm run format` and `lint`.
|
Use `mise //server:lint-fix` and `mise //server:format-fix` to automatically correct some issues.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Mobile Checklist
|
## Mobile Checklist
|
||||||
@@ -53,6 +61,17 @@ Run all these commands at once with `mise //mobile:checklist`
|
|||||||
You can use `mise //mobile:lint-fix` to potentially correct some issues automatically for `mise //mobile:lint`.
|
You can use `mise //mobile:lint-fix` to potentially correct some issues automatically for `mise //mobile:lint`.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## Machine Learning Checklist
|
||||||
|
|
||||||
|
- [ ] `mise //machine-learning:lint` (linting via ruff)
|
||||||
|
- [ ] `mise //machine-learning:format` (formatting via ruff)
|
||||||
|
- [ ] `mise //machine-learning:check` (type checking via mypy)
|
||||||
|
- [ ] `mise //machine-learning:test` (unit tests via pytest)
|
||||||
|
|
||||||
|
:::tip AIO
|
||||||
|
Run all machine learning checks with `mise //machine-learning:checklist`
|
||||||
|
:::
|
||||||
|
|
||||||
## OpenAPI
|
## OpenAPI
|
||||||
|
|
||||||
The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/api.md) for more details.
|
The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/api.md) for more details.
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ This environment includes the services below. Additional details are available i
|
|||||||
|
|
||||||
All the services are packaged to run as with single Docker Compose command.
|
All the services are packaged to run as with single Docker Compose command.
|
||||||
|
|
||||||
|
:::tip mise
|
||||||
|
[mise](https://mise.jdx.dev) is used throughout the project to manage tool versions and run tasks. [Install mise](https://mise.jdx.dev/installing-mise.html), then from the repo root run `mise trust` and `mise install` to get all required tools. Tasks for each service can be run from the repo root using `mise //namespace:task` (e.g. `mise //server:lint`). To list all available tasks, run `mise tasks ls --all`.
|
||||||
|
:::
|
||||||
|
|
||||||
### Server and web apps
|
### Server and web apps
|
||||||
|
|
||||||
1. Clone the project repo.
|
1. Clone the project repo.
|
||||||
@@ -56,22 +60,23 @@ You can access the web from `http://your-machine-ip:3000` or `http://localhost:3
|
|||||||
|
|
||||||
#### Connect web to a remote backend
|
#### Connect web to a remote backend
|
||||||
|
|
||||||
If you only want to do web development connected to an existing, remote backend, follow these steps:
|
If you only want to do web development connected to an existing, remote backend, run from the repo root:
|
||||||
|
|
||||||
1. Build the Immich SDK - `pnpm --filter @immich/sdk install && pnpm --filter @immich/sdk build`
|
|
||||||
2. Enter the web directory - `cd web/`
|
|
||||||
3. Install web dependencies - `pnpm i`
|
|
||||||
4. Start the web development server
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
IMMICH_SERVER_URL=https://demo.immich.app/ pnpm run dev
|
IMMICH_SERVER_URL=https://demo.immich.app/ mise //web:start
|
||||||
|
```
|
||||||
|
|
||||||
|
This will install all dependencies (including the SDK) and start the dev server in one step. To connect to the hosted demo server specifically, use the shorthand:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mise //web:start-demo
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're using PowerShell on Windows you may need to set the env var separately like so:
|
If you're using PowerShell on Windows you may need to set the env var separately like so:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
$env:IMMICH_SERVER_URL = "https://demo.immich.app/"
|
$env:IMMICH_SERVER_URL = "https://demo.immich.app/"
|
||||||
pnpm run dev
|
mise //web:start
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `@immich/ui`
|
#### `@immich/ui`
|
||||||
@@ -90,24 +95,38 @@ To see local changes to `@immich/ui` in Immich, do the following:
|
|||||||
|
|
||||||
#### Setup
|
#### Setup
|
||||||
|
|
||||||
1. [Install mise](https://mise.jdx.dev/installing-mise.html).
|
1. Run `mise //mobile:install` to install Flutter dependencies.
|
||||||
2. Change to the immich (root) directory and trust the mise config with `mise trust`.
|
2. Run `mise //mobile:translation` to generate the translation file.
|
||||||
3. Install tools with mise: `mise install`.
|
3. Change to the `mobile/` directory and run `flutter run` to start the app.
|
||||||
4. Change to the `mobile/` directory.
|
|
||||||
5. Run `flutter pub get` to install the dependencies.
|
|
||||||
6. Run `make translation` to generate the translation file.
|
|
||||||
7. Run `flutter run` to start the app.
|
|
||||||
|
|
||||||
#### Translation
|
#### Translation
|
||||||
|
|
||||||
To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then, from the `mobile/` directory, run
|
To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make translation
|
mise //mobile:translation
|
||||||
```
|
```
|
||||||
|
|
||||||
The mobile app asks you what backend to connect to. You can utilize the demo backend (https://demo.immich.app/) if you don't need to change server code or upload photos. Alternatively, you can run the server yourself per the instructions above.
|
The mobile app asks you what backend to connect to. You can utilize the demo backend (https://demo.immich.app/) if you don't need to change server code or upload photos. Alternatively, you can run the server yourself per the instructions above.
|
||||||
|
|
||||||
|
#### UI components and widget previews
|
||||||
|
|
||||||
|
Shared design-system widgets (buttons, inputs, forms) live in the
|
||||||
|
[`immich_ui` package](https://github.com/immich-app/immich/tree/main/mobile/packages/ui/)
|
||||||
|
under `mobile/packages/ui/`. Components are defined in `lib/src/components/`
|
||||||
|
and have matching previews in `lib/src/previews/`.
|
||||||
|
|
||||||
|
To inspect a component in isolation with a light/dark toggle and hot reload,
|
||||||
|
launch [Flutter's Widget Previewer](https://docs.flutter.dev/tools/widget-previewer):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mobile/packages/ui
|
||||||
|
flutter widget-preview start
|
||||||
|
```
|
||||||
|
|
||||||
|
In VS Code or Android Studio with the Flutter plugin, the previewer
|
||||||
|
auto-starts when you open the **Flutter Widget Preview** tab in the sidebar.
|
||||||
|
|
||||||
## IDE setup
|
## IDE setup
|
||||||
|
|
||||||
### Lint / format extensions
|
### Lint / format extensions
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
### Unit tests
|
### Unit tests
|
||||||
|
|
||||||
Unit are run by calling `pnpm run test` from the `server/` directory.
|
Unit tests are run with `mise //server:test`.
|
||||||
You need to run `pnpm install` (in `server/`) before _once_.
|
You need to run `mise //server:install` before _once_.
|
||||||
|
|
||||||
### End to end tests
|
### End to end tests
|
||||||
|
|
||||||
@@ -17,8 +17,7 @@ make e2e
|
|||||||
|
|
||||||
Before you can run the tests, you need to run the following commands _once_:
|
Before you can run the tests, you need to run the following commands _once_:
|
||||||
|
|
||||||
- `pnpm install`
|
- `mise //e2e:ci-setup` (installs e2e, SDK, and CLI dependencies)
|
||||||
- `pnpm --filter @immich/sdk --filter @immich/cli build`
|
|
||||||
- `mise //:open-api`
|
- `mise //:open-api`
|
||||||
|
|
||||||
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:
|
||||||
|
|||||||
@@ -154,33 +154,33 @@ Redis (Sentinel) URL example JSON before encoding:
|
|||||||
|
|
||||||
## Machine Learning
|
## Machine Learning
|
||||||
|
|
||||||
| Variable | Description | Default | Containers |
|
| Variable | Description | Default | Containers |
|
||||||
| :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------------------------: | :--------------- |
|
| :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------: | :--------------- |
|
||||||
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning |
|
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning |
|
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning |
|
||||||
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
|
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
|
||||||
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning |
|
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
|
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`<sup>\*3</sup> | HTTP Keep-alive time in seconds | `2` | machine learning |
|
| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`<sup>\*3</sup> | HTTP Keep-alive time in seconds | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning |
|
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `300` (`900` if using ROCm) | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION` | Comma-separated list of (recognition) OCR model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION` | Comma-separated list of (recognition) OCR model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__OCR__DETECTION` | Comma-separated list of (detection) OCR model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__OCR__DETECTION` | Comma-separated list of (detection) OCR model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning |
|
| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning |
|
||||||
| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning |
|
| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning |
|
||||||
| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning |
|
| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_DEVICE_IDS`<sup>\*4</sup> | Device IDs to use in multi-GPU environments | `0` | machine learning |
|
| `MACHINE_LEARNING_DEVICE_IDS`<sup>\*4</sup> | Device IDs to use in multi-GPU environments | `0` | machine learning |
|
||||||
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
|
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
|
||||||
| `MACHINE_LEARNING_MAX_BATCH_SIZE__OCR` | Set the maximum number of boxes that will be processed at once by the OCR model | `6` | machine learning |
|
| `MACHINE_LEARNING_MAX_BATCH_SIZE__OCR` | Set the maximum number of boxes that will be processed at once by the OCR model | `6` | machine learning |
|
||||||
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
|
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
|
||||||
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spun up while inferencing. | `1` | machine learning |
|
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spun up while inferencing. | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_ARENA` | Pre-allocates CPU memory to avoid memory fragmentation | true | machine learning |
|
| `MACHINE_LEARNING_MODEL_ARENA` | Pre-allocates CPU memory to avoid memory fragmentation | true | machine learning |
|
||||||
| `MACHINE_LEARNING_OPENVINO_PRECISION` | If set to FP16, uses half-precision floating-point operations for faster inference with reduced accuracy (one of [`FP16`, `FP32`], applies only to OpenVINO) | `FP32` | machine learning |
|
| `MACHINE_LEARNING_OPENVINO_PRECISION` | If set to FP16, uses half-precision floating-point operations for faster inference with reduced accuracy (one of [`FP16`, `FP32`], applies only to OpenVINO) | `FP32` | machine learning |
|
||||||
|
|
||||||
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ run = "pnpm install --filter documentation --frozen-lockfile"
|
|||||||
|
|
||||||
[tasks.start]
|
[tasks.start]
|
||||||
env._.path = "./node_modules/.bin"
|
env._.path = "./node_modules/.bin"
|
||||||
run = "docusaurus --port 3005"
|
run = "docusaurus start --port 3005"
|
||||||
|
|
||||||
[tasks.build]
|
[tasks.build]
|
||||||
env._.path = "./node_modules/.bin"
|
env._.path = "./node_modules/.bin"
|
||||||
|
|||||||
@@ -83,9 +83,7 @@ volumes:
|
|||||||
model_cache:
|
model_cache:
|
||||||
prometheus_data:
|
prometheus_data:
|
||||||
grafana_data:
|
grafana_data:
|
||||||
pnpm_cache:
|
build_cache:
|
||||||
pnpm_store_server:
|
|
||||||
pnpm_store_web:
|
|
||||||
server_node_modules:
|
server_node_modules:
|
||||||
web_node_modules:
|
web_node_modules:
|
||||||
github_node_modules:
|
github_node_modules:
|
||||||
|
|||||||
+16
-1
@@ -1,11 +1,21 @@
|
|||||||
[tasks.install]
|
[tasks.install]
|
||||||
run = "pnpm install --filter immich-e2e --frozen-lockfile"
|
run = "pnpm install --filter immich-e2e --frozen-lockfile"
|
||||||
|
|
||||||
|
[tasks.build]
|
||||||
|
dir = "{{ config_root }}"
|
||||||
|
run = "docker compose build"
|
||||||
|
|
||||||
[tasks.test]
|
[tasks.test]
|
||||||
|
depends = ["//e2e:build", "//e2e:ci-setup"]
|
||||||
env._.path = "./node_modules/.bin"
|
env._.path = "./node_modules/.bin"
|
||||||
run = "vitest --run"
|
run = "vitest --run"
|
||||||
|
|
||||||
|
[tasks.playwright-install]
|
||||||
|
env._.path = "./node_modules/.bin"
|
||||||
|
run = "playwright install"
|
||||||
|
|
||||||
[tasks."test-web"]
|
[tasks."test-web"]
|
||||||
|
depends = ["//e2e:build", "//e2e:ci-setup", "//e2e:playwright-install"]
|
||||||
env._.path = "./node_modules/.bin"
|
env._.path = "./node_modules/.bin"
|
||||||
run = "playwright test"
|
run = "playwright test"
|
||||||
|
|
||||||
@@ -30,7 +40,12 @@ run = "tsc --noEmit"
|
|||||||
|
|
||||||
|
|
||||||
[tasks.ci-setup]
|
[tasks.ci-setup]
|
||||||
depends = ["//:sdk:install", "//:sdk:build", "//cli:install", "//cli:build"]
|
depends = [
|
||||||
|
"//:sdk:install",
|
||||||
|
"//:sdk:build",
|
||||||
|
"//packages/cli:install",
|
||||||
|
"//packages/cli:build",
|
||||||
|
]
|
||||||
run = { task = ":install" }
|
run = { task = ":install" }
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ export function toColumnarFormat(assets: MockTimelineAsset[]): TimeBucketAssetRe
|
|||||||
result.duration.push(asset.duration);
|
result.duration.push(asset.duration);
|
||||||
result.projectionType.push(asset.projectionType);
|
result.projectionType.push(asset.projectionType);
|
||||||
result.livePhotoVideoId.push(asset.livePhotoVideoId);
|
result.livePhotoVideoId.push(asset.livePhotoVideoId);
|
||||||
result.city.push(asset.city);
|
result.city?.push(asset.city);
|
||||||
result.country.push(asset.country);
|
result.country?.push(asset.country);
|
||||||
result.visibility.push(asset.visibility);
|
result.visibility.push(asset.visibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -536,7 +536,7 @@ test.describe('Timeline', () => {
|
|||||||
force: false,
|
force: false,
|
||||||
ids: [assetToTrash.id],
|
ids: [assetToTrash.id],
|
||||||
});
|
});
|
||||||
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
await page.locator('#control-bar').getByLabel('Close').click();
|
||||||
await page.getByText('Trash', { exact: true }).click();
|
await page.getByText('Trash', { exact: true }).click();
|
||||||
await timelineUtils.waitForTimelineLoad(page);
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
await thumbnailUtils.expectInViewport(page, assetToTrash.id);
|
await thumbnailUtils.expectInViewport(page, assetToTrash.id);
|
||||||
@@ -676,7 +676,7 @@ test.describe('Timeline', () => {
|
|||||||
ids: [assetToArchive.id],
|
ids: [assetToArchive.id],
|
||||||
});
|
});
|
||||||
await thumbnailUtils.expectThumbnailIsArchive(page, assetToArchive.id);
|
await thumbnailUtils.expectThumbnailIsArchive(page, assetToArchive.id);
|
||||||
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
await page.locator('#control-bar').getByLabel('Close').click();
|
||||||
await page.getByRole('link').getByText('Archive').click();
|
await page.getByRole('link').getByText('Archive').click();
|
||||||
await timelineUtils.waitForTimelineLoad(page);
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
||||||
@@ -823,7 +823,7 @@ test.describe('Timeline', () => {
|
|||||||
});
|
});
|
||||||
// ensure thumbnail still exists and has favorite icon
|
// ensure thumbnail still exists and has favorite icon
|
||||||
await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id);
|
await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id);
|
||||||
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
await page.locator('#control-bar').getByLabel('Close').click();
|
||||||
await page.getByRole('link').getByText('Favorites').click();
|
await page.getByRole('link').getByText('Favorites').click();
|
||||||
await timelineUtils.waitForTimelineLoad(page);
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
await pageUtils.goToAsset(page, assetToFavorite.fileCreatedAt);
|
await pageUtils.goToAsset(page, assetToFavorite.fileCreatedAt);
|
||||||
|
|||||||
@@ -698,6 +698,7 @@
|
|||||||
"birthdate_saved": "Date of birth saved successfully",
|
"birthdate_saved": "Date of birth saved successfully",
|
||||||
"birthdate_set_description": "Date of birth is used to calculate the age of this person at the time of a photo.",
|
"birthdate_set_description": "Date of birth is used to calculate the age of this person at the time of a photo.",
|
||||||
"blurred_background": "Blurred background",
|
"blurred_background": "Blurred background",
|
||||||
|
"browse_templates": "Browse templates",
|
||||||
"bugs_and_feature_requests": "Bugs & Feature Requests",
|
"bugs_and_feature_requests": "Bugs & Feature Requests",
|
||||||
"build": "Build",
|
"build": "Build",
|
||||||
"build_image": "Build Image",
|
"build_image": "Build Image",
|
||||||
@@ -839,6 +840,7 @@
|
|||||||
"copy_error": "Copy error",
|
"copy_error": "Copy error",
|
||||||
"copy_file_path": "Copy file path",
|
"copy_file_path": "Copy file path",
|
||||||
"copy_image": "Copy Image",
|
"copy_image": "Copy Image",
|
||||||
|
"copy_json": "Copy JSON",
|
||||||
"copy_link": "Copy link",
|
"copy_link": "Copy link",
|
||||||
"copy_link_to_clipboard": "Copy link to clipboard",
|
"copy_link_to_clipboard": "Copy link to clipboard",
|
||||||
"copy_password": "Copy password",
|
"copy_password": "Copy password",
|
||||||
@@ -976,7 +978,10 @@
|
|||||||
"downloading_asset_filename": "Downloading asset {filename}",
|
"downloading_asset_filename": "Downloading asset {filename}",
|
||||||
"downloading_from_icloud": "Downloading from iCloud",
|
"downloading_from_icloud": "Downloading from iCloud",
|
||||||
"downloading_media": "Downloading media",
|
"downloading_media": "Downloading media",
|
||||||
|
"drag_to_reorder": "Drag to reorder",
|
||||||
"drop_files_to_upload": "Drop files anywhere to upload",
|
"drop_files_to_upload": "Drop files anywhere to upload",
|
||||||
|
"duplicate": "Duplicate",
|
||||||
|
"duplicate_workflow": "Duplicate workflow",
|
||||||
"duplicates": "Duplicates",
|
"duplicates": "Duplicates",
|
||||||
"duplicates_description": "Resolve each group by indicating which, if any, are duplicates.",
|
"duplicates_description": "Resolve each group by indicating which, if any, are duplicates.",
|
||||||
"duration": "Duration",
|
"duration": "Duration",
|
||||||
@@ -2228,6 +2233,7 @@
|
|||||||
"slideshow_repeat": "Repeat slideshow",
|
"slideshow_repeat": "Repeat slideshow",
|
||||||
"slideshow_repeat_description": "Loop back to beginning when slideshow ends",
|
"slideshow_repeat_description": "Loop back to beginning when slideshow ends",
|
||||||
"slideshow_settings": "Slideshow settings",
|
"slideshow_settings": "Slideshow settings",
|
||||||
|
"smart_album": "Smart album",
|
||||||
"sort_albums_by": "Sort albums by...",
|
"sort_albums_by": "Sort albums by...",
|
||||||
"sort_created": "Date created",
|
"sort_created": "Date created",
|
||||||
"sort_items": "Number of items",
|
"sort_items": "Number of items",
|
||||||
@@ -2254,6 +2260,7 @@
|
|||||||
"step_delete_confirm": "Are you sure you want to delete this step?",
|
"step_delete_confirm": "Are you sure you want to delete this step?",
|
||||||
"step_details": "Step details",
|
"step_details": "Step details",
|
||||||
"steps": "Steps",
|
"steps": "Steps",
|
||||||
|
"steps_count": "{count, plural, one {# step} other {# steps}}",
|
||||||
"stop_casting": "Stop casting",
|
"stop_casting": "Stop casting",
|
||||||
"stop_motion_photo": "Stop Motion Photo",
|
"stop_motion_photo": "Stop Motion Photo",
|
||||||
"stop_photo_sharing": "Stop sharing your photos?",
|
"stop_photo_sharing": "Stop sharing your photos?",
|
||||||
@@ -2415,6 +2422,7 @@
|
|||||||
"use_browser_locale_description": "Format dates, times, and numbers based on your browser locale",
|
"use_browser_locale_description": "Format dates, times, and numbers based on your browser locale",
|
||||||
"use_current_connection": "Use current connection",
|
"use_current_connection": "Use current connection",
|
||||||
"use_custom_date_range": "Use custom date range instead",
|
"use_custom_date_range": "Use custom date range instead",
|
||||||
|
"use_template": "Use template",
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"user_has_been_deleted": "This user has been deleted.",
|
"user_has_been_deleted": "This user has been deleted.",
|
||||||
"user_id": "User ID",
|
"user_id": "User ID",
|
||||||
@@ -2476,6 +2484,7 @@
|
|||||||
"week": "Week",
|
"week": "Week",
|
||||||
"welcome": "Welcome",
|
"welcome": "Welcome",
|
||||||
"welcome_to_immich": "Welcome to Immich",
|
"welcome_to_immich": "Welcome to Immich",
|
||||||
|
"when": "When",
|
||||||
"width": "Width",
|
"width": "Width",
|
||||||
"wifi_name": "Wi-Fi Name",
|
"wifi_name": "Wi-Fi Name",
|
||||||
"workflow": "Workflow",
|
"workflow": "Workflow",
|
||||||
@@ -2488,6 +2497,7 @@
|
|||||||
"workflow_name": "Workflow name",
|
"workflow_name": "Workflow name",
|
||||||
"workflow_navigation_prompt": "Are you sure you want to leave without saving your changes?",
|
"workflow_navigation_prompt": "Are you sure you want to leave without saving your changes?",
|
||||||
"workflow_summary": "Workflow summary",
|
"workflow_summary": "Workflow summary",
|
||||||
|
"workflow_templates": "Workflow templates",
|
||||||
"workflow_update_success": "Workflow updated successfully",
|
"workflow_update_success": "Workflow updated successfully",
|
||||||
"workflow_updated": "Workflow updated",
|
"workflow_updated": "Workflow updated",
|
||||||
"workflows": "Workflows",
|
"workflows": "Workflows",
|
||||||
|
|||||||
@@ -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
|
from pydantic import BaseModel, Field
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.logging import RichHandler
|
from rich.logging import RichHandler
|
||||||
@@ -42,6 +42,10 @@ class MaxBatchSize(BaseModel):
|
|||||||
ocr: int | None = None
|
ocr: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def default_worker_timeout() -> int:
|
||||||
|
return 900 if os.environ.get("DEVICE") == "rocm" else 300
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
env_prefix="MACHINE_LEARNING_",
|
env_prefix="MACHINE_LEARNING_",
|
||||||
@@ -54,7 +58,7 @@ class Settings(BaseSettings):
|
|||||||
model_ttl: int = 300
|
model_ttl: int = 300
|
||||||
model_ttl_poll_s: int = 10
|
model_ttl_poll_s: int = 10
|
||||||
workers: int = 1
|
workers: int = 1
|
||||||
worker_timeout: int = 300
|
worker_timeout: int = Field(default_factory=default_worker_timeout)
|
||||||
http_keepalive_timeout_s: int = 2
|
http_keepalive_timeout_s: int = 2
|
||||||
test_full: bool = False
|
test_full: bool = False
|
||||||
request_threads: int = os.cpu_count() or 4
|
request_threads: int = os.cpu_count() or 4
|
||||||
|
|||||||
@@ -89,4 +89,10 @@ class FaceRecognizer(InferenceModel):
|
|||||||
@property
|
@property
|
||||||
def _batch_size_default(self) -> int | None:
|
def _batch_size_default(self) -> int | None:
|
||||||
providers = ort.get_available_providers()
|
providers = ort.get_available_providers()
|
||||||
return None if self.model_format == ModelFormat.ONNX and "OpenVINOExecutionProvider" not in providers else 1
|
if (
|
||||||
|
self.model_format == ModelFormat.ONNX
|
||||||
|
and "MIGraphXExecutionProvider" not in providers
|
||||||
|
and "OpenVINOExecutionProvider" not in providers
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
return 1
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ class TextRecognizer(InferenceModel):
|
|||||||
rec_batch_num=max_batch_size if max_batch_size else 6,
|
rec_batch_num=max_batch_size if max_batch_size else 6,
|
||||||
rec_img_shape=(3, 48, 320),
|
rec_img_shape=(3, 48, 320),
|
||||||
lang_type=self.language,
|
lang_type=self.language,
|
||||||
|
model_root_dir=self.cache_dir,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return session
|
return session
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from threading import Lock
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -12,6 +13,37 @@ from immich_ml.schemas import ModelPrecision, SessionNode
|
|||||||
|
|
||||||
from ..config import log, settings
|
from ..config import log, settings
|
||||||
|
|
||||||
|
MigraphxInputSignature = tuple[tuple[str, str, tuple[int, ...]], ...]
|
||||||
|
|
||||||
|
_migraphx_registry_lock = Lock()
|
||||||
|
_migraphx_model_locks: dict[str, Lock] = {}
|
||||||
|
_migraphx_compiled_inputs: set[tuple[str, MigraphxInputSignature]] = set()
|
||||||
|
|
||||||
|
|
||||||
|
def _migraphx_get_model_lock(model_key: str) -> Lock:
|
||||||
|
with _migraphx_registry_lock:
|
||||||
|
lock = _migraphx_model_locks.get(model_key)
|
||||||
|
if lock is None:
|
||||||
|
lock = Lock()
|
||||||
|
_migraphx_model_locks[model_key] = lock
|
||||||
|
return lock
|
||||||
|
|
||||||
|
|
||||||
|
def _migraphx_has_compiled_input(key: tuple[str, MigraphxInputSignature]) -> bool:
|
||||||
|
with _migraphx_registry_lock:
|
||||||
|
return key in _migraphx_compiled_inputs
|
||||||
|
|
||||||
|
|
||||||
|
def _migraphx_mark_compiled_input(key: tuple[str, MigraphxInputSignature]) -> None:
|
||||||
|
with _migraphx_registry_lock:
|
||||||
|
_migraphx_compiled_inputs.add(key)
|
||||||
|
|
||||||
|
|
||||||
|
def _migraphx_input_signature(
|
||||||
|
input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]],
|
||||||
|
) -> MigraphxInputSignature:
|
||||||
|
return tuple((name, str(value.dtype), tuple(value.shape)) for name, value in sorted(input_feed.items()))
|
||||||
|
|
||||||
|
|
||||||
class OrtSession:
|
class OrtSession:
|
||||||
session: ort.InferenceSession
|
session: ort.InferenceSession
|
||||||
@@ -48,7 +80,21 @@ class OrtSession:
|
|||||||
input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]],
|
input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]],
|
||||||
run_options: Any = None,
|
run_options: Any = None,
|
||||||
) -> list[NDArray[np.float32]]:
|
) -> list[NDArray[np.float32]]:
|
||||||
outputs: list[NDArray[np.float32]] = self.session.run(output_names, input_feed, run_options)
|
if "MIGraphXExecutionProvider" in self.providers:
|
||||||
|
model_key = self.model_path.resolve().as_posix()
|
||||||
|
input_key = (model_key, _migraphx_input_signature(input_feed))
|
||||||
|
if not _migraphx_has_compiled_input(input_key):
|
||||||
|
model_lock = _migraphx_get_model_lock(model_key)
|
||||||
|
with model_lock:
|
||||||
|
if not _migraphx_has_compiled_input(input_key):
|
||||||
|
outputs: list[NDArray[np.float32]] = self.session.run(output_names, input_feed, run_options)
|
||||||
|
_migraphx_mark_compiled_input(input_key)
|
||||||
|
return outputs
|
||||||
|
|
||||||
|
outputs = self.session.run(output_names, input_feed, run_options)
|
||||||
|
return outputs
|
||||||
|
|
||||||
|
outputs = self.session.run(output_names, input_feed, run_options)
|
||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ dependencies = [
|
|||||||
"fastapi>=0.95.2,<1.0",
|
"fastapi>=0.95.2,<1.0",
|
||||||
"gunicorn>=21.1.0",
|
"gunicorn>=21.1.0",
|
||||||
"huggingface-hub>=1.0,<2.0",
|
"huggingface-hub>=1.0,<2.0",
|
||||||
"insightface>=0.7.3,<1.0",
|
"insightface>=0.7.3,<2.0",
|
||||||
"numpy>=2.4.0,<3.0",
|
"numpy>=2.4.0,<3.0",
|
||||||
"opencv-python-headless>=4.7.0.72,<5.0",
|
"opencv-python-headless>=4.7.0.72,<5.0",
|
||||||
"orjson>=3.9.5",
|
"orjson>=3.9.5",
|
||||||
|
|||||||
@@ -35,7 +35,37 @@ from immich_ml.sessions.ort import OrtSession
|
|||||||
from immich_ml.sessions.rknn import RknnSession, run_inference
|
from immich_ml.sessions.rknn import RknnSession, run_inference
|
||||||
|
|
||||||
|
|
||||||
|
class FakeLock:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.enter = mock.Mock()
|
||||||
|
self.exit = mock.Mock()
|
||||||
|
|
||||||
|
def __enter__(self) -> None:
|
||||||
|
self.enter()
|
||||||
|
|
||||||
|
def __exit__(self, *args: object) -> None:
|
||||||
|
self.exit(*args)
|
||||||
|
|
||||||
|
|
||||||
class TestBase:
|
class TestBase:
|
||||||
|
def test_sets_default_worker_timeout(self, monkeypatch: MonkeyPatch) -> None:
|
||||||
|
monkeypatch.delenv("DEVICE", raising=False)
|
||||||
|
monkeypatch.delenv("MACHINE_LEARNING_WORKER_TIMEOUT", raising=False)
|
||||||
|
|
||||||
|
assert Settings().worker_timeout == 300
|
||||||
|
|
||||||
|
def test_sets_rocm_default_worker_timeout(self, monkeypatch: MonkeyPatch) -> None:
|
||||||
|
monkeypatch.setenv("DEVICE", "rocm")
|
||||||
|
monkeypatch.delenv("MACHINE_LEARNING_WORKER_TIMEOUT", raising=False)
|
||||||
|
|
||||||
|
assert Settings().worker_timeout == 900
|
||||||
|
|
||||||
|
def test_worker_timeout_env_override(self, monkeypatch: MonkeyPatch) -> None:
|
||||||
|
monkeypatch.setenv("DEVICE", "rocm")
|
||||||
|
monkeypatch.setenv("MACHINE_LEARNING_WORKER_TIMEOUT", "1200")
|
||||||
|
|
||||||
|
assert Settings().worker_timeout == 1200
|
||||||
|
|
||||||
def test_sets_default_cache_dir(self) -> None:
|
def test_sets_default_cache_dir(self) -> None:
|
||||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||||
|
|
||||||
@@ -413,6 +443,52 @@ class TestOrtSession:
|
|||||||
|
|
||||||
assert sess_options is session.sess_options
|
assert sess_options is session.sess_options
|
||||||
|
|
||||||
|
def test_serializes_rocm_first_run_for_new_input_signature(self, mocker: MockerFixture) -> None:
|
||||||
|
lock = FakeLock()
|
||||||
|
get_model_lock = mocker.patch("immich_ml.sessions.ort._migraphx_get_model_lock", return_value=lock)
|
||||||
|
mocker.patch("immich_ml.sessions.ort._migraphx_compiled_inputs", set())
|
||||||
|
mocker.patch("immich_ml.sessions.ort.Path.mkdir")
|
||||||
|
session = OrtSession("/cache/ViT-B-32__openai/model.onnx", providers=["MIGraphXExecutionProvider"])
|
||||||
|
input_feed = {"input": np.random.rand(1, 3, 224, 224).astype(np.float32)}
|
||||||
|
|
||||||
|
session.run(None, input_feed)
|
||||||
|
session.run(None, input_feed)
|
||||||
|
|
||||||
|
lock.enter.assert_called_once()
|
||||||
|
lock.exit.assert_called_once()
|
||||||
|
get_model_lock.assert_called_once()
|
||||||
|
session.session.run.assert_has_calls([mock.call(None, input_feed, None), mock.call(None, input_feed, None)])
|
||||||
|
|
||||||
|
def test_serializes_rocm_run_for_each_new_input_signature(self, mocker: MockerFixture) -> None:
|
||||||
|
lock = FakeLock()
|
||||||
|
mocker.patch("immich_ml.sessions.ort._migraphx_get_model_lock", return_value=lock)
|
||||||
|
mocker.patch("immich_ml.sessions.ort._migraphx_compiled_inputs", set())
|
||||||
|
mocker.patch("immich_ml.sessions.ort.Path.mkdir")
|
||||||
|
session = OrtSession("/cache/ViT-B-32__openai/model.onnx", providers=["MIGraphXExecutionProvider"])
|
||||||
|
input_feed = {"input": np.random.rand(1, 3, 224, 224).astype(np.float32)}
|
||||||
|
new_shape_input_feed = {"input": np.random.rand(2, 3, 224, 224).astype(np.float32)}
|
||||||
|
|
||||||
|
session.run(None, input_feed)
|
||||||
|
session.run(None, new_shape_input_feed)
|
||||||
|
|
||||||
|
assert lock.enter.call_count == 2
|
||||||
|
assert lock.exit.call_count == 2
|
||||||
|
session.session.run.assert_has_calls(
|
||||||
|
[mock.call(None, input_feed, None), mock.call(None, new_shape_input_feed, None)]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_does_not_serialize_non_rocm_run(self, mocker: MockerFixture) -> None:
|
||||||
|
lock = FakeLock()
|
||||||
|
get_model_lock = mocker.patch("immich_ml.sessions.ort._migraphx_get_model_lock", return_value=lock)
|
||||||
|
session = OrtSession("/cache/ViT-B-32__openai/model.onnx", providers=["CPUExecutionProvider"])
|
||||||
|
input_feed = {"input": np.random.rand(1, 3, 224, 224).astype(np.float32)}
|
||||||
|
|
||||||
|
session.run(None, input_feed)
|
||||||
|
|
||||||
|
get_model_lock.assert_not_called()
|
||||||
|
lock.enter.assert_not_called()
|
||||||
|
session.session.run.assert_called_once_with(None, input_feed, None)
|
||||||
|
|
||||||
|
|
||||||
class TestAnnSession:
|
class TestAnnSession:
|
||||||
def test_creates_ann_session(self, ann_session: mock.Mock, info: mock.Mock) -> None:
|
def test_creates_ann_session(self, ann_session: mock.Mock, info: mock.Mock) -> None:
|
||||||
@@ -883,6 +959,34 @@ class TestFaceRecognition:
|
|||||||
onnx.load.assert_not_called()
|
onnx.load.assert_not_called()
|
||||||
onnx.save.assert_not_called()
|
onnx.save.assert_not_called()
|
||||||
|
|
||||||
|
def test_recognition_does_not_add_batch_axis_for_migraphx(
|
||||||
|
self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture
|
||||||
|
) -> None:
|
||||||
|
onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True)
|
||||||
|
update_dims = mocker.patch(
|
||||||
|
"immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True
|
||||||
|
)
|
||||||
|
mocker.patch("immich_ml.models.base.InferenceModel.download")
|
||||||
|
mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX")
|
||||||
|
mocker.patch(
|
||||||
|
"immich_ml.models.facial_recognition.recognition.ort.get_available_providers",
|
||||||
|
return_value=["MIGraphXExecutionProvider", "CPUExecutionProvider"],
|
||||||
|
)
|
||||||
|
path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx"
|
||||||
|
|
||||||
|
inputs = [SimpleNamespace(name="input.1", shape=(1, 3, 224, 224))]
|
||||||
|
outputs = [SimpleNamespace(name="output.1", shape=(1, 800))]
|
||||||
|
ort_session.return_value.get_inputs.return_value = inputs
|
||||||
|
ort_session.return_value.get_outputs.return_value = outputs
|
||||||
|
|
||||||
|
face_recognizer = FaceRecognizer("buffalo_s", cache_dir=path)
|
||||||
|
face_recognizer.load()
|
||||||
|
|
||||||
|
assert face_recognizer.batch_size == 1
|
||||||
|
update_dims.assert_not_called()
|
||||||
|
onnx.load.assert_not_called()
|
||||||
|
onnx.save.assert_not_called()
|
||||||
|
|
||||||
def test_set_custom_max_batch_size(self, mocker: MockerFixture) -> None:
|
def test_set_custom_max_batch_size(self, mocker: MockerFixture) -> None:
|
||||||
mocker.patch.object(settings, "max_batch_size", MaxBatchSize(facial_recognition=2))
|
mocker.patch.object(settings, "max_batch_size", MaxBatchSize(facial_recognition=2))
|
||||||
|
|
||||||
@@ -924,7 +1028,12 @@ class TestOcr:
|
|||||||
text_recognizer.load()
|
text_recognizer.load()
|
||||||
|
|
||||||
rapid_recognizer.assert_called_once_with(
|
rapid_recognizer.assert_called_once_with(
|
||||||
OcrOptions(session=ort_session.return_value, rec_batch_num=6, rec_img_shape=(3, 48, 320))
|
OcrOptions(
|
||||||
|
session=ort_session.return_value,
|
||||||
|
rec_batch_num=6,
|
||||||
|
rec_img_shape=(3, 48, 320),
|
||||||
|
model_root_dir=text_recognizer.cache_dir,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_set_custom_max_batch_size(self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture) -> None:
|
def test_set_custom_max_batch_size(self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture) -> None:
|
||||||
@@ -937,7 +1046,12 @@ class TestOcr:
|
|||||||
text_recognizer.load()
|
text_recognizer.load()
|
||||||
|
|
||||||
rapid_recognizer.assert_called_once_with(
|
rapid_recognizer.assert_called_once_with(
|
||||||
OcrOptions(session=ort_session.return_value, rec_batch_num=4, rec_img_shape=(3, 48, 320))
|
OcrOptions(
|
||||||
|
session=ort_session.return_value,
|
||||||
|
rec_batch_num=4,
|
||||||
|
rec_img_shape=(3, 48, 320),
|
||||||
|
model_root_dir=text_recognizer.cache_dir,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_ignore_other_custom_max_batch_size(
|
def test_ignore_other_custom_max_batch_size(
|
||||||
@@ -952,7 +1066,12 @@ class TestOcr:
|
|||||||
text_recognizer.load()
|
text_recognizer.load()
|
||||||
|
|
||||||
rapid_recognizer.assert_called_once_with(
|
rapid_recognizer.assert_called_once_with(
|
||||||
OcrOptions(session=ort_session.return_value, rec_batch_num=6, rec_img_shape=(3, 48, 320))
|
OcrOptions(
|
||||||
|
session=ort_session.return_value,
|
||||||
|
rec_batch_num=6,
|
||||||
|
rec_img_shape=(3, 48, 320),
|
||||||
|
model_root_dir=text_recognizer.cache_dir,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Generated
+1
-1
@@ -1004,7 +1004,7 @@ requires-dist = [
|
|||||||
{ name = "fastapi", specifier = ">=0.95.2,<1.0" },
|
{ name = "fastapi", specifier = ">=0.95.2,<1.0" },
|
||||||
{ name = "gunicorn", specifier = ">=21.1.0" },
|
{ name = "gunicorn", specifier = ">=21.1.0" },
|
||||||
{ name = "huggingface-hub", specifier = ">=1.0,<2.0" },
|
{ name = "huggingface-hub", specifier = ">=1.0,<2.0" },
|
||||||
{ name = "insightface", specifier = ">=0.7.3,<1.0" },
|
{ name = "insightface", specifier = ">=0.7.3,<2.0" },
|
||||||
{ name = "numpy", specifier = ">=2.4.0,<3.0" },
|
{ name = "numpy", specifier = ">=2.4.0,<3.0" },
|
||||||
{ name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" },
|
||||||
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" },
|
||||||
|
|||||||
@@ -1,9 +1,31 @@
|
|||||||
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
||||||
|
|
||||||
[[tools."aqua:flutter/flutter"]]
|
[[tools."aqua:flutter/flutter"]]
|
||||||
version = "3.41.9"
|
version = "3.44.0"
|
||||||
backend = "aqua:flutter/flutter"
|
backend = "aqua:flutter/flutter"
|
||||||
|
|
||||||
|
[tools."aqua:flutter/flutter"."platforms.linux-arm64"]
|
||||||
|
url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.0-stable.tar.xz"
|
||||||
|
|
||||||
|
[tools."aqua:flutter/flutter"."platforms.linux-arm64-musl"]
|
||||||
|
url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.0-stable.tar.xz"
|
||||||
|
|
||||||
|
[tools."aqua:flutter/flutter"."platforms.linux-x64"]
|
||||||
|
url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.0-stable.tar.xz"
|
||||||
|
|
||||||
|
[tools."aqua:flutter/flutter"."platforms.linux-x64-musl"]
|
||||||
|
url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.0-stable.tar.xz"
|
||||||
|
|
||||||
|
[tools."aqua:flutter/flutter"."platforms.macos-arm64"]
|
||||||
|
checksum = "blake3:fb03aa5d9790205c948922ec3f0751c16e4575b09d6ae9dd4fbeb664a69f0e00"
|
||||||
|
url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_arm64_3.44.0-stable.zip"
|
||||||
|
|
||||||
|
[tools."aqua:flutter/flutter"."platforms.macos-x64"]
|
||||||
|
url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_3.44.0-stable.zip"
|
||||||
|
|
||||||
|
[tools."aqua:flutter/flutter"."platforms.windows-x64"]
|
||||||
|
url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_3.44.0-stable.zip"
|
||||||
|
|
||||||
[[tools.flutter]]
|
[[tools.flutter]]
|
||||||
version = "3.41.9-stable"
|
version = "3.41.9-stable"
|
||||||
backend = "asdf:flutter"
|
backend = "asdf:flutter"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ config_roots = [
|
|||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
node = "24.15.0"
|
node = "24.15.0"
|
||||||
"aqua:flutter/flutter" = "3.41.9"
|
"aqua:flutter/flutter" = "3.44.0"
|
||||||
pnpm = "10.33.4"
|
pnpm = "10.33.4"
|
||||||
terragrunt = "1.0.3"
|
terragrunt = "1.0.3"
|
||||||
opentofu = "1.11.6"
|
opentofu = "1.11.6"
|
||||||
@@ -54,8 +54,8 @@ lockfile = true
|
|||||||
|
|
||||||
[tasks.plugins]
|
[tasks.plugins]
|
||||||
run = [
|
run = [
|
||||||
"pnpm --filter @immich/plugin-sdk --filter @immich/plugin-core install --frozen-lockfile",
|
"pnpm --filter @immich/sdk --filter @immich/plugin-sdk --filter @immich/plugin-core install --frozen-lockfile",
|
||||||
"pnpm --filter @immich/plugin-sdk --filter @immich/plugin-core build",
|
"pnpm --filter @immich/sdk --filter @immich/plugin-sdk --filter @immich/plugin-core build",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tasks.open-api-typescript]
|
[tasks.open-api-typescript]
|
||||||
@@ -73,7 +73,6 @@ run = "bash ./bin/generate-dart-sdk.sh"
|
|||||||
env = { SHARP_IGNORE_GLOBAL_LIBVIPS = true }
|
env = { SHARP_IGNORE_GLOBAL_LIBVIPS = true }
|
||||||
run = [
|
run = [
|
||||||
{ task = "//:plugins" },
|
{ task = "//:plugins" },
|
||||||
{ task = "//server:build" },
|
|
||||||
{ task = "//server:install" },
|
{ task = "//server:install" },
|
||||||
{ task = "//server:build" },
|
{ task = "//server:build" },
|
||||||
{ task = "//server:sync-open-api" },
|
{ task = "//server:sync-open-api" },
|
||||||
@@ -85,6 +84,72 @@ run = [
|
|||||||
dir = "server"
|
dir = "server"
|
||||||
run = "node ./dist/bin/sync-sql.js"
|
run = "node ./dist/bin/sync-sql.js"
|
||||||
|
|
||||||
|
# TODO dev, prod, and e2e should be de-duplicated by using env but for some reason I ran into issues
|
||||||
|
[tasks.dev]
|
||||||
|
depends = "//:plugins"
|
||||||
|
dir = "docker"
|
||||||
|
interactive = true
|
||||||
|
env = { COMPOSE_BAKE = true }
|
||||||
|
run = "docker compose -f ./docker-compose.dev.yml up --remove-orphans"
|
||||||
|
depends_post = "//:dev-down"
|
||||||
|
|
||||||
|
[tasks.dev-update]
|
||||||
|
run = { task = "//:dev", args = ["--build", "-V"] }
|
||||||
|
|
||||||
|
[tasks.dev-scale]
|
||||||
|
run = { task = "//:dev", args = ["--build", "-V", "--scale immich-server=3"] }
|
||||||
|
|
||||||
|
[tasks.dev-down]
|
||||||
|
dir = "docker"
|
||||||
|
run = "docker compose -f ./docker-compose.dev.yml down --remove-orphans"
|
||||||
|
|
||||||
|
[tasks.prod]
|
||||||
|
depends = "//:plugins"
|
||||||
|
dir = "docker"
|
||||||
|
interactive = true
|
||||||
|
env = { COMPOSE_BAKE = true }
|
||||||
|
run = "docker compose -f ./docker-compose.prod.yml up --build --remove-orphans"
|
||||||
|
depends_post = "//:prod-down"
|
||||||
|
|
||||||
|
[tasks.prod-scale]
|
||||||
|
run = { task = "//:prod", args = [
|
||||||
|
"--build",
|
||||||
|
"-V",
|
||||||
|
"--scale immich-server=3",
|
||||||
|
"--scale immich-microservices",
|
||||||
|
] }
|
||||||
|
|
||||||
|
[tasks.prod-down]
|
||||||
|
dir = "docker"
|
||||||
|
run = "docker compose -f ./docker-compose.prod.yml down --remove-orphans"
|
||||||
|
|
||||||
|
[tasks.e2e]
|
||||||
|
depends = "//:plugins"
|
||||||
|
dir = "e2e"
|
||||||
|
interactive = true
|
||||||
|
env = { COMPOSE_BAKE = true }
|
||||||
|
run = "docker compose -f ./docker-compose.yml up --remove-orphans"
|
||||||
|
depends_post = "//:e2e-down"
|
||||||
|
|
||||||
|
[tasks.e2e-dev]
|
||||||
|
depends = "//:plugins"
|
||||||
|
dir = "e2e"
|
||||||
|
interactive = true
|
||||||
|
env = { COMPOSE_BAKE = true }
|
||||||
|
run = "docker compose -f ./docker-compose.dev.yml up --remove-orphans"
|
||||||
|
depends_post = "//:e2e-dev-down"
|
||||||
|
|
||||||
|
[tasks.e2e-update]
|
||||||
|
run = { task = "//:e2e", args = ["--build", '-V'] }
|
||||||
|
|
||||||
|
[tasks.e2e-down]
|
||||||
|
dir = "e2e"
|
||||||
|
run = "docker compose -f ./docker-compose.yml down --remove-orphans"
|
||||||
|
|
||||||
|
[tasks.e2e-dev-down]
|
||||||
|
dir = "e2e"
|
||||||
|
run = "docker compose -f ./docker-compose.dev.yml down --remove-orphans"
|
||||||
|
|
||||||
# SDK tasks
|
# SDK tasks
|
||||||
[tasks."sdk:install"]
|
[tasks."sdk:install"]
|
||||||
dir = "packages/sdk"
|
dir = "packages/sdk"
|
||||||
@@ -100,3 +165,14 @@ run = "pnpm format"
|
|||||||
|
|
||||||
[tasks."i18n:format-fix"]
|
[tasks."i18n:format-fix"]
|
||||||
run = "pnpm format:fix"
|
run = "pnpm format:fix"
|
||||||
|
|
||||||
|
[tasks.clean]
|
||||||
|
run = [
|
||||||
|
"find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +",
|
||||||
|
"find . -name 'dist' -type d -prune -exec rm -rf '{}' +",
|
||||||
|
"find . -name 'build' -type d -prune -exec rm -rf '{}' +",
|
||||||
|
"find . -name '.svelte-kit' -type d -prune -exec rm -rf '{}' +",
|
||||||
|
"find . -name 'coverage' -type d -prune -exec rm -rf '{}' +",
|
||||||
|
"find . -name '.pnpm-store' -type d -prune -exec rm -rf '{}' +",
|
||||||
|
{ task = "//:*-down" },
|
||||||
|
]
|
||||||
|
|||||||
Vendored
-1
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"dart.flutterSdkPath": ".fvm/versions/3.41.9",
|
|
||||||
"dart.lineLength": 120,
|
"dart.lineLength": 120,
|
||||||
"[dart]": {
|
"[dart]": {
|
||||||
"editor.rulers": [
|
"editor.rulers": [
|
||||||
|
|||||||
@@ -5,3 +5,7 @@ android.nonTransitiveRClass=false
|
|||||||
android.nonFinalResIds=false
|
android.nonFinalResIds=false
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
|
# This builtInKotlin flag was added automatically by Flutter migrator
|
||||||
|
android.builtInKotlin=false
|
||||||
|
# This newDsl flag was added automatically by Flutter migrator
|
||||||
|
android.newDsl=false
|
||||||
|
|||||||
+3368
File diff suppressed because it is too large
Load Diff
@@ -1,58 +1,23 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- background_downloader (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- bonsoir_darwin (0.0.1):
|
- bonsoir_darwin (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- connectivity_plus (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- cupertino_http (0.0.1):
|
- cupertino_http (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- device_info_plus (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_local_notifications (0.0.1):
|
- flutter_local_notifications (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_native_splash (2.4.3):
|
|
||||||
- Flutter
|
|
||||||
- flutter_secure_storage (6.0.0):
|
- flutter_secure_storage (6.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_udid (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- KeychainAccess
|
|
||||||
- flutter_web_auth_2 (5.0.0):
|
|
||||||
- Flutter
|
|
||||||
- fluttertoast (0.0.2):
|
- fluttertoast (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- geolocator_apple (1.2.0):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- home_widget (0.0.1):
|
- home_widget (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- image_picker_ios (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- integration_test (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- KeychainAccess (4.2.2)
|
|
||||||
- local_auth_darwin (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- MapLibre (6.14.0)
|
|
||||||
- maplibre_gl (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- MapLibre (= 6.14.0)
|
|
||||||
- native_video_player (1.0.0):
|
- native_video_player (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- network_info_plus (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- package_info_plus (0.4.5):
|
|
||||||
- Flutter
|
|
||||||
- permission_handler_apple (9.3.0):
|
- permission_handler_apple (9.3.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- photo_manager (3.9.0):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- share_handler_ios (0.0.14):
|
- share_handler_ios (0.0.14):
|
||||||
- Flutter
|
- Flutter
|
||||||
- share_handler_ios/share_handler_ios_models (= 0.0.14)
|
- share_handler_ios/share_handler_ios_models (= 0.0.14)
|
||||||
@@ -61,144 +26,56 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- share_handler_ios_models
|
- share_handler_ios_models
|
||||||
- share_handler_ios_models (0.0.9)
|
- share_handler_ios_models (0.0.9)
|
||||||
- share_plus (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- shared_preferences_foundation (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- url_launcher_ios (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- wakelock_plus (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- background_downloader (from `.symlinks/plugins/background_downloader/ios`)
|
|
||||||
- bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`)
|
- bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`)
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
|
||||||
- cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`)
|
- cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
|
|
||||||
- flutter_web_auth_2 (from `.symlinks/plugins/flutter_web_auth_2/ios`)
|
|
||||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||||
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
|
|
||||||
- home_widget (from `.symlinks/plugins/home_widget/ios`)
|
- home_widget (from `.symlinks/plugins/home_widget/ios`)
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
|
||||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
|
||||||
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
|
||||||
- maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`)
|
|
||||||
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
|
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
|
||||||
- network_info_plus (from `.symlinks/plugins/network_info_plus/ios`)
|
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
- photo_manager (from `.symlinks/plugins/photo_manager/darwin`)
|
|
||||||
- share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`)
|
- share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`)
|
||||||
- share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`)
|
- share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
|
||||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
|
||||||
|
|
||||||
SPEC REPOS:
|
|
||||||
trunk:
|
|
||||||
- KeychainAccess
|
|
||||||
- MapLibre
|
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
background_downloader:
|
|
||||||
:path: ".symlinks/plugins/background_downloader/ios"
|
|
||||||
bonsoir_darwin:
|
bonsoir_darwin:
|
||||||
:path: ".symlinks/plugins/bonsoir_darwin/darwin"
|
:path: ".symlinks/plugins/bonsoir_darwin/darwin"
|
||||||
connectivity_plus:
|
|
||||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
|
||||||
cupertino_http:
|
cupertino_http:
|
||||||
:path: ".symlinks/plugins/cupertino_http/darwin"
|
:path: ".symlinks/plugins/cupertino_http/darwin"
|
||||||
device_info_plus:
|
|
||||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_local_notifications:
|
flutter_local_notifications:
|
||||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||||
flutter_native_splash:
|
|
||||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||||
flutter_udid:
|
|
||||||
:path: ".symlinks/plugins/flutter_udid/ios"
|
|
||||||
flutter_web_auth_2:
|
|
||||||
:path: ".symlinks/plugins/flutter_web_auth_2/ios"
|
|
||||||
fluttertoast:
|
fluttertoast:
|
||||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||||
geolocator_apple:
|
|
||||||
:path: ".symlinks/plugins/geolocator_apple/darwin"
|
|
||||||
home_widget:
|
home_widget:
|
||||||
:path: ".symlinks/plugins/home_widget/ios"
|
:path: ".symlinks/plugins/home_widget/ios"
|
||||||
image_picker_ios:
|
|
||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
|
||||||
integration_test:
|
|
||||||
:path: ".symlinks/plugins/integration_test/ios"
|
|
||||||
local_auth_darwin:
|
|
||||||
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
|
||||||
maplibre_gl:
|
|
||||||
:path: ".symlinks/plugins/maplibre_gl/ios"
|
|
||||||
native_video_player:
|
native_video_player:
|
||||||
:path: ".symlinks/plugins/native_video_player/ios"
|
:path: ".symlinks/plugins/native_video_player/ios"
|
||||||
network_info_plus:
|
|
||||||
:path: ".symlinks/plugins/network_info_plus/ios"
|
|
||||||
package_info_plus:
|
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
photo_manager:
|
|
||||||
:path: ".symlinks/plugins/photo_manager/darwin"
|
|
||||||
share_handler_ios:
|
share_handler_ios:
|
||||||
:path: ".symlinks/plugins/share_handler_ios/ios"
|
:path: ".symlinks/plugins/share_handler_ios/ios"
|
||||||
share_handler_ios_models:
|
share_handler_ios_models:
|
||||||
:path: ".symlinks/plugins/share_handler_ios/ios/Models"
|
:path: ".symlinks/plugins/share_handler_ios/ios/Models"
|
||||||
share_plus:
|
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
|
||||||
shared_preferences_foundation:
|
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
|
||||||
url_launcher_ios:
|
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
|
||||||
wakelock_plus:
|
|
||||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
|
||||||
bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e
|
bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e
|
||||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
|
||||||
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
|
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
|
||||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
|
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
|
||||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
|
||||||
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
||||||
flutter_udid: 92a5d31fe0526b7b6002a2318df702e12e7eb300
|
|
||||||
flutter_web_auth_2: 646fc9df97a01c59e5eea99b237da2c6360f8439
|
|
||||||
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
|
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
|
||||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
|
||||||
home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
|
home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
|
||||||
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
|
||||||
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
|
|
||||||
KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51
|
|
||||||
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
|
||||||
MapLibre: 69e572367f4ef6287e18246cfafc39c80cdcabcd
|
|
||||||
maplibre_gl: 3c924e44725147b03dda33430ad216005b40555f
|
|
||||||
native_video_player: b65c58951ede2f93d103a25366bdebca95081265
|
native_video_player: b65c58951ede2f93d103a25366bdebca95081265
|
||||||
network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc
|
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
|
||||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||||
photo_manager: 25fd77df14f4f0ba5ef99e2c61814dde77e2bceb
|
|
||||||
share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb
|
share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb
|
||||||
share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871
|
share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871
|
||||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
|
||||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
|
||||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
|
||||||
|
|
||||||
PODFILE CHECKSUM: 938abbae4114b9c2140c550a2a0d8f7c674f5dfe
|
PODFILE CHECKSUM: 938abbae4114b9c2140c550a2a0d8f7c674f5dfe
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
FEE084F82EC172460045228E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084F72EC172460045228E /* SQLiteData */; };
|
FEE084F82EC172460045228E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084F72EC172460045228E /* SQLiteData */; };
|
||||||
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FA2EC1725A0045228E /* RawStructuredFieldValues */; };
|
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FA2EC1725A0045228E /* RawStructuredFieldValues */; };
|
||||||
FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FC2EC1725A0045228E /* StructuredFieldValues */; };
|
FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FC2EC1725A0045228E /* StructuredFieldValues */; };
|
||||||
|
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -129,6 +130,7 @@
|
|||||||
FE5499F72F1198DE006016CB /* RemoteImagesImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImagesImpl.swift; sourceTree = "<group>"; };
|
FE5499F72F1198DE006016CB /* RemoteImagesImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImagesImpl.swift; sourceTree = "<group>"; };
|
||||||
FE5FE4AD2F30FBC000A71243 /* ImageProcessing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProcessing.swift; sourceTree = "<group>"; };
|
FE5FE4AD2F30FBC000A71243 /* ImageProcessing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProcessing.swift; sourceTree = "<group>"; };
|
||||||
FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbhash.swift; sourceTree = "<group>"; };
|
FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbhash.swift; sourceTree = "<group>"; };
|
||||||
|
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
@@ -193,6 +195,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
||||||
FEE084F82EC172460045228E /* SQLiteData in Frameworks */,
|
FEE084F82EC172460045228E /* SQLiteData in Frameworks */,
|
||||||
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */,
|
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */,
|
||||||
FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */,
|
FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */,
|
||||||
@@ -247,6 +250,7 @@
|
|||||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
@@ -360,6 +364,9 @@
|
|||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
packageProductDependencies = (
|
||||||
|
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||||
|
);
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
@@ -463,6 +470,7 @@
|
|||||||
);
|
);
|
||||||
mainGroup = 97C146E51CF9000F007C117D;
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
|
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||||
FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */,
|
FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */,
|
||||||
FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */,
|
FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */,
|
||||||
);
|
);
|
||||||
@@ -1285,7 +1293,17 @@
|
|||||||
package = FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */;
|
package = FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */;
|
||||||
productName = StructuredFieldValues;
|
productName = StructuredFieldValues;
|
||||||
};
|
};
|
||||||
|
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = FlutterGeneratedPluginSwiftPackage;
|
||||||
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
|
/* Begin XCLocalSwiftPackageReference section */
|
||||||
|
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
||||||
|
isa = XCLocalSwiftPackageReference;
|
||||||
|
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
||||||
|
};
|
||||||
|
/* End XCLocalSwiftPackageReference section */
|
||||||
};
|
};
|
||||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-2
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "9be33bfaa68721646604aefff3cabbdaf9a193da192aae024c265065671f6c49",
|
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "combine-schedulers",
|
"identity" : "combine-schedulers",
|
||||||
@@ -19,6 +18,24 @@
|
|||||||
"version" : "7.8.0"
|
"version" : "7.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "keychainaccess",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/kishikawakatsumi/KeychainAccess",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
|
||||||
|
"version" : "4.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "maplibre-gl-native-distribution",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/maplibre/maplibre-gl-native-distribution.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "60d9bb85c94ce6e7fc4406cd32529fd12bdb7809",
|
||||||
|
"version" : "6.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "sqlite-data",
|
"identity" : "sqlite-data",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -146,5 +163,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 3
|
"version" : 2
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,24 @@
|
|||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
|
<PreActions>
|
||||||
|
<ExecutionAction
|
||||||
|
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||||
|
<ActionContent
|
||||||
|
title = "Run Prepare Flutter Framework Script"
|
||||||
|
scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
||||||
|
<EnvironmentBuildable>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Immich.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</EnvironmentBuildable>
|
||||||
|
</ActionContent>
|
||||||
|
</ExecutionAction>
|
||||||
|
</PreActions>
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "9be33bfaa68721646604aefff3cabbdaf9a193da192aae024c265065671f6c49",
|
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "combine-schedulers",
|
"identity" : "combine-schedulers",
|
||||||
@@ -19,6 +18,24 @@
|
|||||||
"version" : "7.9.0"
|
"version" : "7.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "keychainaccess",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/kishikawakatsumi/KeychainAccess",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
|
||||||
|
"version" : "4.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "maplibre-gl-native-distribution",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/maplibre/maplibre-gl-native-distribution.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "60d9bb85c94ce6e7fc4406cd32529fd12bdb7809",
|
||||||
|
"version" : "6.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "sqlite-data",
|
"identity" : "sqlite-data",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -146,5 +163,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 3
|
"version" : 2
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const String kBackupLivePhotoGroup = 'backup_live_photo_group';
|
|||||||
const String kDownloadGroupImage = 'group_image';
|
const String kDownloadGroupImage = 'group_image';
|
||||||
const String kDownloadGroupVideo = 'group_video';
|
const String kDownloadGroupVideo = 'group_video';
|
||||||
const String kDownloadGroupLivePhoto = 'group_livephoto';
|
const String kDownloadGroupLivePhoto = 'group_livephoto';
|
||||||
|
const String kShareDownloadGroup = 'group_share';
|
||||||
|
|
||||||
// Timeline constants
|
// Timeline constants
|
||||||
const int kTimelineNoneSegmentSize = 120;
|
const int kTimelineNoneSegmentSize = 120;
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:immich_mobile/constants/colors.dart';
|
||||||
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/album_config.dart';
|
import 'package:immich_mobile/domain/models/config/album_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/backup_config.dart';
|
import 'package:immich_mobile/domain/models/config/backup_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/cleanup_config.dart';
|
import 'package:immich_mobile/domain/models/config/cleanup_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/image_config.dart';
|
import 'package:immich_mobile/domain/models/config/image_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/map_config.dart';
|
import 'package:immich_mobile/domain/models/config/map_config.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/config/network_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/slideshow_config.dart';
|
import 'package:immich_mobile/domain/models/config/slideshow_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/theme_config.dart';
|
import 'package:immich_mobile/domain/models/config/theme_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/timeline_config.dart';
|
import 'package:immich_mobile/domain/models/config/timeline_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/viewer_config.dart';
|
import 'package:immich_mobile/domain/models/config/viewer_config.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/settings_key.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
|
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||||
|
|
||||||
|
const defaultConfig = AppConfig();
|
||||||
|
|
||||||
class AppConfig {
|
class AppConfig {
|
||||||
|
final LogLevel logLevel;
|
||||||
final ThemeConfig theme;
|
final ThemeConfig theme;
|
||||||
final CleanupConfig cleanup;
|
final CleanupConfig cleanup;
|
||||||
final MapConfig map;
|
final MapConfig map;
|
||||||
@@ -18,8 +29,10 @@ class AppConfig {
|
|||||||
final SlideshowConfig slideshow;
|
final SlideshowConfig slideshow;
|
||||||
final AlbumConfig album;
|
final AlbumConfig album;
|
||||||
final BackupConfig backup;
|
final BackupConfig backup;
|
||||||
|
final NetworkConfig network;
|
||||||
|
|
||||||
const AppConfig({
|
const AppConfig({
|
||||||
|
this.logLevel = .info,
|
||||||
this.theme = const .new(),
|
this.theme = const .new(),
|
||||||
this.cleanup = const .new(),
|
this.cleanup = const .new(),
|
||||||
this.map = const .new(),
|
this.map = const .new(),
|
||||||
@@ -29,9 +42,11 @@ class AppConfig {
|
|||||||
this.slideshow = const .new(),
|
this.slideshow = const .new(),
|
||||||
this.album = const .new(),
|
this.album = const .new(),
|
||||||
this.backup = const .new(),
|
this.backup = const .new(),
|
||||||
|
this.network = const .new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
AppConfig copyWith({
|
AppConfig copyWith({
|
||||||
|
LogLevel? logLevel,
|
||||||
ThemeConfig? theme,
|
ThemeConfig? theme,
|
||||||
CleanupConfig? cleanup,
|
CleanupConfig? cleanup,
|
||||||
MapConfig? map,
|
MapConfig? map,
|
||||||
@@ -41,7 +56,9 @@ class AppConfig {
|
|||||||
SlideshowConfig? slideshow,
|
SlideshowConfig? slideshow,
|
||||||
AlbumConfig? album,
|
AlbumConfig? album,
|
||||||
BackupConfig? backup,
|
BackupConfig? backup,
|
||||||
|
NetworkConfig? network,
|
||||||
}) => .new(
|
}) => .new(
|
||||||
|
logLevel: logLevel ?? this.logLevel,
|
||||||
theme: theme ?? this.theme,
|
theme: theme ?? this.theme,
|
||||||
cleanup: cleanup ?? this.cleanup,
|
cleanup: cleanup ?? this.cleanup,
|
||||||
map: map ?? this.map,
|
map: map ?? this.map,
|
||||||
@@ -51,12 +68,14 @@ class AppConfig {
|
|||||||
slideshow: slideshow ?? this.slideshow,
|
slideshow: slideshow ?? this.slideshow,
|
||||||
album: album ?? this.album,
|
album: album ?? this.album,
|
||||||
backup: backup ?? this.backup,
|
backup: backup ?? this.backup,
|
||||||
|
network: network ?? this.network,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
(other is AppConfig &&
|
(other is AppConfig &&
|
||||||
|
other.logLevel == logLevel &&
|
||||||
other.theme == theme &&
|
other.theme == theme &&
|
||||||
other.cleanup == cleanup &&
|
other.cleanup == cleanup &&
|
||||||
other.map == map &&
|
other.map == map &&
|
||||||
@@ -65,12 +84,113 @@ class AppConfig {
|
|||||||
other.viewer == viewer &&
|
other.viewer == viewer &&
|
||||||
other.slideshow == slideshow &&
|
other.slideshow == slideshow &&
|
||||||
other.album == album &&
|
other.album == album &&
|
||||||
other.backup == backup);
|
other.backup == backup &&
|
||||||
|
other.network == network);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(theme, cleanup, map, timeline, image, viewer, slideshow, album, backup);
|
int get hashCode =>
|
||||||
|
Object.hash(logLevel, theme, cleanup, map, timeline, image, viewer, slideshow, album, backup, network);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'AppConfig(theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup)';
|
'AppConfig(logLevel: $logLevel, theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup, network: $network)';
|
||||||
|
|
||||||
|
T read<T extends Object>(SettingsKey<T> key) =>
|
||||||
|
(switch (key) {
|
||||||
|
.logLevel => logLevel,
|
||||||
|
.themePrimaryColor => theme.primaryColor,
|
||||||
|
.themeMode => theme.mode,
|
||||||
|
.themeDynamic => theme.dynamicTheme,
|
||||||
|
.themeColorfulInterface => theme.colorfulInterface,
|
||||||
|
.imagePreferRemote => image.preferRemote,
|
||||||
|
.imageLoadOriginal => image.loadOriginal,
|
||||||
|
.viewerLoopVideo => viewer.loopVideo,
|
||||||
|
.viewerLoadOriginalVideo => viewer.loadOriginalVideo,
|
||||||
|
.viewerAutoPlayVideo => viewer.autoPlayVideo,
|
||||||
|
.viewerTapToNavigate => viewer.tapToNavigate,
|
||||||
|
.networkAutoEndpointSwitching => network.autoEndpointSwitching,
|
||||||
|
.networkPreferredWifiName => network.preferredWifiName,
|
||||||
|
.networkLocalEndpoint => network.localEndpoint,
|
||||||
|
.networkExternalEndpointList => network.externalEndpointList,
|
||||||
|
.networkCustomHeaders => network.customHeaders,
|
||||||
|
.albumSortMode => album.sortMode,
|
||||||
|
.albumIsReverse => album.isReverse,
|
||||||
|
.albumIsGrid => album.isGrid,
|
||||||
|
.backupEnabled => backup.enabled,
|
||||||
|
.backupUseCellularForVideos => backup.useCellularForVideos,
|
||||||
|
.backupUseCellularForPhotos => backup.useCellularForPhotos,
|
||||||
|
.backupRequireCharging => backup.requireCharging,
|
||||||
|
.backupTriggerDelay => backup.triggerDelay,
|
||||||
|
.backupSyncAlbums => backup.syncAlbums,
|
||||||
|
.timelineTilesPerRow => timeline.tilesPerRow,
|
||||||
|
.timelineGroupAssetsBy => timeline.groupAssetsBy,
|
||||||
|
.timelineStorageIndicator => timeline.storageIndicator,
|
||||||
|
.mapShowFavoriteOnly => map.favoritesOnly,
|
||||||
|
.mapRelativeDate => map.relativeDays,
|
||||||
|
.mapIncludeArchived => map.includeArchived,
|
||||||
|
.mapThemeMode => map.themeMode,
|
||||||
|
.mapWithPartners => map.withPartners,
|
||||||
|
.cleanupKeepFavorites => cleanup.keepFavorites,
|
||||||
|
.cleanupKeepMediaType => cleanup.keepMediaType,
|
||||||
|
.cleanupKeepAlbumIds => cleanup.keepAlbumIds,
|
||||||
|
.cleanupCutoffDaysAgo => cleanup.cutoffDaysAgo,
|
||||||
|
.cleanupDefaultsInitialized => cleanup.defaultsInitialized,
|
||||||
|
.slideshowTransition => slideshow.transition,
|
||||||
|
.slideshowRepeat => slideshow.repeat,
|
||||||
|
.slideshowDuration => slideshow.duration,
|
||||||
|
.slideshowLook => slideshow.look,
|
||||||
|
.slideshowDirection => slideshow.direction,
|
||||||
|
})
|
||||||
|
as T;
|
||||||
|
|
||||||
|
factory AppConfig.fromEntries(Map<SettingsKey<Object>, Object> overrides) =>
|
||||||
|
overrides.entries.fold(const AppConfig(), (config, entry) => config.write(entry.key, entry.value));
|
||||||
|
|
||||||
|
AppConfig write<T extends Object>(SettingsKey<T> key, T value) {
|
||||||
|
return switch (key) {
|
||||||
|
.logLevel => copyWith(logLevel: value as LogLevel),
|
||||||
|
.themePrimaryColor => copyWith(theme: theme.copyWith(primaryColor: value as ImmichColorPreset)),
|
||||||
|
.themeMode => copyWith(theme: theme.copyWith(mode: value as ThemeMode)),
|
||||||
|
.themeDynamic => copyWith(theme: theme.copyWith(dynamicTheme: value as bool)),
|
||||||
|
.themeColorfulInterface => copyWith(theme: theme.copyWith(colorfulInterface: value as bool)),
|
||||||
|
.imagePreferRemote => copyWith(image: image.copyWith(preferRemote: value as bool)),
|
||||||
|
.imageLoadOriginal => copyWith(image: image.copyWith(loadOriginal: value as bool)),
|
||||||
|
.viewerLoopVideo => copyWith(viewer: viewer.copyWith(loopVideo: value as bool)),
|
||||||
|
.viewerLoadOriginalVideo => copyWith(viewer: viewer.copyWith(loadOriginalVideo: value as bool)),
|
||||||
|
.viewerAutoPlayVideo => copyWith(viewer: viewer.copyWith(autoPlayVideo: value as bool)),
|
||||||
|
.viewerTapToNavigate => copyWith(viewer: viewer.copyWith(tapToNavigate: value as bool)),
|
||||||
|
.networkAutoEndpointSwitching => copyWith(network: network.copyWith(autoEndpointSwitching: value as bool)),
|
||||||
|
.networkPreferredWifiName => copyWith(network: network.copyWith(preferredWifiName: (value as String))),
|
||||||
|
.networkLocalEndpoint => copyWith(network: network.copyWith(localEndpoint: (value as String))),
|
||||||
|
.networkExternalEndpointList => copyWith(network: network.copyWith(externalEndpointList: value as List<String>)),
|
||||||
|
.networkCustomHeaders => copyWith(network: network.copyWith(customHeaders: value as Map<String, String>)),
|
||||||
|
.albumSortMode => copyWith(album: album.copyWith(sortMode: value as AlbumSortMode)),
|
||||||
|
.albumIsReverse => copyWith(album: album.copyWith(isReverse: value as bool)),
|
||||||
|
.albumIsGrid => copyWith(album: album.copyWith(isGrid: value as bool)),
|
||||||
|
.backupEnabled => copyWith(backup: backup.copyWith(enabled: value as bool)),
|
||||||
|
.backupUseCellularForVideos => copyWith(backup: backup.copyWith(useCellularForVideos: value as bool)),
|
||||||
|
.backupUseCellularForPhotos => copyWith(backup: backup.copyWith(useCellularForPhotos: value as bool)),
|
||||||
|
.backupRequireCharging => copyWith(backup: backup.copyWith(requireCharging: value as bool)),
|
||||||
|
.backupTriggerDelay => copyWith(backup: backup.copyWith(triggerDelay: value as int)),
|
||||||
|
.backupSyncAlbums => copyWith(backup: backup.copyWith(syncAlbums: value as bool)),
|
||||||
|
.timelineTilesPerRow => copyWith(timeline: timeline.copyWith(tilesPerRow: value as int)),
|
||||||
|
.timelineGroupAssetsBy => copyWith(timeline: timeline.copyWith(groupAssetsBy: value as GroupAssetsBy)),
|
||||||
|
.timelineStorageIndicator => copyWith(timeline: timeline.copyWith(storageIndicator: value as bool)),
|
||||||
|
.mapShowFavoriteOnly => copyWith(map: map.copyWith(favoritesOnly: value as bool)),
|
||||||
|
.mapRelativeDate => copyWith(map: map.copyWith(relativeDays: value as int)),
|
||||||
|
.mapIncludeArchived => copyWith(map: map.copyWith(includeArchived: value as bool)),
|
||||||
|
.mapThemeMode => copyWith(map: map.copyWith(themeMode: value as ThemeMode)),
|
||||||
|
.mapWithPartners => copyWith(map: map.copyWith(withPartners: value as bool)),
|
||||||
|
.cleanupKeepFavorites => copyWith(cleanup: cleanup.copyWith(keepFavorites: value as bool)),
|
||||||
|
.cleanupKeepMediaType => copyWith(cleanup: cleanup.copyWith(keepMediaType: value as AssetKeepType)),
|
||||||
|
.cleanupKeepAlbumIds => copyWith(cleanup: cleanup.copyWith(keepAlbumIds: value as List<String>)),
|
||||||
|
.cleanupCutoffDaysAgo => copyWith(cleanup: cleanup.copyWith(cutoffDaysAgo: value as int)),
|
||||||
|
.cleanupDefaultsInitialized => copyWith(cleanup: cleanup.copyWith(defaultsInitialized: value as bool)),
|
||||||
|
.slideshowTransition => copyWith(slideshow: slideshow.copyWith(transition: value as bool)),
|
||||||
|
.slideshowRepeat => copyWith(slideshow: slideshow.copyWith(repeat: value as bool)),
|
||||||
|
.slideshowDuration => copyWith(slideshow: slideshow.copyWith(duration: value as int)),
|
||||||
|
.slideshowLook => copyWith(slideshow: slideshow.copyWith(look: value as SlideshowLook)),
|
||||||
|
.slideshowDirection => copyWith(slideshow: slideshow.copyWith(direction: value as SlideshowDirection)),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import 'package:flutter/foundation.dart';
|
|||||||
|
|
||||||
class NetworkConfig {
|
class NetworkConfig {
|
||||||
final bool autoEndpointSwitching;
|
final bool autoEndpointSwitching;
|
||||||
final String? preferredWifiName;
|
final String preferredWifiName;
|
||||||
final String? localEndpoint;
|
final String localEndpoint;
|
||||||
final List<String> externalEndpointList;
|
final List<String> externalEndpointList;
|
||||||
final Map<String, String> customHeaders;
|
final Map<String, String> customHeaders;
|
||||||
|
|
||||||
const NetworkConfig({
|
const NetworkConfig({
|
||||||
this.autoEndpointSwitching = false,
|
this.autoEndpointSwitching = false,
|
||||||
this.preferredWifiName,
|
this.preferredWifiName = '',
|
||||||
this.localEndpoint,
|
this.localEndpoint = '',
|
||||||
this.externalEndpointList = const [],
|
this.externalEndpointList = const [],
|
||||||
this.customHeaders = const {},
|
this.customHeaders = const {},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import 'package:immich_mobile/domain/models/config/network_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
|
||||||
|
|
||||||
class SystemConfig {
|
|
||||||
final LogLevel logLevel;
|
|
||||||
final NetworkConfig network;
|
|
||||||
|
|
||||||
const SystemConfig({this.logLevel = .info, this.network = const .new()});
|
|
||||||
|
|
||||||
SystemConfig copyWith({LogLevel? logLevel, NetworkConfig? network}) =>
|
|
||||||
SystemConfig(logLevel: logLevel ?? this.logLevel, network: network ?? this.network);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) || (other is SystemConfig && other.logLevel == logLevel && other.network == network);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(logLevel, network);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'SystemConfig(logLevel: $logLevel, network: $network)';
|
|
||||||
}
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:immich_mobile/constants/colors.dart';
|
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/system_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
|
||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
|
||||||
|
|
||||||
enum MetadataDomain<T extends Object> {
|
|
||||||
appConfig<AppConfig>('config.app'),
|
|
||||||
systemConfig<SystemConfig>('config.system');
|
|
||||||
|
|
||||||
final String prefix;
|
|
||||||
const MetadataDomain(this.prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MetadataKey<T extends Object> {
|
|
||||||
// Theme
|
|
||||||
themePrimaryColor<ImmichColorPreset>(.appConfig, 'theme.primaryColor', .indigo, _EnumCodec(ImmichColorPreset.values)),
|
|
||||||
themeMode<ThemeMode>(.appConfig, 'theme.mode', .system, _EnumCodec(ThemeMode.values)),
|
|
||||||
themeDynamic<bool>(.appConfig, 'theme.dynamic', false),
|
|
||||||
themeColorfulInterface<bool>(.appConfig, 'theme.colorfulInterface', true),
|
|
||||||
|
|
||||||
// Image
|
|
||||||
imagePreferRemote<bool>(.appConfig, 'image.preferRemote', false),
|
|
||||||
imageLoadOriginal<bool>(.appConfig, 'image.loadOriginal', false),
|
|
||||||
|
|
||||||
// Viewer
|
|
||||||
viewerLoopVideo<bool>(.appConfig, 'viewer.loopVideo', true),
|
|
||||||
viewerLoadOriginalVideo<bool>(.appConfig, 'viewer.loadOriginalVideo', false),
|
|
||||||
viewerAutoPlayVideo<bool>(.appConfig, 'viewer.autoPlayVideo', true),
|
|
||||||
viewerTapToNavigate<bool>(.appConfig, 'viewer.tapToNavigate', false),
|
|
||||||
|
|
||||||
// Network
|
|
||||||
networkAutoEndpointSwitching<bool>(.systemConfig, 'network.autoEndpointSwitching', false),
|
|
||||||
networkPreferredWifiName<String>(.systemConfig, 'network.preferredWifiName', ''),
|
|
||||||
networkLocalEndpoint<String>(.systemConfig, 'network.localEndpoint', ''),
|
|
||||||
networkExternalEndpointList<List<String>>(
|
|
||||||
.systemConfig,
|
|
||||||
'network.externalEndpointList',
|
|
||||||
[],
|
|
||||||
_ListCodec(_PrimitiveCodec.string),
|
|
||||||
),
|
|
||||||
networkCustomHeaders<Map<String, String>>(
|
|
||||||
.systemConfig,
|
|
||||||
'network.customHeaders',
|
|
||||||
{},
|
|
||||||
_MapCodec(_PrimitiveCodec.string, _PrimitiveCodec.string),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Album
|
|
||||||
albumSortMode<AlbumSortMode>(
|
|
||||||
.appConfig,
|
|
||||||
'album.sortMode',
|
|
||||||
AlbumSortMode.mostRecent,
|
|
||||||
_EnumCodec(AlbumSortMode.values),
|
|
||||||
),
|
|
||||||
albumIsReverse<bool>(.appConfig, 'album.isReverse', true),
|
|
||||||
albumIsGrid<bool>(.appConfig, 'album.isGrid', false),
|
|
||||||
|
|
||||||
// Backup
|
|
||||||
backupEnabled<bool>(.appConfig, 'backup.enabled', false),
|
|
||||||
backupUseCellularForVideos<bool>(.appConfig, 'backup.useCellularForVideos', false),
|
|
||||||
backupUseCellularForPhotos<bool>(.appConfig, 'backup.useCellularForPhotos', false),
|
|
||||||
backupRequireCharging<bool>(.appConfig, 'backup.requireCharging', false),
|
|
||||||
backupTriggerDelay<int>(.appConfig, 'backup.triggerDelay', 30),
|
|
||||||
backupSyncAlbums<bool>(.appConfig, 'backup.syncAlbums', false),
|
|
||||||
|
|
||||||
// Timeline
|
|
||||||
timelineTilesPerRow<int>(.appConfig, 'timeline.tilesPerRow', 4),
|
|
||||||
timelineGroupAssetsBy<GroupAssetsBy>(
|
|
||||||
.appConfig,
|
|
||||||
'timeline.groupAssetsBy',
|
|
||||||
GroupAssetsBy.day,
|
|
||||||
_EnumCodec(GroupAssetsBy.values),
|
|
||||||
),
|
|
||||||
timelineStorageIndicator<bool>(.appConfig, 'timeline.storageIndicator', true),
|
|
||||||
|
|
||||||
// Log
|
|
||||||
logLevel<LogLevel>(.systemConfig, 'log.level', .info, _EnumCodec(LogLevel.values)),
|
|
||||||
|
|
||||||
// Map
|
|
||||||
mapShowFavoriteOnly<bool>(.appConfig, 'map.showFavoriteOnly', false),
|
|
||||||
mapRelativeDate<int>(.appConfig, 'map.relativeDate', 0),
|
|
||||||
mapIncludeArchived<bool>(.appConfig, 'map.includeArchived', false),
|
|
||||||
mapThemeMode<ThemeMode>(.appConfig, 'map.themeMode', .system, _EnumCodec(ThemeMode.values)),
|
|
||||||
mapWithPartners<bool>(.appConfig, 'map.withPartners', false),
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
cleanupKeepFavorites<bool>(.appConfig, 'cleanup.keepFavorites', true),
|
|
||||||
cleanupKeepMediaType<AssetKeepType>(
|
|
||||||
.appConfig,
|
|
||||||
'cleanup.keepMediaType',
|
|
||||||
AssetKeepType.none,
|
|
||||||
_EnumCodec(AssetKeepType.values),
|
|
||||||
),
|
|
||||||
cleanupKeepAlbumIds<List<String>>(.appConfig, 'cleanup.keepAlbumIds', [], _ListCodec(_PrimitiveCodec.string)),
|
|
||||||
cleanupCutoffDaysAgo<int>(.appConfig, 'cleanup.cutoffDaysAgo', -1),
|
|
||||||
cleanupDefaultsInitialized<bool>(.appConfig, 'cleanup.defaultsInitialized', false),
|
|
||||||
|
|
||||||
// Slideshow
|
|
||||||
slideshowTransition<bool>(.appConfig, 'slideshow.transition', true),
|
|
||||||
slideshowRepeat<bool>(.appConfig, 'slideshow.repeat', true),
|
|
||||||
slideshowDuration<int>(.appConfig, 'slideshow.duration', 5),
|
|
||||||
slideshowLook<SlideshowLook>(.appConfig, 'slideshow.look', SlideshowLook.contain, _EnumCodec(SlideshowLook.values)),
|
|
||||||
slideshowDirection<SlideshowDirection>(
|
|
||||||
.appConfig,
|
|
||||||
'slideshow.direction',
|
|
||||||
SlideshowDirection.forward,
|
|
||||||
_EnumCodec(SlideshowDirection.values),
|
|
||||||
);
|
|
||||||
|
|
||||||
final MetadataDomain domain;
|
|
||||||
final String name;
|
|
||||||
final T defaultValue;
|
|
||||||
final _MetadataCodec<T>? _codecOverride;
|
|
||||||
|
|
||||||
const MetadataKey(this.domain, this.name, this.defaultValue, [this._codecOverride]);
|
|
||||||
|
|
||||||
String get key => '${domain.prefix}.$name';
|
|
||||||
|
|
||||||
_MetadataCodec<T> get _codec => _codecOverride ?? _MetadataCodec.forPrimitive(defaultValue);
|
|
||||||
|
|
||||||
String encode(T value) => _codec.encode(value);
|
|
||||||
|
|
||||||
T decode(String raw) => _codec.decode(raw) ?? defaultValue;
|
|
||||||
|
|
||||||
static Map<String, MetadataKey<Object>> asKeyMap() => {for (var value in MetadataKey.values) value.key: value};
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class _MetadataCodec<T extends Object> {
|
|
||||||
const _MetadataCodec();
|
|
||||||
|
|
||||||
String encode(T value);
|
|
||||||
T? decode(String raw);
|
|
||||||
|
|
||||||
static const Map<Type, _MetadataCodec<Object>> _primitives = {
|
|
||||||
int: _PrimitiveCodec.integer,
|
|
||||||
double: _PrimitiveCodec.real,
|
|
||||||
bool: _PrimitiveCodec.boolean,
|
|
||||||
String: _PrimitiveCodec.string,
|
|
||||||
DateTime: _DateTimeCodec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
static _MetadataCodec<T> forPrimitive<T extends Object>(T sample) {
|
|
||||||
final codec = _primitives[sample.runtimeType];
|
|
||||||
if (codec == null) {
|
|
||||||
throw StateError(
|
|
||||||
'No primitive codec for ${sample.runtimeType}. Provide an explicit codec when defining the MetadataKey.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return codec as _MetadataCodec<T>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _EnumCodec<T extends Enum> extends _MetadataCodec<T> {
|
|
||||||
final List<T> values;
|
|
||||||
|
|
||||||
const _EnumCodec(this.values);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(T value) => value.name;
|
|
||||||
|
|
||||||
@override
|
|
||||||
T? decode(String raw) => values.firstWhereOrNull((v) => v.name == raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _DateTimeCodec extends _MetadataCodec<DateTime> {
|
|
||||||
const _DateTimeCodec();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(DateTime value) => value.toIso8601String();
|
|
||||||
|
|
||||||
@override
|
|
||||||
DateTime? decode(String raw) => DateTime.tryParse(raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _MapCodec<K extends Object, V extends Object> extends _MetadataCodec<Map<K, V>> {
|
|
||||||
final _MetadataCodec<K> _keyCodec;
|
|
||||||
final _MetadataCodec<V> _valueCodec;
|
|
||||||
|
|
||||||
const _MapCodec(this._keyCodec, this._valueCodec);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(Map<K, V> value) {
|
|
||||||
final entries = <String, String>{};
|
|
||||||
value.forEach((k, v) => entries[_keyCodec.encode(k)] = _valueCodec.encode(v));
|
|
||||||
return jsonEncode(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<K, V>? decode(String raw) {
|
|
||||||
try {
|
|
||||||
final decoded = jsonDecode(raw);
|
|
||||||
if (decoded is! Map) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final result = <K, V>{};
|
|
||||||
for (final entry in decoded.entries) {
|
|
||||||
final rawKey = entry.key;
|
|
||||||
final rawValue = entry.value;
|
|
||||||
if (rawKey is! String || rawValue is! String) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final k = _keyCodec.decode(rawKey);
|
|
||||||
final v = _valueCodec.decode(rawValue);
|
|
||||||
if (k == null || v == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
result[k] = v;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} on FormatException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _ListCodec<T extends Object> extends _MetadataCodec<List<T>> {
|
|
||||||
final _MetadataCodec<T> _elementCodec;
|
|
||||||
|
|
||||||
const _ListCodec(this._elementCodec);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(List<T> value) => jsonEncode(value.map(_elementCodec.encode).toList());
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<T>? decode(String raw) {
|
|
||||||
try {
|
|
||||||
final decoded = jsonDecode(raw);
|
|
||||||
if (decoded is! List) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final result = <T>[];
|
|
||||||
for (final item in decoded) {
|
|
||||||
if (item is! String) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final element = _elementCodec.decode(item);
|
|
||||||
if (element == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
result.add(element);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} on FormatException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _PrimitiveCodec<T extends Object> extends _MetadataCodec<T> {
|
|
||||||
final T? Function(String) _parse;
|
|
||||||
|
|
||||||
const _PrimitiveCodec._(this._parse);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(T value) => value.toString();
|
|
||||||
|
|
||||||
@override
|
|
||||||
T? decode(String raw) => _parse(raw);
|
|
||||||
|
|
||||||
static const integer = _PrimitiveCodec<int>._(int.tryParse);
|
|
||||||
static const real = _PrimitiveCodec<double>._(double.tryParse);
|
|
||||||
static const boolean = _PrimitiveCodec<bool>._(bool.tryParse);
|
|
||||||
static const string = _PrimitiveCodec<String>._(_identity);
|
|
||||||
|
|
||||||
static String? _identity(String s) => s;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:immich_mobile/constants/colors.dart';
|
||||||
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
|
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||||
|
|
||||||
|
enum SettingsKey<T extends Object> {
|
||||||
|
// Theme
|
||||||
|
themePrimaryColor<ImmichColorPreset>(codec: _EnumCodec(ImmichColorPreset.values)),
|
||||||
|
themeMode<ThemeMode>(codec: _EnumCodec(ThemeMode.values)),
|
||||||
|
themeDynamic<bool>(),
|
||||||
|
themeColorfulInterface<bool>(),
|
||||||
|
|
||||||
|
// Image
|
||||||
|
imagePreferRemote<bool>(),
|
||||||
|
imageLoadOriginal<bool>(),
|
||||||
|
|
||||||
|
// Viewer
|
||||||
|
viewerLoopVideo<bool>(),
|
||||||
|
viewerLoadOriginalVideo<bool>(),
|
||||||
|
viewerAutoPlayVideo<bool>(),
|
||||||
|
viewerTapToNavigate<bool>(),
|
||||||
|
|
||||||
|
// Network
|
||||||
|
networkAutoEndpointSwitching<bool>(),
|
||||||
|
networkPreferredWifiName<String>(),
|
||||||
|
networkLocalEndpoint<String>(),
|
||||||
|
networkExternalEndpointList<List<String>>(codec: _ListCodec(_PrimitiveCodec.string)),
|
||||||
|
networkCustomHeaders<Map<String, String>>(codec: _MapCodec(_PrimitiveCodec.string, _PrimitiveCodec.string)),
|
||||||
|
|
||||||
|
// Album
|
||||||
|
albumSortMode<AlbumSortMode>(codec: _EnumCodec(AlbumSortMode.values)),
|
||||||
|
albumIsReverse<bool>(),
|
||||||
|
albumIsGrid<bool>(),
|
||||||
|
|
||||||
|
// Backup
|
||||||
|
backupEnabled<bool>(),
|
||||||
|
backupUseCellularForVideos<bool>(),
|
||||||
|
backupUseCellularForPhotos<bool>(),
|
||||||
|
backupRequireCharging<bool>(),
|
||||||
|
backupTriggerDelay<int>(),
|
||||||
|
backupSyncAlbums<bool>(),
|
||||||
|
|
||||||
|
// Timeline
|
||||||
|
timelineTilesPerRow<int>(),
|
||||||
|
timelineGroupAssetsBy<GroupAssetsBy>(codec: _EnumCodec(GroupAssetsBy.values)),
|
||||||
|
timelineStorageIndicator<bool>(),
|
||||||
|
|
||||||
|
// Log
|
||||||
|
logLevel<LogLevel>(codec: _EnumCodec(LogLevel.values)),
|
||||||
|
|
||||||
|
// Map
|
||||||
|
mapShowFavoriteOnly<bool>(),
|
||||||
|
mapRelativeDate<int>(),
|
||||||
|
mapIncludeArchived<bool>(),
|
||||||
|
mapThemeMode<ThemeMode>(codec: _EnumCodec(ThemeMode.values)),
|
||||||
|
mapWithPartners<bool>(),
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
cleanupKeepFavorites<bool>(),
|
||||||
|
cleanupKeepMediaType<AssetKeepType>(codec: _EnumCodec(AssetKeepType.values)),
|
||||||
|
cleanupKeepAlbumIds<List<String>>(codec: _ListCodec(_PrimitiveCodec.string)),
|
||||||
|
cleanupCutoffDaysAgo<int>(),
|
||||||
|
cleanupDefaultsInitialized<bool>(),
|
||||||
|
|
||||||
|
// Slideshow
|
||||||
|
slideshowTransition<bool>(),
|
||||||
|
slideshowRepeat<bool>(),
|
||||||
|
slideshowDuration<int>(),
|
||||||
|
slideshowLook<SlideshowLook>(codec: _EnumCodec(SlideshowLook.values)),
|
||||||
|
slideshowDirection<SlideshowDirection>(codec: _EnumCodec(SlideshowDirection.values));
|
||||||
|
|
||||||
|
final _SettingsCodec<T>? _codecOverride;
|
||||||
|
|
||||||
|
const SettingsKey({_SettingsCodec<T>? codec}) : _codecOverride = codec;
|
||||||
|
|
||||||
|
_SettingsCodec<T> get _codec => _codecOverride ?? _SettingsCodec.forType(T);
|
||||||
|
|
||||||
|
String encode(T value) => _codec.encode(value);
|
||||||
|
|
||||||
|
T decode(String raw) => _codec.decode(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class _SettingsCodec<T extends Object> {
|
||||||
|
const _SettingsCodec();
|
||||||
|
|
||||||
|
String encode(T value);
|
||||||
|
T decode(String raw);
|
||||||
|
|
||||||
|
static const Map<Type, _SettingsCodec<Object>> _primitives = {
|
||||||
|
int: _PrimitiveCodec.integer,
|
||||||
|
double: _PrimitiveCodec.real,
|
||||||
|
bool: _PrimitiveCodec.boolean,
|
||||||
|
String: _PrimitiveCodec.string,
|
||||||
|
DateTime: _DateTimeCodec(),
|
||||||
|
};
|
||||||
|
|
||||||
|
static _SettingsCodec<T> forType<T extends Object>(Type runtimeType) {
|
||||||
|
final codec = _primitives[runtimeType];
|
||||||
|
if (codec == null) {
|
||||||
|
throw StateError('No primitive codec for $runtimeType. Provide an explicit codec when defining the SettingsKey.');
|
||||||
|
}
|
||||||
|
return codec as _SettingsCodec<T>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class _EnumCodec<T extends Enum> extends _SettingsCodec<T> {
|
||||||
|
final List<T> values;
|
||||||
|
|
||||||
|
const _EnumCodec(this.values);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(T value) => value.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
T decode(String raw) => values.firstWhere((v) => v.name == raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class _DateTimeCodec extends _SettingsCodec<DateTime> {
|
||||||
|
const _DateTimeCodec();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(DateTime value) => value.toIso8601String();
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime decode(String raw) => DateTime.parse(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class _MapCodec<K extends Object, V extends Object> extends _SettingsCodec<Map<K, V>> {
|
||||||
|
final _SettingsCodec<K> _keyCodec;
|
||||||
|
final _SettingsCodec<V> _valueCodec;
|
||||||
|
|
||||||
|
const _MapCodec(this._keyCodec, this._valueCodec);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(Map<K, V> value) {
|
||||||
|
final entries = <String, String>{};
|
||||||
|
value.forEach((k, v) => entries[_keyCodec.encode(k)] = _valueCodec.encode(v));
|
||||||
|
return jsonEncode(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<K, V> decode(String raw) {
|
||||||
|
try {
|
||||||
|
final decoded = jsonDecode(raw);
|
||||||
|
if (decoded is! Map) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
final result = <K, V>{};
|
||||||
|
for (final entry in decoded.entries) {
|
||||||
|
final rawKey = entry.key;
|
||||||
|
final rawValue = entry.value;
|
||||||
|
if (rawKey is! String || rawValue is! String) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
final k = _keyCodec.decode(rawKey);
|
||||||
|
final v = _valueCodec.decode(rawValue);
|
||||||
|
result[k] = v;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} on FormatException {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class _ListCodec<T extends Object> extends _SettingsCodec<List<T>> {
|
||||||
|
final _SettingsCodec<T> _elementCodec;
|
||||||
|
|
||||||
|
const _ListCodec(this._elementCodec);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(List<T> value) => jsonEncode(value.map(_elementCodec.encode).toList());
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<T> decode(String raw) {
|
||||||
|
try {
|
||||||
|
final decoded = jsonDecode(raw);
|
||||||
|
if (decoded is! List) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
final result = <T>[];
|
||||||
|
for (final item in decoded) {
|
||||||
|
if (item is! String) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
final element = _elementCodec.decode(item);
|
||||||
|
result.add(element);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} on FormatException {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class _PrimitiveCodec<T extends Object> extends _SettingsCodec<T> {
|
||||||
|
final T Function(String) _parse;
|
||||||
|
|
||||||
|
const _PrimitiveCodec._(this._parse);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(T value) => value.toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
T decode(String raw) => _parse(raw);
|
||||||
|
|
||||||
|
static const integer = _PrimitiveCodec<int>._(int.parse);
|
||||||
|
static const real = _PrimitiveCodec<double>._(double.parse);
|
||||||
|
static const boolean = _PrimitiveCodec<bool>._(bool.parse);
|
||||||
|
static const string = _PrimitiveCodec<String>._(_identity);
|
||||||
|
|
||||||
|
static String _identity(String s) => s;
|
||||||
|
}
|
||||||
@@ -8,11 +8,7 @@ class AssetService {
|
|||||||
final RemoteAssetRepository _remoteAssetRepository;
|
final RemoteAssetRepository _remoteAssetRepository;
|
||||||
final DriftLocalAssetRepository _localAssetRepository;
|
final DriftLocalAssetRepository _localAssetRepository;
|
||||||
|
|
||||||
const AssetService({
|
const AssetService({required this._remoteAssetRepository, required this._localAssetRepository});
|
||||||
required RemoteAssetRepository remoteAssetRepository,
|
|
||||||
required DriftLocalAssetRepository localAssetRepository,
|
|
||||||
}) : _remoteAssetRepository = remoteAssetRepository,
|
|
||||||
_localAssetRepository = localAssetRepository;
|
|
||||||
|
|
||||||
Future<BaseAsset?> getAsset(BaseAsset asset) {
|
Future<BaseAsset?> getAsset(BaseAsset asset) {
|
||||||
final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id;
|
final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
|||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
@@ -39,7 +39,7 @@ class BackgroundWorkerFgService {
|
|||||||
_foregroundHostApi.saveNotificationMessage(title, body);
|
_foregroundHostApi.saveNotificationMessage(title, body);
|
||||||
|
|
||||||
Future<void> configure({int? minimumDelaySeconds, bool? requireCharging}) {
|
Future<void> configure({int? minimumDelaySeconds, bool? requireCharging}) {
|
||||||
final backup = MetadataRepository.instance.appConfig.backup;
|
final backup = SettingsRepository.instance.appConfig.backup;
|
||||||
return _foregroundHostApi.configure(
|
return _foregroundHostApi.configure(
|
||||||
BackgroundWorkerSettings(
|
BackgroundWorkerSettings(
|
||||||
minimumDelaySeconds: minimumDelaySeconds ?? backup.triggerDelay,
|
minimumDelaySeconds: minimumDelaySeconds ?? backup.triggerDelay,
|
||||||
@@ -61,15 +61,13 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
|
|
||||||
bool _isCleanedUp = false;
|
bool _isCleanedUp = false;
|
||||||
|
|
||||||
BackgroundWorkerBgService({required Drift drift, required DriftLogger driftLogger})
|
BackgroundWorkerBgService({required this._drift, required this._driftLogger})
|
||||||
: _drift = drift,
|
: _backgroundHostApi = BackgroundWorkerBgHostApi() {
|
||||||
_driftLogger = driftLogger,
|
_ref = ProviderContainer(overrides: [driftProvider.overrideWith(driftOverride(_drift))]);
|
||||||
_backgroundHostApi = BackgroundWorkerBgHostApi() {
|
|
||||||
_ref = ProviderContainer(overrides: [driftProvider.overrideWith(driftOverride(drift))]);
|
|
||||||
BackgroundWorkerFlutterApi.setUp(this);
|
BackgroundWorkerFlutterApi.setUp(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _isBackupEnabled => MetadataRepository.instance.appConfig.backup.enabled;
|
bool get _isBackupEnabled => SettingsRepository.instance.appConfig.backup.enabled;
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -21,18 +21,13 @@ class HashService {
|
|||||||
final _log = Logger('HashService');
|
final _log = Logger('HashService');
|
||||||
|
|
||||||
HashService({
|
HashService({
|
||||||
required DriftLocalAlbumRepository localAlbumRepository,
|
required this._localAlbumRepository,
|
||||||
required DriftLocalAssetRepository localAssetRepository,
|
required this._localAssetRepository,
|
||||||
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
|
required this._trashedLocalAssetRepository,
|
||||||
required NativeSyncApi nativeSyncApi,
|
required this._nativeSyncApi,
|
||||||
bool Function()? cancelChecker,
|
this._cancelChecker,
|
||||||
int? batchSize,
|
int? batchSize,
|
||||||
}) : _localAlbumRepository = localAlbumRepository,
|
}) : _batchSize = batchSize ?? kBatchHashFileLimit;
|
||||||
_localAssetRepository = localAssetRepository,
|
|
||||||
_trashedLocalAssetRepository = trashedLocalAssetRepository,
|
|
||||||
_cancelChecker = cancelChecker,
|
|
||||||
_nativeSyncApi = nativeSyncApi,
|
|
||||||
_batchSize = batchSize ?? kBatchHashFileLimit;
|
|
||||||
|
|
||||||
bool get isCancelled => _cancelChecker?.call() ?? false;
|
bool get isCancelled => _cancelChecker?.call() ?? false;
|
||||||
|
|
||||||
|
|||||||
@@ -28,18 +28,13 @@ class LocalSyncService {
|
|||||||
final Logger _log = Logger("DeviceSyncService");
|
final Logger _log = Logger("DeviceSyncService");
|
||||||
|
|
||||||
LocalSyncService({
|
LocalSyncService({
|
||||||
required DriftLocalAlbumRepository localAlbumRepository,
|
required this._localAlbumRepository,
|
||||||
required DriftLocalAssetRepository localAssetRepository,
|
required this._localAssetRepository,
|
||||||
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
|
required this._nativeSyncApi,
|
||||||
required AssetMediaRepository assetMediaRepository,
|
required this._trashedLocalAssetRepository,
|
||||||
required IPermissionRepository permissionRepository,
|
required this._assetMediaRepository,
|
||||||
required NativeSyncApi nativeSyncApi,
|
required this._permissionRepository,
|
||||||
}) : _localAlbumRepository = localAlbumRepository,
|
});
|
||||||
_localAssetRepository = localAssetRepository,
|
|
||||||
_trashedLocalAssetRepository = trashedLocalAssetRepository,
|
|
||||||
_assetMediaRepository = assetMediaRepository,
|
|
||||||
_permissionRepository = permissionRepository,
|
|
||||||
_nativeSyncApi = nativeSyncApi;
|
|
||||||
|
|
||||||
Future<void> sync({bool full = false}) async {
|
Future<void> sync({bool full = false}) async {
|
||||||
final Stopwatch stopwatch = Stopwatch()..start();
|
final Stopwatch stopwatch = Stopwatch()..start();
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
import 'package:immich_mobile/domain/models/settings_key.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
@@ -12,10 +12,10 @@ import 'package:logging/logging.dart';
|
|||||||
///
|
///
|
||||||
/// It listens to Dart's [Logger.root], buffers logs in memory (optionally),
|
/// It listens to Dart's [Logger.root], buffers logs in memory (optionally),
|
||||||
/// writes them to a persistent [LogRepository], and manages log levels via
|
/// writes them to a persistent [LogRepository], and manages log levels via
|
||||||
/// [MetadataRepository].
|
/// [SettingsRepository].
|
||||||
class LogService {
|
class LogService {
|
||||||
final LogRepository _logRepository;
|
final LogRepository _logRepository;
|
||||||
final MetadataRepository _metadataRepository;
|
final SettingsRepository _settingsRepository;
|
||||||
|
|
||||||
final List<LogMessage> _msgBuffer = [];
|
final List<LogMessage> _msgBuffer = [];
|
||||||
|
|
||||||
@@ -38,12 +38,12 @@ class LogService {
|
|||||||
|
|
||||||
static Future<LogService> init({
|
static Future<LogService> init({
|
||||||
required LogRepository logRepository,
|
required LogRepository logRepository,
|
||||||
required MetadataRepository metadataRepository,
|
required SettingsRepository settingsRepository,
|
||||||
bool shouldBuffer = true,
|
bool shouldBuffer = true,
|
||||||
}) async {
|
}) async {
|
||||||
_instance ??= await create(
|
_instance ??= await create(
|
||||||
logRepository: logRepository,
|
logRepository: logRepository,
|
||||||
metadataRepository: metadataRepository,
|
settingsRepository: settingsRepository,
|
||||||
shouldBuffer: shouldBuffer,
|
shouldBuffer: shouldBuffer,
|
||||||
);
|
);
|
||||||
return _instance!;
|
return _instance!;
|
||||||
@@ -51,17 +51,17 @@ class LogService {
|
|||||||
|
|
||||||
static Future<LogService> create({
|
static Future<LogService> create({
|
||||||
required LogRepository logRepository,
|
required LogRepository logRepository,
|
||||||
required MetadataRepository metadataRepository,
|
required SettingsRepository settingsRepository,
|
||||||
bool shouldBuffer = true,
|
bool shouldBuffer = true,
|
||||||
}) async {
|
}) async {
|
||||||
final instance = LogService._(logRepository, metadataRepository, shouldBuffer);
|
final instance = LogService._(logRepository, settingsRepository, shouldBuffer);
|
||||||
await logRepository.truncate(limit: kLogTruncateLimit);
|
await logRepository.truncate(limit: kLogTruncateLimit);
|
||||||
final level = instance._metadataRepository.systemConfig.logLevel;
|
final level = instance._settingsRepository.appConfig.logLevel;
|
||||||
Logger.root.level = Level.LEVELS.elementAtOrNull(level.index) ?? Level.INFO;
|
Logger.root.level = Level.LEVELS.elementAtOrNull(level.index) ?? Level.INFO;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogService._(this._logRepository, this._metadataRepository, this._shouldBuffer) {
|
LogService._(this._logRepository, this._settingsRepository, this._shouldBuffer) {
|
||||||
_logSubscription = Logger.root.onRecord.listen(_handleLogRecord);
|
_logSubscription = Logger.root.onRecord.listen(_handleLogRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ class LogService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setLogLevel(LogLevel level) async {
|
Future<void> setLogLevel(LogLevel level) async {
|
||||||
await _metadataRepository.write(MetadataKey.logLevel, level);
|
await _settingsRepository.write(SettingsKey.logLevel, level);
|
||||||
Logger.root.level = level.toLevel();
|
Logger.root.level = level.toLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ typedef MapQuery = ({MapMarkerSource markerSource});
|
|||||||
class MapFactory {
|
class MapFactory {
|
||||||
final DriftMapRepository _mapRepository;
|
final DriftMapRepository _mapRepository;
|
||||||
|
|
||||||
const MapFactory({required DriftMapRepository mapRepository}) : _mapRepository = mapRepository;
|
const MapFactory({required this._mapRepository});
|
||||||
|
|
||||||
MapService remote(List<String> ownerIds, TimelineMapOptions options) =>
|
MapService remote(List<String> ownerIds, TimelineMapOptions options) =>
|
||||||
MapService(_mapRepository.remote(ownerIds, options));
|
MapService(_mapRepository.remote(ownerIds, options));
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ final AppSetting = SettingsService(storeService: StoreService.I);
|
|||||||
class SettingsService {
|
class SettingsService {
|
||||||
final StoreService _storeService;
|
final StoreService _storeService;
|
||||||
|
|
||||||
const SettingsService({required StoreService storeService}) : _storeService = storeService;
|
const SettingsService({required this._storeService});
|
||||||
|
|
||||||
T get<T>(Setting<T> setting) => _storeService.get(setting.storeKey, setting.defaultValue);
|
T get<T>(Setting<T> setting) => _storeService.get(setting.storeKey, setting.defaultValue);
|
||||||
|
|
||||||
|
|||||||
@@ -41,24 +41,16 @@ class SyncStreamService {
|
|||||||
final bool Function()? _cancelChecker;
|
final bool Function()? _cancelChecker;
|
||||||
|
|
||||||
SyncStreamService({
|
SyncStreamService({
|
||||||
required SyncApiRepository syncApiRepository,
|
required this._syncApiRepository,
|
||||||
required SyncStreamRepository syncStreamRepository,
|
required this._syncStreamRepository,
|
||||||
required DriftLocalAssetRepository localAssetRepository,
|
required this._localAssetRepository,
|
||||||
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
|
required this._trashedLocalAssetRepository,
|
||||||
required AssetMediaRepository assetMediaRepository,
|
required this._assetMediaRepository,
|
||||||
required IPermissionRepository permissionRepository,
|
required this._permissionRepository,
|
||||||
required SyncMigrationRepository syncMigrationRepository,
|
required this._syncMigrationRepository,
|
||||||
required ApiService api,
|
required this._api,
|
||||||
bool Function()? cancelChecker,
|
this._cancelChecker,
|
||||||
}) : _syncApiRepository = syncApiRepository,
|
});
|
||||||
_syncStreamRepository = syncStreamRepository,
|
|
||||||
_localAssetRepository = localAssetRepository,
|
|
||||||
_trashedLocalAssetRepository = trashedLocalAssetRepository,
|
|
||||||
_assetMediaRepository = assetMediaRepository,
|
|
||||||
_permissionRepository = permissionRepository,
|
|
||||||
_syncMigrationRepository = syncMigrationRepository,
|
|
||||||
_api = api,
|
|
||||||
_cancelChecker = cancelChecker;
|
|
||||||
|
|
||||||
bool get isCancelled => _cancelChecker?.call() ?? false;
|
bool get isCancelled => _cancelChecker?.call() ?? false;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
|||||||
import 'package:immich_mobile/domain/models/events.model.dart';
|
import 'package:immich_mobile/domain/models/events.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
|
||||||
import 'package:immich_mobile/utils/async_mutex.dart';
|
import 'package:immich_mobile/utils/async_mutex.dart';
|
||||||
|
|
||||||
@@ -39,16 +39,12 @@ enum TimelineOrigin {
|
|||||||
|
|
||||||
class TimelineFactory {
|
class TimelineFactory {
|
||||||
final DriftTimelineRepository _timelineRepository;
|
final DriftTimelineRepository _timelineRepository;
|
||||||
final MetadataRepository _metadataRepository;
|
final SettingsRepository _settingsRepository;
|
||||||
|
|
||||||
const TimelineFactory({
|
const TimelineFactory({required this._timelineRepository, required this._settingsRepository});
|
||||||
required DriftTimelineRepository timelineRepository,
|
|
||||||
required MetadataRepository metadataRepository,
|
|
||||||
}) : _timelineRepository = timelineRepository,
|
|
||||||
_metadataRepository = metadataRepository;
|
|
||||||
|
|
||||||
GroupAssetsBy get groupBy {
|
GroupAssetsBy get groupBy {
|
||||||
final group = _metadataRepository.appConfig.timeline.groupAssetsBy;
|
final group = _settingsRepository.appConfig.timeline.groupAssetsBy;
|
||||||
// We do not support auto grouping in the new timeline yet, fallback to day grouping
|
// We do not support auto grouping in the new timeline yet, fallback to day grouping
|
||||||
return group == GroupAssetsBy.auto ? GroupAssetsBy.day : group;
|
return group == GroupAssetsBy.auto ? GroupAssetsBy.day : group;
|
||||||
}
|
}
|
||||||
@@ -108,12 +104,7 @@ class TimelineService {
|
|||||||
TimelineService(TimelineQuery query)
|
TimelineService(TimelineQuery query)
|
||||||
: this._(assetSource: query.assetSource, bucketSource: query.bucketSource, origin: query.origin);
|
: this._(assetSource: query.assetSource, bucketSource: query.bucketSource, origin: query.origin);
|
||||||
|
|
||||||
TimelineService._({
|
TimelineService._({required this._assetSource, required this._bucketSource, required this.origin}) {
|
||||||
required TimelineAssetSource assetSource,
|
|
||||||
required TimelineBucketSource bucketSource,
|
|
||||||
required this.origin,
|
|
||||||
}) : _assetSource = assetSource,
|
|
||||||
_bucketSource = bucketSource {
|
|
||||||
_bucketSubscription = _bucketSource().listen((buckets) {
|
_bucketSubscription = _bucketSource().listen((buckets) {
|
||||||
_mutex.run(() async {
|
_mutex.run(() async {
|
||||||
final totalAssets = buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount);
|
final totalAssets = buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount);
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ class UserService {
|
|||||||
final UserApiRepository _userApiRepository;
|
final UserApiRepository _userApiRepository;
|
||||||
final StoreService _storeService;
|
final StoreService _storeService;
|
||||||
|
|
||||||
UserService({required UserApiRepository userApiRepository, required StoreService storeService})
|
UserService({required this._userApiRepository, required this._storeService});
|
||||||
: _userApiRepository = userApiRepository,
|
|
||||||
_storeService = storeService;
|
|
||||||
|
|
||||||
UserDto getMyUser() {
|
UserDto getMyUser() {
|
||||||
return _storeService.get(StoreKey.currentUser);
|
return _storeService.get(StoreKey.currentUser);
|
||||||
|
|||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
class MetadataEntity extends Table with DriftDefaultsMixin {
|
class SettingsEntity extends Table with DriftDefaultsMixin {
|
||||||
const MetadataEntity();
|
const SettingsEntity();
|
||||||
|
|
||||||
TextColumn get key => text()();
|
TextColumn get key => text()();
|
||||||
|
|
||||||
@@ -14,5 +14,5 @@ class MetadataEntity extends Table with DriftDefaultsMixin {
|
|||||||
Set<Column> get primaryKey => {key};
|
Set<Column> get primaryKey => {key};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get tableName => "metadata";
|
String get tableName => "settings";
|
||||||
}
|
}
|
||||||
+74
-74
@@ -1,28 +1,28 @@
|
|||||||
// dart format width=80
|
// dart format width=80
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
import 'package:drift/drift.dart' as i0;
|
import 'package:drift/drift.dart' as i0;
|
||||||
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart'
|
||||||
as i1;
|
as i1;
|
||||||
import 'package:immich_mobile/infrastructure/entities/metadata.entity.dart'
|
import 'package:immich_mobile/infrastructure/entities/settings.entity.dart'
|
||||||
as i2;
|
as i2;
|
||||||
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
|
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
|
||||||
|
|
||||||
typedef $$MetadataEntityTableCreateCompanionBuilder =
|
typedef $$SettingsEntityTableCreateCompanionBuilder =
|
||||||
i1.MetadataEntityCompanion Function({
|
i1.SettingsEntityCompanion Function({
|
||||||
required String key,
|
required String key,
|
||||||
required String value,
|
required String value,
|
||||||
i0.Value<DateTime> updatedAt,
|
i0.Value<DateTime> updatedAt,
|
||||||
});
|
});
|
||||||
typedef $$MetadataEntityTableUpdateCompanionBuilder =
|
typedef $$SettingsEntityTableUpdateCompanionBuilder =
|
||||||
i1.MetadataEntityCompanion Function({
|
i1.SettingsEntityCompanion Function({
|
||||||
i0.Value<String> key,
|
i0.Value<String> key,
|
||||||
i0.Value<String> value,
|
i0.Value<String> value,
|
||||||
i0.Value<DateTime> updatedAt,
|
i0.Value<DateTime> updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
class $$MetadataEntityTableFilterComposer
|
class $$SettingsEntityTableFilterComposer
|
||||||
extends i0.Composer<i0.GeneratedDatabase, i1.$MetadataEntityTable> {
|
extends i0.Composer<i0.GeneratedDatabase, i1.$SettingsEntityTable> {
|
||||||
$$MetadataEntityTableFilterComposer({
|
$$SettingsEntityTableFilterComposer({
|
||||||
required super.$db,
|
required super.$db,
|
||||||
required super.$table,
|
required super.$table,
|
||||||
super.joinBuilder,
|
super.joinBuilder,
|
||||||
@@ -45,9 +45,9 @@ class $$MetadataEntityTableFilterComposer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$MetadataEntityTableOrderingComposer
|
class $$SettingsEntityTableOrderingComposer
|
||||||
extends i0.Composer<i0.GeneratedDatabase, i1.$MetadataEntityTable> {
|
extends i0.Composer<i0.GeneratedDatabase, i1.$SettingsEntityTable> {
|
||||||
$$MetadataEntityTableOrderingComposer({
|
$$SettingsEntityTableOrderingComposer({
|
||||||
required super.$db,
|
required super.$db,
|
||||||
required super.$table,
|
required super.$table,
|
||||||
super.joinBuilder,
|
super.joinBuilder,
|
||||||
@@ -70,9 +70,9 @@ class $$MetadataEntityTableOrderingComposer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$MetadataEntityTableAnnotationComposer
|
class $$SettingsEntityTableAnnotationComposer
|
||||||
extends i0.Composer<i0.GeneratedDatabase, i1.$MetadataEntityTable> {
|
extends i0.Composer<i0.GeneratedDatabase, i1.$SettingsEntityTable> {
|
||||||
$$MetadataEntityTableAnnotationComposer({
|
$$SettingsEntityTableAnnotationComposer({
|
||||||
required super.$db,
|
required super.$db,
|
||||||
required super.$table,
|
required super.$table,
|
||||||
super.joinBuilder,
|
super.joinBuilder,
|
||||||
@@ -89,47 +89,47 @@ class $$MetadataEntityTableAnnotationComposer
|
|||||||
$composableBuilder(column: $table.updatedAt, builder: (column) => column);
|
$composableBuilder(column: $table.updatedAt, builder: (column) => column);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$MetadataEntityTableTableManager
|
class $$SettingsEntityTableTableManager
|
||||||
extends
|
extends
|
||||||
i0.RootTableManager<
|
i0.RootTableManager<
|
||||||
i0.GeneratedDatabase,
|
i0.GeneratedDatabase,
|
||||||
i1.$MetadataEntityTable,
|
i1.$SettingsEntityTable,
|
||||||
i1.MetadataEntityData,
|
i1.SettingsEntityData,
|
||||||
i1.$$MetadataEntityTableFilterComposer,
|
i1.$$SettingsEntityTableFilterComposer,
|
||||||
i1.$$MetadataEntityTableOrderingComposer,
|
i1.$$SettingsEntityTableOrderingComposer,
|
||||||
i1.$$MetadataEntityTableAnnotationComposer,
|
i1.$$SettingsEntityTableAnnotationComposer,
|
||||||
$$MetadataEntityTableCreateCompanionBuilder,
|
$$SettingsEntityTableCreateCompanionBuilder,
|
||||||
$$MetadataEntityTableUpdateCompanionBuilder,
|
$$SettingsEntityTableUpdateCompanionBuilder,
|
||||||
(
|
(
|
||||||
i1.MetadataEntityData,
|
i1.SettingsEntityData,
|
||||||
i0.BaseReferences<
|
i0.BaseReferences<
|
||||||
i0.GeneratedDatabase,
|
i0.GeneratedDatabase,
|
||||||
i1.$MetadataEntityTable,
|
i1.$SettingsEntityTable,
|
||||||
i1.MetadataEntityData
|
i1.SettingsEntityData
|
||||||
>,
|
>,
|
||||||
),
|
),
|
||||||
i1.MetadataEntityData,
|
i1.SettingsEntityData,
|
||||||
i0.PrefetchHooks Function()
|
i0.PrefetchHooks Function()
|
||||||
> {
|
> {
|
||||||
$$MetadataEntityTableTableManager(
|
$$SettingsEntityTableTableManager(
|
||||||
i0.GeneratedDatabase db,
|
i0.GeneratedDatabase db,
|
||||||
i1.$MetadataEntityTable table,
|
i1.$SettingsEntityTable table,
|
||||||
) : super(
|
) : super(
|
||||||
i0.TableManagerState(
|
i0.TableManagerState(
|
||||||
db: db,
|
db: db,
|
||||||
table: table,
|
table: table,
|
||||||
createFilteringComposer: () =>
|
createFilteringComposer: () =>
|
||||||
i1.$$MetadataEntityTableFilterComposer($db: db, $table: table),
|
i1.$$SettingsEntityTableFilterComposer($db: db, $table: table),
|
||||||
createOrderingComposer: () =>
|
createOrderingComposer: () =>
|
||||||
i1.$$MetadataEntityTableOrderingComposer($db: db, $table: table),
|
i1.$$SettingsEntityTableOrderingComposer($db: db, $table: table),
|
||||||
createComputedFieldComposer: () => i1
|
createComputedFieldComposer: () => i1
|
||||||
.$$MetadataEntityTableAnnotationComposer($db: db, $table: table),
|
.$$SettingsEntityTableAnnotationComposer($db: db, $table: table),
|
||||||
updateCompanionCallback:
|
updateCompanionCallback:
|
||||||
({
|
({
|
||||||
i0.Value<String> key = const i0.Value.absent(),
|
i0.Value<String> key = const i0.Value.absent(),
|
||||||
i0.Value<String> value = const i0.Value.absent(),
|
i0.Value<String> value = const i0.Value.absent(),
|
||||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
}) => i1.MetadataEntityCompanion(
|
}) => i1.SettingsEntityCompanion(
|
||||||
key: key,
|
key: key,
|
||||||
value: value,
|
value: value,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
@@ -139,7 +139,7 @@ class $$MetadataEntityTableTableManager
|
|||||||
required String key,
|
required String key,
|
||||||
required String value,
|
required String value,
|
||||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
}) => i1.MetadataEntityCompanion.insert(
|
}) => i1.SettingsEntityCompanion.insert(
|
||||||
key: key,
|
key: key,
|
||||||
value: value,
|
value: value,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
@@ -152,34 +152,34 @@ class $$MetadataEntityTableTableManager
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef $$MetadataEntityTableProcessedTableManager =
|
typedef $$SettingsEntityTableProcessedTableManager =
|
||||||
i0.ProcessedTableManager<
|
i0.ProcessedTableManager<
|
||||||
i0.GeneratedDatabase,
|
i0.GeneratedDatabase,
|
||||||
i1.$MetadataEntityTable,
|
i1.$SettingsEntityTable,
|
||||||
i1.MetadataEntityData,
|
i1.SettingsEntityData,
|
||||||
i1.$$MetadataEntityTableFilterComposer,
|
i1.$$SettingsEntityTableFilterComposer,
|
||||||
i1.$$MetadataEntityTableOrderingComposer,
|
i1.$$SettingsEntityTableOrderingComposer,
|
||||||
i1.$$MetadataEntityTableAnnotationComposer,
|
i1.$$SettingsEntityTableAnnotationComposer,
|
||||||
$$MetadataEntityTableCreateCompanionBuilder,
|
$$SettingsEntityTableCreateCompanionBuilder,
|
||||||
$$MetadataEntityTableUpdateCompanionBuilder,
|
$$SettingsEntityTableUpdateCompanionBuilder,
|
||||||
(
|
(
|
||||||
i1.MetadataEntityData,
|
i1.SettingsEntityData,
|
||||||
i0.BaseReferences<
|
i0.BaseReferences<
|
||||||
i0.GeneratedDatabase,
|
i0.GeneratedDatabase,
|
||||||
i1.$MetadataEntityTable,
|
i1.$SettingsEntityTable,
|
||||||
i1.MetadataEntityData
|
i1.SettingsEntityData
|
||||||
>,
|
>,
|
||||||
),
|
),
|
||||||
i1.MetadataEntityData,
|
i1.SettingsEntityData,
|
||||||
i0.PrefetchHooks Function()
|
i0.PrefetchHooks Function()
|
||||||
>;
|
>;
|
||||||
|
|
||||||
class $MetadataEntityTable extends i2.MetadataEntity
|
class $SettingsEntityTable extends i2.SettingsEntity
|
||||||
with i0.TableInfo<$MetadataEntityTable, i1.MetadataEntityData> {
|
with i0.TableInfo<$SettingsEntityTable, i1.SettingsEntityData> {
|
||||||
@override
|
@override
|
||||||
final i0.GeneratedDatabase attachedDatabase;
|
final i0.GeneratedDatabase attachedDatabase;
|
||||||
final String? _alias;
|
final String? _alias;
|
||||||
$MetadataEntityTable(this.attachedDatabase, [this._alias]);
|
$SettingsEntityTable(this.attachedDatabase, [this._alias]);
|
||||||
static const i0.VerificationMeta _keyMeta = const i0.VerificationMeta('key');
|
static const i0.VerificationMeta _keyMeta = const i0.VerificationMeta('key');
|
||||||
@override
|
@override
|
||||||
late final i0.GeneratedColumn<String> key = i0.GeneratedColumn<String>(
|
late final i0.GeneratedColumn<String> key = i0.GeneratedColumn<String>(
|
||||||
@@ -219,10 +219,10 @@ class $MetadataEntityTable extends i2.MetadataEntity
|
|||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@override
|
@override
|
||||||
String get actualTableName => $name;
|
String get actualTableName => $name;
|
||||||
static const String $name = 'metadata';
|
static const String $name = 'settings';
|
||||||
@override
|
@override
|
||||||
i0.VerificationContext validateIntegrity(
|
i0.VerificationContext validateIntegrity(
|
||||||
i0.Insertable<i1.MetadataEntityData> instance, {
|
i0.Insertable<i1.SettingsEntityData> instance, {
|
||||||
bool isInserting = false,
|
bool isInserting = false,
|
||||||
}) {
|
}) {
|
||||||
final context = i0.VerificationContext();
|
final context = i0.VerificationContext();
|
||||||
@@ -255,9 +255,9 @@ class $MetadataEntityTable extends i2.MetadataEntity
|
|||||||
@override
|
@override
|
||||||
Set<i0.GeneratedColumn> get $primaryKey => {key};
|
Set<i0.GeneratedColumn> get $primaryKey => {key};
|
||||||
@override
|
@override
|
||||||
i1.MetadataEntityData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
i1.SettingsEntityData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
return i1.MetadataEntityData(
|
return i1.SettingsEntityData(
|
||||||
key: attachedDatabase.typeMapping.read(
|
key: attachedDatabase.typeMapping.read(
|
||||||
i0.DriftSqlType.string,
|
i0.DriftSqlType.string,
|
||||||
data['${effectivePrefix}key'],
|
data['${effectivePrefix}key'],
|
||||||
@@ -274,8 +274,8 @@ class $MetadataEntityTable extends i2.MetadataEntity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
$MetadataEntityTable createAlias(String alias) {
|
$SettingsEntityTable createAlias(String alias) {
|
||||||
return $MetadataEntityTable(attachedDatabase, alias);
|
return $SettingsEntityTable(attachedDatabase, alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -284,12 +284,12 @@ class $MetadataEntityTable extends i2.MetadataEntity
|
|||||||
bool get isStrict => true;
|
bool get isStrict => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetadataEntityData extends i0.DataClass
|
class SettingsEntityData extends i0.DataClass
|
||||||
implements i0.Insertable<i1.MetadataEntityData> {
|
implements i0.Insertable<i1.SettingsEntityData> {
|
||||||
final String key;
|
final String key;
|
||||||
final String value;
|
final String value;
|
||||||
final DateTime updatedAt;
|
final DateTime updatedAt;
|
||||||
const MetadataEntityData({
|
const SettingsEntityData({
|
||||||
required this.key,
|
required this.key,
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
@@ -303,12 +303,12 @@ class MetadataEntityData extends i0.DataClass
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
factory MetadataEntityData.fromJson(
|
factory SettingsEntityData.fromJson(
|
||||||
Map<String, dynamic> json, {
|
Map<String, dynamic> json, {
|
||||||
i0.ValueSerializer? serializer,
|
i0.ValueSerializer? serializer,
|
||||||
}) {
|
}) {
|
||||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
return MetadataEntityData(
|
return SettingsEntityData(
|
||||||
key: serializer.fromJson<String>(json['key']),
|
key: serializer.fromJson<String>(json['key']),
|
||||||
value: serializer.fromJson<String>(json['value']),
|
value: serializer.fromJson<String>(json['value']),
|
||||||
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||||
@@ -324,17 +324,17 @@ class MetadataEntityData extends i0.DataClass
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
i1.MetadataEntityData copyWith({
|
i1.SettingsEntityData copyWith({
|
||||||
String? key,
|
String? key,
|
||||||
String? value,
|
String? value,
|
||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
}) => i1.MetadataEntityData(
|
}) => i1.SettingsEntityData(
|
||||||
key: key ?? this.key,
|
key: key ?? this.key,
|
||||||
value: value ?? this.value,
|
value: value ?? this.value,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
);
|
);
|
||||||
MetadataEntityData copyWithCompanion(i1.MetadataEntityCompanion data) {
|
SettingsEntityData copyWithCompanion(i1.SettingsEntityCompanion data) {
|
||||||
return MetadataEntityData(
|
return SettingsEntityData(
|
||||||
key: data.key.present ? data.key.value : this.key,
|
key: data.key.present ? data.key.value : this.key,
|
||||||
value: data.value.present ? data.value.value : this.value,
|
value: data.value.present ? data.value.value : this.value,
|
||||||
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
||||||
@@ -343,7 +343,7 @@ class MetadataEntityData extends i0.DataClass
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return (StringBuffer('MetadataEntityData(')
|
return (StringBuffer('SettingsEntityData(')
|
||||||
..write('key: $key, ')
|
..write('key: $key, ')
|
||||||
..write('value: $value, ')
|
..write('value: $value, ')
|
||||||
..write('updatedAt: $updatedAt')
|
..write('updatedAt: $updatedAt')
|
||||||
@@ -356,29 +356,29 @@ class MetadataEntityData extends i0.DataClass
|
|||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
(other is i1.MetadataEntityData &&
|
(other is i1.SettingsEntityData &&
|
||||||
other.key == this.key &&
|
other.key == this.key &&
|
||||||
other.value == this.value &&
|
other.value == this.value &&
|
||||||
other.updatedAt == this.updatedAt);
|
other.updatedAt == this.updatedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetadataEntityCompanion
|
class SettingsEntityCompanion
|
||||||
extends i0.UpdateCompanion<i1.MetadataEntityData> {
|
extends i0.UpdateCompanion<i1.SettingsEntityData> {
|
||||||
final i0.Value<String> key;
|
final i0.Value<String> key;
|
||||||
final i0.Value<String> value;
|
final i0.Value<String> value;
|
||||||
final i0.Value<DateTime> updatedAt;
|
final i0.Value<DateTime> updatedAt;
|
||||||
const MetadataEntityCompanion({
|
const SettingsEntityCompanion({
|
||||||
this.key = const i0.Value.absent(),
|
this.key = const i0.Value.absent(),
|
||||||
this.value = const i0.Value.absent(),
|
this.value = const i0.Value.absent(),
|
||||||
this.updatedAt = const i0.Value.absent(),
|
this.updatedAt = const i0.Value.absent(),
|
||||||
});
|
});
|
||||||
MetadataEntityCompanion.insert({
|
SettingsEntityCompanion.insert({
|
||||||
required String key,
|
required String key,
|
||||||
required String value,
|
required String value,
|
||||||
this.updatedAt = const i0.Value.absent(),
|
this.updatedAt = const i0.Value.absent(),
|
||||||
}) : key = i0.Value(key),
|
}) : key = i0.Value(key),
|
||||||
value = i0.Value(value);
|
value = i0.Value(value);
|
||||||
static i0.Insertable<i1.MetadataEntityData> custom({
|
static i0.Insertable<i1.SettingsEntityData> custom({
|
||||||
i0.Expression<String>? key,
|
i0.Expression<String>? key,
|
||||||
i0.Expression<String>? value,
|
i0.Expression<String>? value,
|
||||||
i0.Expression<DateTime>? updatedAt,
|
i0.Expression<DateTime>? updatedAt,
|
||||||
@@ -390,12 +390,12 @@ class MetadataEntityCompanion
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
i1.MetadataEntityCompanion copyWith({
|
i1.SettingsEntityCompanion copyWith({
|
||||||
i0.Value<String>? key,
|
i0.Value<String>? key,
|
||||||
i0.Value<String>? value,
|
i0.Value<String>? value,
|
||||||
i0.Value<DateTime>? updatedAt,
|
i0.Value<DateTime>? updatedAt,
|
||||||
}) {
|
}) {
|
||||||
return i1.MetadataEntityCompanion(
|
return i1.SettingsEntityCompanion(
|
||||||
key: key ?? this.key,
|
key: key ?? this.key,
|
||||||
value: value ?? this.value,
|
value: value ?? this.value,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
@@ -419,7 +419,7 @@ class MetadataEntityCompanion
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return (StringBuffer('MetadataEntityCompanion(')
|
return (StringBuffer('SettingsEntityCompanion(')
|
||||||
..write('key: $key, ')
|
..write('key: $key, ')
|
||||||
..write('value: $value, ')
|
..write('value: $value, ')
|
||||||
..write('updatedAt: $updatedAt')
|
..write('updatedAt: $updatedAt')
|
||||||
@@ -13,7 +13,7 @@ import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/memory.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/memory.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/metadata.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/settings.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/person.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/person.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
|
||||||
@@ -55,7 +55,7 @@ import 'package:logging/logging.dart';
|
|||||||
StoreEntity,
|
StoreEntity,
|
||||||
TrashedLocalAssetEntity,
|
TrashedLocalAssetEntity,
|
||||||
AssetEditEntity,
|
AssetEditEntity,
|
||||||
MetadataEntity,
|
SettingsEntity,
|
||||||
],
|
],
|
||||||
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
|
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
|
||||||
)
|
)
|
||||||
@@ -98,7 +98,7 @@ class Drift extends $Drift {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 26;
|
int get schemaVersion => 27;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@@ -276,6 +276,9 @@ class Drift extends $Drift {
|
|||||||
from25To26: (m, v26) async {
|
from25To26: (m, v26) async {
|
||||||
await m.addColumn(v26.remoteAssetEntity, v26.remoteAssetEntity.uploadedAt);
|
await m.addColumn(v26.remoteAssetEntity, v26.remoteAssetEntity.uploadedAt);
|
||||||
},
|
},
|
||||||
|
from26To27: (m, v27) async {
|
||||||
|
await customStatement('ALTER TABLE metadata RENAME TO settings');
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity
|
|||||||
as i20;
|
as i20;
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart'
|
||||||
as i21;
|
as i21;
|
||||||
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart'
|
||||||
as i22;
|
as i22;
|
||||||
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||||
as i23;
|
as i23;
|
||||||
@@ -91,7 +91,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
.$TrashedLocalAssetEntityTable(this);
|
.$TrashedLocalAssetEntityTable(this);
|
||||||
late final i21.$AssetEditEntityTable assetEditEntity = i21
|
late final i21.$AssetEditEntityTable assetEditEntity = i21
|
||||||
.$AssetEditEntityTable(this);
|
.$AssetEditEntityTable(this);
|
||||||
late final i22.$MetadataEntityTable metadataEntity = i22.$MetadataEntityTable(
|
late final i22.$SettingsEntityTable settingsEntity = i22.$SettingsEntityTable(
|
||||||
this,
|
this,
|
||||||
);
|
);
|
||||||
i23.MergedAssetDrift get mergedAssetDrift => i24.ReadDatabaseContainer(
|
i23.MergedAssetDrift get mergedAssetDrift => i24.ReadDatabaseContainer(
|
||||||
@@ -132,7 +132,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
storeEntity,
|
storeEntity,
|
||||||
trashedLocalAssetEntity,
|
trashedLocalAssetEntity,
|
||||||
assetEditEntity,
|
assetEditEntity,
|
||||||
metadataEntity,
|
settingsEntity,
|
||||||
i10.idxPartnerSharedWithId,
|
i10.idxPartnerSharedWithId,
|
||||||
i11.idxLatLng,
|
i11.idxLatLng,
|
||||||
i11.idxRemoteExifCity,
|
i11.idxRemoteExifCity,
|
||||||
@@ -395,6 +395,6 @@ class $DriftManager {
|
|||||||
);
|
);
|
||||||
i21.$$AssetEditEntityTableTableManager get assetEditEntity =>
|
i21.$$AssetEditEntityTableTableManager get assetEditEntity =>
|
||||||
i21.$$AssetEditEntityTableTableManager(_db, _db.assetEditEntity);
|
i21.$$AssetEditEntityTableTableManager(_db, _db.assetEditEntity);
|
||||||
i22.$$MetadataEntityTableTableManager get metadataEntity =>
|
i22.$$SettingsEntityTableTableManager get settingsEntity =>
|
||||||
i22.$$MetadataEntityTableTableManager(_db, _db.metadataEntity);
|
i22.$$SettingsEntityTableTableManager(_db, _db.settingsEntity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13539,6 +13539,550 @@ i1.GeneratedColumn<String> _column_212(String aliasedName) =>
|
|||||||
type: i1.DriftSqlType.string,
|
type: i1.DriftSqlType.string,
|
||||||
$customConstraints: 'NULL',
|
$customConstraints: 'NULL',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final class Schema27 extends i0.VersionedSchema {
|
||||||
|
Schema27({required super.database}) : super(version: 27);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||||
|
userEntity,
|
||||||
|
remoteAssetEntity,
|
||||||
|
stackEntity,
|
||||||
|
localAssetEntity,
|
||||||
|
remoteAlbumEntity,
|
||||||
|
localAlbumEntity,
|
||||||
|
localAlbumAssetEntity,
|
||||||
|
idxLocalAlbumAssetAlbumAsset,
|
||||||
|
idxLocalAssetChecksum,
|
||||||
|
idxLocalAssetCloudId,
|
||||||
|
idxStackPrimaryAssetId,
|
||||||
|
uQRemoteAssetsOwnerChecksum,
|
||||||
|
uQRemoteAssetsOwnerLibraryChecksum,
|
||||||
|
idxRemoteAssetChecksum,
|
||||||
|
idxRemoteAssetStackId,
|
||||||
|
idxRemoteAssetOwnerVisibilityDeletedCreated,
|
||||||
|
authUserEntity,
|
||||||
|
userMetadataEntity,
|
||||||
|
partnerEntity,
|
||||||
|
remoteExifEntity,
|
||||||
|
remoteAlbumAssetEntity,
|
||||||
|
remoteAlbumUserEntity,
|
||||||
|
remoteAssetCloudIdEntity,
|
||||||
|
memoryEntity,
|
||||||
|
memoryAssetEntity,
|
||||||
|
personEntity,
|
||||||
|
assetFaceEntity,
|
||||||
|
storeEntity,
|
||||||
|
trashedLocalAssetEntity,
|
||||||
|
assetEditEntity,
|
||||||
|
settings,
|
||||||
|
idxPartnerSharedWithId,
|
||||||
|
idxLatLng,
|
||||||
|
idxRemoteExifCity,
|
||||||
|
idxRemoteAlbumAssetAlbumAsset,
|
||||||
|
idxRemoteAssetCloudId,
|
||||||
|
idxPersonOwnerId,
|
||||||
|
idxAssetFacePersonId,
|
||||||
|
idxAssetFaceAssetId,
|
||||||
|
idxAssetFaceVisiblePerson,
|
||||||
|
idxTrashedLocalAssetChecksum,
|
||||||
|
idxTrashedLocalAssetAlbum,
|
||||||
|
idxAssetEditAssetId,
|
||||||
|
];
|
||||||
|
late final Shape33 userEntity = Shape33(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_109,
|
||||||
|
_column_110,
|
||||||
|
_column_111,
|
||||||
|
_column_112,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape50 remoteAssetEntity = Shape50(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_108,
|
||||||
|
_column_113,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_116,
|
||||||
|
_column_117,
|
||||||
|
_column_118,
|
||||||
|
_column_107,
|
||||||
|
_column_119,
|
||||||
|
_column_120,
|
||||||
|
_column_121,
|
||||||
|
_column_122,
|
||||||
|
_column_123,
|
||||||
|
_column_124,
|
||||||
|
_column_212,
|
||||||
|
_column_125,
|
||||||
|
_column_126,
|
||||||
|
_column_127,
|
||||||
|
_column_128,
|
||||||
|
_column_129,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape35 stackEntity = Shape35(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'stack_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_121,
|
||||||
|
_column_130,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape36 localAssetEntity = Shape36(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_108,
|
||||||
|
_column_113,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_116,
|
||||||
|
_column_117,
|
||||||
|
_column_118,
|
||||||
|
_column_107,
|
||||||
|
_column_131,
|
||||||
|
_column_120,
|
||||||
|
_column_132,
|
||||||
|
_column_133,
|
||||||
|
_column_134,
|
||||||
|
_column_135,
|
||||||
|
_column_136,
|
||||||
|
_column_137,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape48 remoteAlbumEntity = Shape48(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_138,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_139,
|
||||||
|
_column_140,
|
||||||
|
_column_141,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape38 localAlbumEntity = Shape38(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_album_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_115,
|
||||||
|
_column_142,
|
||||||
|
_column_143,
|
||||||
|
_column_144,
|
||||||
|
_column_145,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape39 localAlbumAssetEntity = Shape39(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_album_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||||
|
columns: [_column_146, _column_147, _column_145],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index(
|
||||||
|
'idx_local_album_asset_album_asset',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxLocalAssetChecksum = i1.Index(
|
||||||
|
'idx_local_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index idxLocalAssetCloudId = i1.Index(
|
||||||
|
'idx_local_asset_cloud_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxStackPrimaryAssetId = i1.Index(
|
||||||
|
'idx_stack_primary_asset_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)',
|
||||||
|
);
|
||||||
|
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
|
||||||
|
'UQ_remote_assets_owner_checksum',
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
|
||||||
|
);
|
||||||
|
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
|
||||||
|
'UQ_remote_assets_owner_library_checksum',
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetChecksum = i1.Index(
|
||||||
|
'idx_remote_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetStackId = i1.Index(
|
||||||
|
'idx_remote_asset_stack_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetOwnerVisibilityDeletedCreated = i1.Index(
|
||||||
|
'idx_remote_asset_owner_visibility_deleted_created',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)',
|
||||||
|
);
|
||||||
|
late final Shape40 authUserEntity = Shape40(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'auth_user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_109,
|
||||||
|
_column_148,
|
||||||
|
_column_110,
|
||||||
|
_column_111,
|
||||||
|
_column_149,
|
||||||
|
_column_150,
|
||||||
|
_column_151,
|
||||||
|
_column_152,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape4 userMetadataEntity = Shape4(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_metadata_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
|
||||||
|
columns: [_column_153, _column_154, _column_155],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape41 partnerEntity = Shape41(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'partner_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
|
||||||
|
columns: [_column_156, _column_157, _column_158],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape42 remoteExifEntity = Shape42(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_exif_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_159,
|
||||||
|
_column_160,
|
||||||
|
_column_161,
|
||||||
|
_column_162,
|
||||||
|
_column_163,
|
||||||
|
_column_164,
|
||||||
|
_column_117,
|
||||||
|
_column_116,
|
||||||
|
_column_165,
|
||||||
|
_column_166,
|
||||||
|
_column_167,
|
||||||
|
_column_168,
|
||||||
|
_column_135,
|
||||||
|
_column_136,
|
||||||
|
_column_169,
|
||||||
|
_column_170,
|
||||||
|
_column_171,
|
||||||
|
_column_172,
|
||||||
|
_column_173,
|
||||||
|
_column_174,
|
||||||
|
_column_175,
|
||||||
|
_column_176,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape7 remoteAlbumAssetEntity = Shape7(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||||
|
columns: [_column_159, _column_177],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape10 remoteAlbumUserEntity = Shape10(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
|
||||||
|
columns: [_column_177, _column_153, _column_178],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape43 remoteAssetCloudIdEntity = Shape43(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_asset_cloud_id_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_159,
|
||||||
|
_column_179,
|
||||||
|
_column_180,
|
||||||
|
_column_134,
|
||||||
|
_column_135,
|
||||||
|
_column_136,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape44 memoryEntity = Shape44(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'memory_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_124,
|
||||||
|
_column_121,
|
||||||
|
_column_113,
|
||||||
|
_column_181,
|
||||||
|
_column_182,
|
||||||
|
_column_183,
|
||||||
|
_column_184,
|
||||||
|
_column_185,
|
||||||
|
_column_186,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape12 memoryAssetEntity = Shape12(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'memory_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
|
||||||
|
columns: [_column_159, _column_187],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape45 personEntity = Shape45(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'person_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_121,
|
||||||
|
_column_108,
|
||||||
|
_column_188,
|
||||||
|
_column_189,
|
||||||
|
_column_190,
|
||||||
|
_column_191,
|
||||||
|
_column_192,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape46 assetFaceEntity = Shape46(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'asset_face_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_159,
|
||||||
|
_column_193,
|
||||||
|
_column_194,
|
||||||
|
_column_195,
|
||||||
|
_column_196,
|
||||||
|
_column_197,
|
||||||
|
_column_198,
|
||||||
|
_column_199,
|
||||||
|
_column_200,
|
||||||
|
_column_201,
|
||||||
|
_column_124,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape18 storeEntity = Shape18(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'store_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [_column_202, _column_203, _column_204],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape47 trashedLocalAssetEntity = Shape47(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'trashed_local_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id, album_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_108,
|
||||||
|
_column_113,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_116,
|
||||||
|
_column_117,
|
||||||
|
_column_118,
|
||||||
|
_column_107,
|
||||||
|
_column_205,
|
||||||
|
_column_131,
|
||||||
|
_column_120,
|
||||||
|
_column_132,
|
||||||
|
_column_206,
|
||||||
|
_column_137,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape32 assetEditEntity = Shape32(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'asset_edit_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_159,
|
||||||
|
_column_207,
|
||||||
|
_column_208,
|
||||||
|
_column_209,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape49 settings = Shape49(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'settings',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY("key")'],
|
||||||
|
columns: [_column_210, _column_211, _column_115],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
final i1.Index idxPartnerSharedWithId = i1.Index(
|
||||||
|
'idx_partner_shared_with_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxLatLng = i1.Index(
|
||||||
|
'idx_lat_lng',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteExifCity = i1.Index(
|
||||||
|
'idx_remote_exif_city',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index(
|
||||||
|
'idx_remote_album_asset_album_asset',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetCloudId = i1.Index(
|
||||||
|
'idx_remote_asset_cloud_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxPersonOwnerId = i1.Index(
|
||||||
|
'idx_person_owner_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxAssetFacePersonId = i1.Index(
|
||||||
|
'idx_asset_face_person_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxAssetFaceAssetId = i1.Index(
|
||||||
|
'idx_asset_face_asset_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxAssetFaceVisiblePerson = i1.Index(
|
||||||
|
'idx_asset_face_visible_person',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL',
|
||||||
|
);
|
||||||
|
final i1.Index idxTrashedLocalAssetChecksum = i1.Index(
|
||||||
|
'idx_trashed_local_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index idxTrashedLocalAssetAlbum = i1.Index(
|
||||||
|
'idx_trashed_local_asset_album',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxAssetEditAssetId = i1.Index(
|
||||||
|
'idx_asset_edit_asset_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
i0.MigrationStepWithVersion migrationSteps({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
@@ -13565,6 +14109,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
|
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
|
||||||
required Future<void> Function(i1.Migrator m, Schema25 schema) from24To25,
|
required Future<void> Function(i1.Migrator m, Schema25 schema) from24To25,
|
||||||
required Future<void> Function(i1.Migrator m, Schema26 schema) from25To26,
|
required Future<void> Function(i1.Migrator m, Schema26 schema) from25To26,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema27 schema) from26To27,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
@@ -13693,6 +14238,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from25To26(migrator, schema);
|
await from25To26(migrator, schema);
|
||||||
return 26;
|
return 26;
|
||||||
|
case 26:
|
||||||
|
final schema = Schema27(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from26To27(migrator, schema);
|
||||||
|
return 27;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
@@ -13725,6 +14275,7 @@ i1.OnUpgrade stepByStep({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
|
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
|
||||||
required Future<void> Function(i1.Migrator m, Schema25 schema) from24To25,
|
required Future<void> Function(i1.Migrator m, Schema25 schema) from24To25,
|
||||||
required Future<void> Function(i1.Migrator m, Schema26 schema) from25To26,
|
required Future<void> Function(i1.Migrator m, Schema26 schema) from25To26,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema27 schema) from26To27,
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
@@ -13752,5 +14303,6 @@ i1.OnUpgrade stepByStep({
|
|||||||
from23To24: from23To24,
|
from23To24: from23To24,
|
||||||
from24To25: from24To25,
|
from24To25: from24To25,
|
||||||
from25To26: from25To26,
|
from25To26: from25To26,
|
||||||
|
from26To27: from26To27,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/system_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/extensions/string_extensions.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
|
||||||
|
|
||||||
class MetadataRepository extends DriftDatabaseRepository {
|
|
||||||
final Drift _db;
|
|
||||||
final Map<MetadataKey, Object> _cache = {};
|
|
||||||
|
|
||||||
MetadataRepository._(this._db) : super(_db);
|
|
||||||
|
|
||||||
static MetadataRepository? _instance;
|
|
||||||
|
|
||||||
static MetadataRepository get instance {
|
|
||||||
final instance = _instance;
|
|
||||||
if (instance == null) {
|
|
||||||
throw StateError('MetadataRepository not initialized. Call ensureInitialized() first');
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppConfig _appConfig = const .new();
|
|
||||||
AppConfig get appConfig => _appConfig;
|
|
||||||
|
|
||||||
SystemConfig _systemConfig = const .new();
|
|
||||||
SystemConfig get systemConfig => _systemConfig;
|
|
||||||
|
|
||||||
static Future<MetadataRepository> ensureInitialized(Drift db) async {
|
|
||||||
if (_instance == null) {
|
|
||||||
final instance = MetadataRepository._(db);
|
|
||||||
await instance._hydrate();
|
|
||||||
_instance = instance;
|
|
||||||
}
|
|
||||||
return _instance!;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> refresh() async {
|
|
||||||
instance._cache.clear();
|
|
||||||
instance._appConfig = const .new();
|
|
||||||
instance._systemConfig = const .new();
|
|
||||||
await instance._hydrate();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _hydrate() async => _hydrateCache(await _db.select(_db.metadataEntity).get());
|
|
||||||
|
|
||||||
T _read<T extends Object>(MetadataKey<T> key) => (_cache[key] as T?) ?? key.defaultValue;
|
|
||||||
|
|
||||||
Future<void> write<T extends Object, U extends T>(MetadataKey<T> key, U value) async {
|
|
||||||
if (_read(key) == value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _db
|
|
||||||
.into(_db.metadataEntity)
|
|
||||||
.insertOnConflictUpdate(
|
|
||||||
MetadataEntityCompanion.insert(key: key.key, value: key.encode(value), updatedAt: Value(DateTime.now())),
|
|
||||||
);
|
|
||||||
_updateCache(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> delete<T extends Object>(MetadataKey<T> key) async {
|
|
||||||
await (_db.delete(_db.metadataEntity)..where((t) => t.key.equals(key.key))).go();
|
|
||||||
_updateCache(key, key.defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<AppConfig> watchAppConfig() => _watchDomain(.appConfig).distinct();
|
|
||||||
|
|
||||||
Stream<SystemConfig> watchSystemConfig() => _watchDomain(.systemConfig).distinct();
|
|
||||||
|
|
||||||
Stream<T> _watchDomain<T extends Object>(MetadataDomain<T> domain) {
|
|
||||||
final query = _db.select(_db.metadataEntity)..where((t) => t.key.like('${domain.prefix}.%'));
|
|
||||||
return query.watch().map((rows) {
|
|
||||||
_hydrateCache(rows);
|
|
||||||
return domain.config(this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _hydrateCache(List<MetadataEntityData> rows) {
|
|
||||||
final keyMap = MetadataKey.asKeyMap();
|
|
||||||
for (final row in rows) {
|
|
||||||
final key = keyMap[row.key];
|
|
||||||
if (key == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_updateCache(key, key.decode(row.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateCache<T extends Object>(MetadataKey<T> key, T value) {
|
|
||||||
if (_cache[key] == value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_cache[key] = value;
|
|
||||||
key.domain.rebuild(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension<T extends Object> on MetadataDomain<T> {
|
|
||||||
T config(MetadataRepository repo) => switch (this) {
|
|
||||||
.appConfig => repo._appConfig as T,
|
|
||||||
.systemConfig => repo._systemConfig as T,
|
|
||||||
};
|
|
||||||
|
|
||||||
void rebuild(MetadataRepository repo) {
|
|
||||||
switch (this) {
|
|
||||||
case .appConfig:
|
|
||||||
repo._appConfig = .new(
|
|
||||||
theme: .new(
|
|
||||||
mode: repo._read(.themeMode),
|
|
||||||
primaryColor: repo._read(.themePrimaryColor),
|
|
||||||
dynamicTheme: repo._read(.themeDynamic),
|
|
||||||
colorfulInterface: repo._read(.themeColorfulInterface),
|
|
||||||
),
|
|
||||||
cleanup: .new(
|
|
||||||
keepFavorites: repo._read(.cleanupKeepFavorites),
|
|
||||||
keepMediaType: repo._read(.cleanupKeepMediaType),
|
|
||||||
keepAlbumIds: repo._read(.cleanupKeepAlbumIds),
|
|
||||||
cutoffDaysAgo: repo._read(.cleanupCutoffDaysAgo),
|
|
||||||
defaultsInitialized: repo._read(.cleanupDefaultsInitialized),
|
|
||||||
),
|
|
||||||
map: .new(
|
|
||||||
relativeDays: repo._read(.mapRelativeDate),
|
|
||||||
favoritesOnly: repo._read(.mapShowFavoriteOnly),
|
|
||||||
includeArchived: repo._read(.mapIncludeArchived),
|
|
||||||
themeMode: repo._read(.mapThemeMode),
|
|
||||||
withPartners: repo._read(.mapWithPartners),
|
|
||||||
),
|
|
||||||
timeline: .new(
|
|
||||||
tilesPerRow: repo._read(.timelineTilesPerRow),
|
|
||||||
groupAssetsBy: repo._read(.timelineGroupAssetsBy),
|
|
||||||
storageIndicator: repo._read(.timelineStorageIndicator),
|
|
||||||
),
|
|
||||||
image: .new(preferRemote: repo._read(.imagePreferRemote), loadOriginal: repo._read(.imageLoadOriginal)),
|
|
||||||
viewer: .new(
|
|
||||||
loopVideo: repo._read(.viewerLoopVideo),
|
|
||||||
loadOriginalVideo: repo._read(.viewerLoadOriginalVideo),
|
|
||||||
autoPlayVideo: repo._read(.viewerAutoPlayVideo),
|
|
||||||
tapToNavigate: repo._read(.viewerTapToNavigate),
|
|
||||||
),
|
|
||||||
slideshow: .new(
|
|
||||||
transition: repo._read(.slideshowTransition),
|
|
||||||
repeat: repo._read(.slideshowRepeat),
|
|
||||||
duration: repo._read(.slideshowDuration),
|
|
||||||
look: repo._read(.slideshowLook),
|
|
||||||
direction: repo._read(.slideshowDirection),
|
|
||||||
),
|
|
||||||
album: .new(
|
|
||||||
sortMode: repo._read(.albumSortMode),
|
|
||||||
isReverse: repo._read(.albumIsReverse),
|
|
||||||
isGrid: repo._read(.albumIsGrid),
|
|
||||||
),
|
|
||||||
backup: .new(
|
|
||||||
enabled: repo._read(.backupEnabled),
|
|
||||||
useCellularForVideos: repo._read(.backupUseCellularForVideos),
|
|
||||||
useCellularForPhotos: repo._read(.backupUseCellularForPhotos),
|
|
||||||
requireCharging: repo._read(.backupRequireCharging),
|
|
||||||
triggerDelay: repo._read(.backupTriggerDelay),
|
|
||||||
syncAlbums: repo._read(.backupSyncAlbums),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case .systemConfig:
|
|
||||||
repo._systemConfig = .new(
|
|
||||||
logLevel: repo._read(.logLevel),
|
|
||||||
network: .new(
|
|
||||||
autoEndpointSwitching: repo._read(.networkAutoEndpointSwitching),
|
|
||||||
preferredWifiName: repo._read(.networkPreferredWifiName).nullIfEmpty,
|
|
||||||
localEndpoint: repo._read(.networkLocalEndpoint).nullIfEmpty,
|
|
||||||
externalEndpointList: repo._read(.networkExternalEndpointList),
|
|
||||||
customHeaders: repo._read(.networkCustomHeaders),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/settings_key.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
|
||||||
|
class SettingsRepository extends DriftDatabaseRepository {
|
||||||
|
final Drift _db;
|
||||||
|
|
||||||
|
SettingsRepository._(this._db) : super(_db);
|
||||||
|
|
||||||
|
static SettingsRepository? _instance;
|
||||||
|
|
||||||
|
static SettingsRepository get instance {
|
||||||
|
final instance = _instance;
|
||||||
|
if (instance == null) {
|
||||||
|
throw StateError('SettingsRepository not initialized. Call ensureInitialized() first');
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppConfig _appConfig = const .new();
|
||||||
|
AppConfig get appConfig => _appConfig;
|
||||||
|
|
||||||
|
static Future<SettingsRepository> ensureInitialized(Drift db) async {
|
||||||
|
if (_instance == null) {
|
||||||
|
final instance = SettingsRepository._(db);
|
||||||
|
await instance.refresh();
|
||||||
|
_instance = instance;
|
||||||
|
}
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refresh() async => _applyOverrides(await _db.select(_db.settingsEntity).get());
|
||||||
|
|
||||||
|
Future<void> clear(Iterable<SettingsKey> keys) async {
|
||||||
|
if (keys.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final names = keys.map((key) => key.name).toList();
|
||||||
|
await (_db.delete(_db.settingsEntity)..where((row) => row.key.isIn(names))).go();
|
||||||
|
|
||||||
|
for (final key in keys) {
|
||||||
|
_appConfig = _appConfig.write(key, defaultConfig.read(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> write<T extends Object, U extends T>(SettingsKey<T> key, U value) async {
|
||||||
|
if (value == _appConfig.read(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == defaultConfig.read(key)) {
|
||||||
|
return clear([key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _db
|
||||||
|
.into(_db.settingsEntity)
|
||||||
|
.insertOnConflictUpdate(
|
||||||
|
SettingsEntityCompanion.insert(key: key.name, value: key.encode(value), updatedAt: Value(DateTime.now())),
|
||||||
|
);
|
||||||
|
_appConfig = _appConfig.write(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<AppConfig> watchConfig() => _db.select(_db.settingsEntity).watch().map((rows) {
|
||||||
|
_applyOverrides(rows);
|
||||||
|
return _appConfig;
|
||||||
|
});
|
||||||
|
|
||||||
|
void _applyOverrides(List<SettingsEntityData> rows) {
|
||||||
|
_appConfig = AppConfig.fromEntries(
|
||||||
|
rows.fold({}, (overrides, row) {
|
||||||
|
final metadataKey = SettingsKey.values.firstWhereOrNull((key) => key.name == row.key);
|
||||||
|
if (metadataKey == null) {
|
||||||
|
return overrides;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {...overrides, metadataKey: metadataKey.decode(row.value)};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
|||||||
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/providers/locale_provider.dart';
|
import 'package:immich_mobile/providers/locale_provider.dart';
|
||||||
import 'package:immich_mobile/providers/routes.provider.dart';
|
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
|||||||
import 'package:immich_mobile/domain/services/sync_linked_album.service.dart';
|
import 'package:immich_mobile/domain/services/sync_linked_album.service.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart';
|
import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart';
|
||||||
@@ -43,7 +43,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
|||||||
_searchController = TextEditingController();
|
_searchController = TextEditingController();
|
||||||
_searchFocusNode = FocusNode();
|
_searchFocusNode = FocusNode();
|
||||||
|
|
||||||
_enableSyncUploadAlbum.value = ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
_enableSyncUploadAlbum.value = ref.read(appConfigProvider).backup.syncAlbums;
|
||||||
ref.read(backupAlbumProvider.notifier).getAll();
|
ref.read(backupAlbumProvider.notifier).getAll();
|
||||||
|
|
||||||
_initialTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
|
_initialTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
|
||||||
@@ -55,7 +55,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final enableSyncUploadAlbum = ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
final enableSyncUploadAlbum = ref.read(appConfigProvider).backup.syncAlbums;
|
||||||
final selectedAlbums = ref
|
final selectedAlbums = ref
|
||||||
.read(backupAlbumProvider)
|
.read(backupAlbumProvider)
|
||||||
.where((a) => a.backupSelection == BackupSelection.selected)
|
.where((a) => a.backupSelection == BackupSelection.selected)
|
||||||
@@ -103,7 +103,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final isBackupEnabled = MetadataRepository.instance.appConfig.backup.enabled;
|
final isBackupEnabled = SettingsRepository.instance.appConfig.backup.enabled;
|
||||||
await ref.read(driftBackupProvider.notifier).getBackupStatus(user.id);
|
await ref.read(driftBackupProvider.notifier).getBackupStatus(user.id);
|
||||||
final currentTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
|
final currentTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
|
||||||
final totalChanged = currentTotalAssetCount != _initialTotalAssetCount;
|
final totalChanged = currentTotalAssetCount != _initialTotalAssetCount;
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import 'package:auto_route/auto_route.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/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/backup_settings/drift_backup_settings.dart';
|
import 'package:immich_mobile/widgets/settings/backup_settings/drift_backup_settings.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -19,7 +19,7 @@ class DriftBackupOptionsPage extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
bool hasPopped = false;
|
bool hasPopped = false;
|
||||||
final previousBackup = ref.read(metadataProvider).appConfig.backup;
|
final previousBackup = ref.read(appConfigProvider).backup;
|
||||||
final previousCellularForVideos = previousBackup.useCellularForVideos;
|
final previousCellularForVideos = previousBackup.useCellularForVideos;
|
||||||
final previousCellularForPhotos = previousBackup.useCellularForPhotos;
|
final previousCellularForPhotos = previousBackup.useCellularForPhotos;
|
||||||
return PopScope(
|
return PopScope(
|
||||||
@@ -27,7 +27,7 @@ class DriftBackupOptionsPage extends ConsumerWidget {
|
|||||||
// There is an issue with Flutter where the pop event
|
// There is an issue with Flutter where the pop event
|
||||||
// can be triggered multiple times, so we guard it with _hasPopped
|
// can be triggered multiple times, so we guard it with _hasPopped
|
||||||
|
|
||||||
final currentBackup = ref.read(metadataProvider).appConfig.backup;
|
final currentBackup = ref.read(appConfigProvider).backup;
|
||||||
final currentCellularForVideos = currentBackup.useCellularForVideos;
|
final currentCellularForVideos = currentBackup.useCellularForVideos;
|
||||||
final currentCellularForPhotos = currentBackup.useCellularForPhotos;
|
final currentCellularForPhotos = currentBackup.useCellularForPhotos;
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ class DriftBackupOptionsPage extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
||||||
final isBackupEnabled = MetadataRepository.instance.appConfig.backup.enabled;
|
final isBackupEnabled = SettingsRepository.instance.appConfig.backup.enabled;
|
||||||
if (!isBackupEnabled) {
|
if (!isBackupEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ 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/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/generated/translations.g.dart';
|
import 'package:immich_mobile/generated/translations.g.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
|
|
||||||
class SettingsHeader {
|
class SettingsHeader {
|
||||||
String key = "";
|
String key = "";
|
||||||
@@ -22,7 +21,7 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
|||||||
final headers = useState<List<SettingsHeader>>([]);
|
final headers = useState<List<SettingsHeader>>([]);
|
||||||
final setInitialHeaders = useState(false);
|
final setInitialHeaders = useState(false);
|
||||||
|
|
||||||
final storedHeaders = ref.read(metadataProvider).systemConfig.network.customHeaders;
|
final storedHeaders = ref.read(appConfigProvider).network.customHeaders;
|
||||||
if (!setInitialHeaders.value) {
|
if (!setInitialHeaders.value) {
|
||||||
storedHeaders.forEach((k, v) {
|
storedHeaders.forEach((k, v) {
|
||||||
final header = SettingsHeader();
|
final header = SettingsHeader();
|
||||||
@@ -94,7 +93,7 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
|||||||
headersMap[key] = value;
|
headersMap[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ref.read(metadataProvider).write(MetadataKey.networkCustomHeaders, headersMap);
|
await ref.read(settingsProvider).write(.networkCustomHeaders, headersMap);
|
||||||
await ref.read(apiServiceProvider).updateHeaders();
|
await ref.read(apiServiceProvider).updateHeaders();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/locales.dart';
|
import 'package:immich_mobile/constants/locales.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||||
import 'package:immich_mobile/generated/translations.g.dart';
|
import 'package:immich_mobile/generated/translations.g.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
@@ -36,7 +36,7 @@ class BootstrapErrorWidget extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext _) {
|
Widget build(BuildContext _) {
|
||||||
final immichTheme = MetadataKey.themePrimaryColor.defaultValue.themeOfPreset;
|
final immichTheme = defaultConfig.theme.primaryColor.themeOfPreset;
|
||||||
|
|
||||||
return EasyLocalization(
|
return EasyLocalization(
|
||||||
supportedLocales: locales.values.toList(),
|
supportedLocales: locales.values.toList(),
|
||||||
@@ -341,7 +341,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
await backgroundManager.hashAssets();
|
await backgroundManager.hashAssets();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MetadataRepository.instance.appConfig.backup.syncAlbums) {
|
if (SettingsRepository.instance.appConfig.backup.syncAlbums) {
|
||||||
await backgroundManager.syncLinkedAlbum();
|
await backgroundManager.syncLinkedAlbum();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -370,7 +370,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _resumeBackup(DriftBackupNotifier notifier) async {
|
Future<void> _resumeBackup(DriftBackupNotifier notifier) async {
|
||||||
final isEnableBackup = MetadataRepository.instance.appConfig.backup.enabled;
|
final isEnableBackup = SettingsRepository.instance.appConfig.backup.enabled;
|
||||||
|
|
||||||
if (isEnableBackup) {
|
if (isEnableBackup) {
|
||||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
final currentUser = Store.tryGet(StoreKey.currentUser);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.wid
|
|||||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
||||||
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
|
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_bu
|
|||||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
|
|
||||||
/// This delete action has the following behavior:
|
/// This delete action has the following behavior:
|
||||||
/// - Prompt to delete the asset locally
|
/// - Prompt to delete the asset locally
|
||||||
@@ -39,6 +40,8 @@ class DeleteLocalActionButton extends ConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ref.invalidate(localAlbumProvider);
|
||||||
|
|
||||||
final successMessage = 'delete_local_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
final successMessage = 'delete_local_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
|||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
|
|
||||||
class _SharePreparingDialog extends StatelessWidget {
|
class _SharePreparingDialog extends StatelessWidget {
|
||||||
const _SharePreparingDialog();
|
final ValueNotifier<double?> progress;
|
||||||
|
|
||||||
|
const _SharePreparingDialog({required this.progress});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -22,8 +24,24 @@ class _SharePreparingDialog extends StatelessWidget {
|
|||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const CircularProgressIndicator(),
|
Container(margin: const EdgeInsets.only(bottom: 12), child: const Text('share_dialog_preparing').tr()),
|
||||||
Container(margin: const EdgeInsets.only(top: 12), child: const Text('share_dialog_preparing').tr()),
|
SizedBox(
|
||||||
|
width: 240,
|
||||||
|
child: ValueListenableBuilder<double?>(
|
||||||
|
valueListenable: progress,
|
||||||
|
builder: (context, value, _) {
|
||||||
|
final percent = value == null ? null : (value * 100).clamp(0, 100);
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
LinearProgressIndicator(value: value, minHeight: 8.0),
|
||||||
|
if (percent != null)
|
||||||
|
Container(margin: const EdgeInsets.only(top: 8), child: Text('${percent.toStringAsFixed(0)}%')),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -43,32 +61,39 @@ class ShareActionButton extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final cancelCompleter = Completer<void>();
|
final cancelCompleter = Completer<void>();
|
||||||
const preparingDialog = _SharePreparingDialog();
|
final progress = ValueNotifier<double?>(null);
|
||||||
|
final preparingDialog = _SharePreparingDialog(progress: progress);
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext buildContext) {
|
builder: (BuildContext buildContext) {
|
||||||
ref.read(actionProvider.notifier).shareAssets(source, context, cancelCompleter: cancelCompleter).then((
|
ref
|
||||||
ActionResult result,
|
.read(actionProvider.notifier)
|
||||||
) {
|
.shareAssets(
|
||||||
if (cancelCompleter.isCompleted || !context.mounted) {
|
source,
|
||||||
return;
|
context,
|
||||||
}
|
cancelCompleter: cancelCompleter,
|
||||||
|
onAssetDownloadProgress: (value) => progress.value = value,
|
||||||
|
)
|
||||||
|
.then((ActionResult result) {
|
||||||
|
if (cancelCompleter.isCompleted || !context.mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ref.read(multiSelectProvider.notifier).reset();
|
ref.read(multiSelectProvider.notifier).reset();
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
context: context,
|
context: context,
|
||||||
msg: 'scaffold_body_error_occurred'.t(context: context),
|
msg: 'scaffold_body_error_occurred'.t(context: context),
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
toastType: ToastType.error,
|
toastType: ToastType.error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildContext.pop();
|
buildContext.pop();
|
||||||
});
|
});
|
||||||
|
|
||||||
// show a loading spinner with a "Preparing" message
|
// Show download progress with a "Preparing" message
|
||||||
return preparingDialog;
|
return preparingDialog;
|
||||||
},
|
},
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
@@ -77,6 +102,7 @@ class ShareActionButton extends ConsumerWidget {
|
|||||||
if (!cancelCompleter.isCompleted) {
|
if (!cancelCompleter.isCompleted) {
|
||||||
cancelCompleter.complete();
|
cancelCompleter.complete();
|
||||||
}
|
}
|
||||||
|
progress.dispose();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,11 @@ import 'package:immich_mobile/models/albums/album_search.model.dart';
|
|||||||
import 'package:immich_mobile/presentation/widgets/album/album_tile.dart';
|
import 'package:immich_mobile/presentation/widgets/album/album_tile.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/album/new_album_name_modal.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/album/new_album_name_modal.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.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/asset_viewer/asset_viewer.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
@@ -58,7 +57,7 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final albumConfig = ref.read(metadataProvider).appConfig.album;
|
final albumConfig = ref.read(appConfigProvider).album;
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
sort = AlbumSort(mode: albumConfig.sortMode, isReverse: albumConfig.isReverse);
|
sort = AlbumSort(mode: albumConfig.sortMode, isReverse: albumConfig.isReverse);
|
||||||
@@ -94,7 +93,7 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
isGrid = !isGrid;
|
isGrid = !isGrid;
|
||||||
});
|
});
|
||||||
ref.read(metadataProvider).write(MetadataKey.albumIsGrid, isGrid);
|
ref.read(settingsProvider).write(.albumIsGrid, isGrid);
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeFilter(QuickFilterMode mode) {
|
void changeFilter(QuickFilterMode mode) {
|
||||||
@@ -110,9 +109,9 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
|
|||||||
this.sort = sort;
|
this.sort = sort;
|
||||||
});
|
});
|
||||||
|
|
||||||
final metadata = ref.read(metadataProvider);
|
final metadata = ref.read(settingsProvider);
|
||||||
await metadata.write(MetadataKey.albumSortMode, sort.mode);
|
await metadata.write(.albumSortMode, sort.mode);
|
||||||
await metadata.write(MetadataKey.albumIsReverse, sort.isReverse);
|
await metadata.write(.albumIsReverse, sort.isReverse);
|
||||||
|
|
||||||
await sortAlbums();
|
await sortAlbums();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
|||||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
||||||
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
|
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
|
||||||
@@ -241,7 +241,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final tapToNavigate = ref.read(metadataProvider).appConfig.viewer.tapToNavigate;
|
final tapToNavigate = ref.read(appConfigProvider).viewer.tapToNavigate;
|
||||||
if (!tapToNavigate) {
|
if (!tapToNavigate) {
|
||||||
_viewer.toggleControls();
|
_viewer.toggleControls();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.pro
|
|||||||
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
|
||||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:native_video_player/native_video_player.dart';
|
import 'package:native_video_player/native_video_player.dart';
|
||||||
@@ -128,7 +128,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
|
|||||||
final remoteId = (videoAsset as RemoteAsset).id;
|
final remoteId = (videoAsset as RemoteAsset).id;
|
||||||
|
|
||||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||||
final isOriginalVideo = ref.read(metadataProvider).appConfig.viewer.loadOriginalVideo;
|
final isOriginalVideo = ref.read(appConfigProvider).viewer.loadOriginalVideo;
|
||||||
final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback';
|
final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback';
|
||||||
final String videoUrl = videoAsset.livePhotoVideoId != null
|
final String videoUrl = videoAsset.livePhotoVideoId != null
|
||||||
? '$serverEndpoint/assets/${videoAsset.livePhotoVideoId}/$postfixUrl'
|
? '$serverEndpoint/assets/${videoAsset.livePhotoVideoId}/$postfixUrl'
|
||||||
@@ -161,7 +161,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final autoPlayVideo = ref.read(metadataProvider).appConfig.viewer.autoPlayVideo;
|
final autoPlayVideo = ref.read(appConfigProvider).viewer.autoPlayVideo;
|
||||||
if (autoPlayVideo || widget.asset.isMotionPhoto) {
|
if (autoPlayVideo || widget.asset.isMotionPhoto) {
|
||||||
await _notifier.play();
|
await _notifier.play();
|
||||||
}
|
}
|
||||||
@@ -212,7 +212,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _notifier.load(source);
|
await _notifier.load(source);
|
||||||
final loopVideo = ref.read(metadataProvider).appConfig.viewer.loopVideo;
|
final loopVideo = ref.read(appConfigProvider).viewer.loopVideo;
|
||||||
await _notifier.setLoop(!widget.asset.isMotionPhoto && loopVideo);
|
await _notifier.setLoop(!widget.asset.isMotionPhoto && loopVideo);
|
||||||
await _notifier.setVolume(1);
|
await _notifier.setVolume(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/setting.model.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||||
@@ -34,7 +33,7 @@ class ViewerKebabMenu extends ConsumerWidget {
|
|||||||
final isInLockedView = ref.watch(inLockedViewProvider);
|
final isInLockedView = ref.watch(inLockedViewProvider);
|
||||||
final currentAlbum = ref.watch(currentRemoteAlbumProvider);
|
final currentAlbum = ref.watch(currentRemoteAlbumProvider);
|
||||||
final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive;
|
final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive;
|
||||||
final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(Setting.advancedTroubleshooting);
|
final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(.advancedTroubleshooting);
|
||||||
|
|
||||||
final actionContext = ActionButtonContext(
|
final actionContext = ActionButtonContext(
|
||||||
asset: asset,
|
asset: asset,
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
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/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
|
|
||||||
class BackupToggleButton extends ConsumerStatefulWidget {
|
class BackupToggleButton extends ConsumerStatefulWidget {
|
||||||
final VoidCallback onStart;
|
final VoidCallback onStart;
|
||||||
@@ -31,7 +30,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
|
|||||||
end: 1,
|
end: 1,
|
||||||
).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeInOut));
|
).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeInOut));
|
||||||
|
|
||||||
_isEnabled = ref.read(metadataProvider).appConfig.backup.enabled;
|
_isEnabled = ref.read(appConfigProvider).backup.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -41,7 +40,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onToggle(bool value) async {
|
Future<void> _onToggle(bool value) async {
|
||||||
await ref.read(metadataProvider).write(MetadataKey.backupEnabled, value);
|
await ref.read(settingsProvider).write(.backupEnabled, value);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isEnabled = value;
|
_isEnabled = value;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:async/async.dart';
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
|
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
||||||
@@ -189,5 +189,5 @@ ImageProvider? getThumbnailImageProvider(BaseAsset asset, {Size size = kThumbnai
|
|||||||
|
|
||||||
bool _shouldUseLocalAsset(BaseAsset asset) =>
|
bool _shouldUseLocalAsset(BaseAsset asset) =>
|
||||||
asset.hasLocal &&
|
asset.hasLocal &&
|
||||||
(!asset.hasRemote || !MetadataRepository.instance.appConfig.image.preferRemote) &&
|
(!asset.hasRemote || !SettingsRepository.instance.appConfig.image.preferRemote) &&
|
||||||
!asset.isEdited;
|
!asset.isEdited;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
|
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart';
|
import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart';
|
import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart';
|
||||||
@@ -104,7 +104,7 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final loadOriginal = MetadataRepository.instance.appConfig.image.loadOriginal;
|
final loadOriginal = SettingsRepository.instance.appConfig.image.loadOriginal;
|
||||||
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
|
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
|
||||||
var request = this.request = LocalImageRequest(
|
var request = this.request = LocalImageRequest(
|
||||||
localId: key.id,
|
localId: key.id,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
|
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart';
|
import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart';
|
import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart';
|
||||||
@@ -122,7 +122,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
|||||||
edited: key.edited,
|
edited: key.edited,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final loadOriginal = assetType == AssetType.image && MetadataRepository.instance.appConfig.image.loadOriginal;
|
final loadOriginal = assetType == AssetType.image && SettingsRepository.instance.appConfig.image.loadOriginal;
|
||||||
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
|
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
|
||||||
|
|
||||||
if (!loadOriginal) {
|
if (!loadOriginal) {
|
||||||
|
|||||||
@@ -296,16 +296,12 @@ class _ThumbnailRenderBox extends RenderBox {
|
|||||||
bool isRepaintBoundary = true;
|
bool isRepaintBoundary = true;
|
||||||
|
|
||||||
_ThumbnailRenderBox({
|
_ThumbnailRenderBox({
|
||||||
required ui.Image? image,
|
required this._image,
|
||||||
required ui.Image? previousImage,
|
required this._previousImage,
|
||||||
required double fadeValue,
|
required this._fadeValue,
|
||||||
required BoxFit fit,
|
required this._fit,
|
||||||
required Gradient placeholderGradient,
|
required this._placeholderGradient,
|
||||||
}) : _image = image,
|
});
|
||||||
_previousImage = previousImage,
|
|
||||||
_fadeValue = fadeValue,
|
|
||||||
_fit = fit,
|
|
||||||
_placeholderGradient = placeholderGradient;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(PaintingContext context, Offset offset) {
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'
|
|||||||
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart';
|
import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||||
|
|
||||||
class ThumbnailTile extends ConsumerStatefulWidget {
|
class ThumbnailTile extends ConsumerStatefulWidget {
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
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/domain/models/events.model.dart';
|
import 'package:immich_mobile/domain/models/events.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/map.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/map.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/map/map_state.provider.dart';
|
import 'package:immich_mobile/providers/map/map_state.provider.dart';
|
||||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||||
|
|
||||||
@@ -81,25 +80,25 @@ class MapStateNotifier extends Notifier<MapState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void switchFavoriteOnly(bool isFavoriteOnly) {
|
void switchFavoriteOnly(bool isFavoriteOnly) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapShowFavoriteOnly, isFavoriteOnly);
|
ref.read(settingsProvider).write(.mapShowFavoriteOnly, isFavoriteOnly);
|
||||||
state = state.copyWith(onlyFavorites: isFavoriteOnly);
|
state = state.copyWith(onlyFavorites: isFavoriteOnly);
|
||||||
EventStream.shared.emit(const MapMarkerReloadEvent());
|
EventStream.shared.emit(const MapMarkerReloadEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchIncludeArchived(bool isIncludeArchived) {
|
void switchIncludeArchived(bool isIncludeArchived) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapIncludeArchived, isIncludeArchived);
|
ref.read(settingsProvider).write(.mapIncludeArchived, isIncludeArchived);
|
||||||
state = state.copyWith(includeArchived: isIncludeArchived);
|
state = state.copyWith(includeArchived: isIncludeArchived);
|
||||||
EventStream.shared.emit(const MapMarkerReloadEvent());
|
EventStream.shared.emit(const MapMarkerReloadEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchWithPartners(bool isWithPartners) {
|
void switchWithPartners(bool isWithPartners) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapWithPartners, isWithPartners);
|
ref.read(settingsProvider).write(.mapWithPartners, isWithPartners);
|
||||||
state = state.copyWith(withPartners: isWithPartners);
|
state = state.copyWith(withPartners: isWithPartners);
|
||||||
EventStream.shared.emit(const MapMarkerReloadEvent());
|
EventStream.shared.emit(const MapMarkerReloadEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
void setRelativeTime(int relativeDays) {
|
void setRelativeTime(int relativeDays) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapRelativeDate, relativeDays);
|
ref.read(settingsProvider).write(.mapRelativeDate, relativeDays);
|
||||||
state = state.copyWith(relativeDays: relativeDays);
|
state = state.copyWith(relativeDays: relativeDays);
|
||||||
EventStream.shared.emit(const MapMarkerReloadEvent());
|
EventStream.shared.emit(const MapMarkerReloadEvent());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,14 +62,11 @@ class RenderFixedRow extends RenderBox
|
|||||||
RenderBoxContainerDefaultsMixin<RenderBox, _RowParentData> {
|
RenderBoxContainerDefaultsMixin<RenderBox, _RowParentData> {
|
||||||
RenderFixedRow({
|
RenderFixedRow({
|
||||||
List<RenderBox>? children,
|
List<RenderBox>? children,
|
||||||
required double height,
|
required this._height,
|
||||||
required List<double> widths,
|
required this._widths,
|
||||||
required double spacing,
|
required this._spacing,
|
||||||
required TextDirection textDirection,
|
required this._textDirection,
|
||||||
}) : _height = height,
|
}) {
|
||||||
_widths = widths,
|
|
||||||
_spacing = spacing,
|
|
||||||
_textDirection = textDirection {
|
|
||||||
addAll(children);
|
addAll(children);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -578,9 +578,7 @@ class _SlideFadeTransition extends StatelessWidget {
|
|||||||
final Animation<double> _animation;
|
final Animation<double> _animation;
|
||||||
final Widget _child;
|
final Widget _child;
|
||||||
|
|
||||||
const _SlideFadeTransition({required Animation<double> animation, required Widget child})
|
const _SlideFadeTransition({required this._animation, required this._child});
|
||||||
: _animation = animation,
|
|
||||||
_child = child;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'package:immich_mobile/domain/models/timeline.model.dart';
|
|||||||
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/fixed/segment_builder.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/fixed/segment_builder.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
|
|
||||||
class TimelineArgs {
|
class TimelineArgs {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import 'package:flutter/rendering.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/events.model.dart';
|
import 'package:immich_mobile/domain/models/events.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||||
@@ -22,7 +21,7 @@ import 'package:immich_mobile/presentation/widgets/timeline/scrubber.widget.dart
|
|||||||
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||||
@@ -397,7 +396,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
|||||||
final grid = CustomScrollView(
|
final grid = CustomScrollView(
|
||||||
primary: true,
|
primary: true,
|
||||||
physics: _scrollPhysics,
|
physics: _scrollPhysics,
|
||||||
cacheExtent: maxHeight * 2,
|
scrollCacheExtent: .pixels(maxHeight * 2),
|
||||||
slivers: [
|
slivers: [
|
||||||
if (isSelectionMode) const SelectionSliverAppBar() else if (widget.appBar != null) widget.appBar!,
|
if (isSelectionMode) const SelectionSliverAppBar() else if (widget.appBar != null) widget.appBar!,
|
||||||
if (widget.topSliverWidget != null) widget.topSliverWidget!,
|
if (widget.topSliverWidget != null) widget.topSliverWidget!,
|
||||||
@@ -459,7 +458,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
|||||||
_restoreAssetIndex = targetAssetIndex;
|
_restoreAssetIndex = targetAssetIndex;
|
||||||
});
|
});
|
||||||
|
|
||||||
ref.read(metadataProvider).write(MetadataKey.timelineTilesPerRow, _perRow);
|
ref.read(settingsProvider).write(.timelineTilesPerRow, _perRow);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -503,7 +502,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
|||||||
class _SliverSegmentedList extends SliverMultiBoxAdaptorWidget {
|
class _SliverSegmentedList extends SliverMultiBoxAdaptorWidget {
|
||||||
final List<Segment> _segments;
|
final List<Segment> _segments;
|
||||||
|
|
||||||
const _SliverSegmentedList({required List<Segment> segments, required super.delegate}) : _segments = segments;
|
const _SliverSegmentedList({required this._segments, required super.delegate});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_RenderSliverTimelineBoxAdaptor createRenderObject(BuildContext context) =>
|
_RenderSliverTimelineBoxAdaptor createRenderObject(BuildContext context) =>
|
||||||
@@ -527,8 +526,7 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
|||||||
markNeedsLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
_RenderSliverTimelineBoxAdaptor({required super.childManager, required List<Segment> segments})
|
_RenderSliverTimelineBoxAdaptor({required super.childManager, required this._segments});
|
||||||
: _segments = segments;
|
|
||||||
|
|
||||||
int getMinChildIndexForScrollOffset(double offset) =>
|
int getMinChildIndexForScrollOffset(double offset) =>
|
||||||
_segments.findByOffset(offset)?.getMinChildIndexForScrollOffset(offset) ?? 0;
|
_segments.findByOffset(offset)?.getMinChildIndexForScrollOffset(offset) ?? 0;
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import 'package:immich_mobile/providers/auth.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/notification_permission.provider.dart';
|
import 'package:immich_mobile/providers/notification_permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
@@ -107,7 +107,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
final backgroundManager = _ref.read(backgroundSyncProvider);
|
final backgroundManager = _ref.read(backgroundSyncProvider);
|
||||||
final isAlbumLinkedSyncEnable = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
final isAlbumLinkedSyncEnable = _ref.read(appConfigProvider).backup.syncAlbums;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
bool syncSuccess = false;
|
bool syncSuccess = false;
|
||||||
@@ -137,7 +137,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _resumeBackup() async {
|
Future<void> _resumeBackup() async {
|
||||||
final isEnableBackup = _ref.read(metadataProvider).appConfig.backup.enabled;
|
final isEnableBackup = _ref.read(appConfigProvider).backup.enabled;
|
||||||
|
|
||||||
if (isEnableBackup) {
|
if (isEnableBackup) {
|
||||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
final currentUser = Store.tryGet(StoreKey.currentUser);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'dart:convert';
|
|||||||
import 'package:flutter_udid/flutter_udid.dart';
|
import 'package:flutter_udid/flutter_udid.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/user.service.dart';
|
import 'package:immich_mobile/domain/services/user.service.dart';
|
||||||
@@ -11,7 +10,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
|||||||
import 'package:immich_mobile/models/auth/auth_state.model.dart';
|
import 'package:immich_mobile/models/auth/auth_state.model.dart';
|
||||||
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/services/auth.service.dart';
|
import 'package:immich_mobile/services/auth.service.dart';
|
||||||
@@ -130,7 +129,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
await _apiService.updateHeaders();
|
await _apiService.updateHeaders();
|
||||||
|
|
||||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||||
final headerMap = _ref.read(metadataProvider).systemConfig.network.customHeaders;
|
final headerMap = _ref.read(appConfigProvider).network.customHeaders;
|
||||||
final customHeaders = headerMap.isEmpty ? null : jsonEncode(headerMap);
|
final customHeaders = headerMap.isEmpty ? null : jsonEncode(headerMap);
|
||||||
await _widgetService.writeCredentials(serverEndpoint, accessToken, customHeaders);
|
await _widgetService.writeCredentials(serverEndpoint, accessToken, customHeaders);
|
||||||
|
|
||||||
@@ -179,19 +178,19 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveWifiName(String wifiName) async {
|
Future<void> saveWifiName(String wifiName) async {
|
||||||
await _ref.read(metadataProvider).write(MetadataKey.networkPreferredWifiName, wifiName);
|
await _ref.read(settingsProvider).write(.networkPreferredWifiName, wifiName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveLocalEndpoint(String url) async {
|
Future<void> saveLocalEndpoint(String url) async {
|
||||||
await _ref.read(metadataProvider).write(MetadataKey.networkLocalEndpoint, url);
|
await _ref.read(settingsProvider).write(.networkLocalEndpoint, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getSavedWifiName() {
|
String? getSavedWifiName() {
|
||||||
return _ref.read(metadataProvider).systemConfig.network.preferredWifiName;
|
return _ref.read(appConfigProvider).network.preferredWifiName;
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getSavedLocalEndpoint() {
|
String? getSavedLocalEndpoint() {
|
||||||
return _ref.read(metadataProvider).systemConfig.network.localEndpoint;
|
return _ref.read(appConfigProvider).network.localEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current server endpoint (with /api) URL from the store
|
/// Returns the current server endpoint (with /api) URL from the store
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/services/cleanup.service.dart';
|
import 'package:immich_mobile/services/cleanup.service.dart';
|
||||||
|
|
||||||
@@ -54,21 +54,21 @@ final cleanupProvider = StateNotifierProvider<CleanupNotifier, CleanupState>((re
|
|||||||
return CleanupNotifier(
|
return CleanupNotifier(
|
||||||
ref.watch(cleanupServiceProvider),
|
ref.watch(cleanupServiceProvider),
|
||||||
ref.watch(currentUserProvider)?.id,
|
ref.watch(currentUserProvider)?.id,
|
||||||
ref.watch(metadataProvider),
|
ref.watch(settingsProvider),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
class CleanupNotifier extends StateNotifier<CleanupState> {
|
class CleanupNotifier extends StateNotifier<CleanupState> {
|
||||||
final CleanupService _cleanupService;
|
final CleanupService _cleanupService;
|
||||||
final String? _userId;
|
final String? _userId;
|
||||||
final MetadataRepository _metadataRepository;
|
final SettingsRepository _settingsRepository;
|
||||||
|
|
||||||
CleanupNotifier(this._cleanupService, this._userId, this._metadataRepository) : super(const CleanupState()) {
|
CleanupNotifier(this._cleanupService, this._userId, this._settingsRepository) : super(const CleanupState()) {
|
||||||
_loadPersistedSettings();
|
_loadPersistedSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _loadPersistedSettings() {
|
void _loadPersistedSettings() {
|
||||||
final cleanup = _metadataRepository.appConfig.cleanup;
|
final cleanup = _settingsRepository.appConfig.cleanup;
|
||||||
final keepFavorites = cleanup.keepFavorites;
|
final keepFavorites = cleanup.keepFavorites;
|
||||||
final keepMediaType = cleanup.keepMediaType;
|
final keepMediaType = cleanup.keepMediaType;
|
||||||
final keepAlbumIds = cleanup.keepAlbumIds.toSet();
|
final keepAlbumIds = cleanup.keepAlbumIds.toSet();
|
||||||
@@ -87,18 +87,18 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
|
|||||||
state = state.copyWith(selectedDate: date, assetsToDelete: []);
|
state = state.copyWith(selectedDate: date, assetsToDelete: []);
|
||||||
if (date != null) {
|
if (date != null) {
|
||||||
final daysAgo = DateTime.now().difference(date).inDays;
|
final daysAgo = DateTime.now().difference(date).inDays;
|
||||||
_metadataRepository.write(.cleanupCutoffDaysAgo, daysAgo);
|
_settingsRepository.write(.cleanupCutoffDaysAgo, daysAgo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setKeepMediaType(AssetKeepType keepMediaType) {
|
void setKeepMediaType(AssetKeepType keepMediaType) {
|
||||||
state = state.copyWith(keepMediaType: keepMediaType, assetsToDelete: []);
|
state = state.copyWith(keepMediaType: keepMediaType, assetsToDelete: []);
|
||||||
_metadataRepository.write(.cleanupKeepMediaType, keepMediaType);
|
_settingsRepository.write(.cleanupKeepMediaType, keepMediaType);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setKeepFavorites(bool keepFavorites) {
|
void setKeepFavorites(bool keepFavorites) {
|
||||||
state = state.copyWith(keepFavorites: keepFavorites, assetsToDelete: []);
|
state = state.copyWith(keepFavorites: keepFavorites, assetsToDelete: []);
|
||||||
_metadataRepository.write(.cleanupKeepFavorites, keepFavorites);
|
_settingsRepository.write(.cleanupKeepFavorites, keepFavorites);
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleKeepAlbum(String albumId) {
|
void toggleKeepAlbum(String albumId) {
|
||||||
@@ -118,7 +118,7 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _persistExcludedAlbumIds(Set<String> albumIds) {
|
void _persistExcludedAlbumIds(Set<String> albumIds) {
|
||||||
_metadataRepository.write(.cleanupKeepAlbumIds, albumIds.toList());
|
_settingsRepository.write(.cleanupKeepAlbumIds, albumIds.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanupStaleAlbumIds(Set<String> existingAlbumIds) {
|
void cleanupStaleAlbumIds(Set<String> existingAlbumIds) {
|
||||||
@@ -131,7 +131,7 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void applyDefaultAlbumSelections(List<(String id, String name)> albums) {
|
void applyDefaultAlbumSelections(List<(String id, String name)> albums) {
|
||||||
final isInitialized = _metadataRepository.appConfig.cleanup.defaultsInitialized;
|
final isInitialized = _settingsRepository.appConfig.cleanup.defaultsInitialized;
|
||||||
if (isInitialized) {
|
if (isInitialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,7 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
|
|||||||
_persistExcludedAlbumIds(keepAlbumIds);
|
_persistExcludedAlbumIds(keepAlbumIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
_metadataRepository.write(.cleanupDefaultsInitialized, true);
|
_settingsRepository.write(.cleanupDefaultsInitialized, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> scanAssets() async {
|
Future<void> scanAssets() async {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.da
|
|||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart' show assetExifProvider;
|
import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart' show assetExifProvider;
|
||||||
import 'package:immich_mobile/providers/infrastructure/tag.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/tag.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
@@ -21,6 +22,7 @@ import 'package:immich_mobile/routing/router.dart';
|
|||||||
import 'package:immich_mobile/services/action.service.dart';
|
import 'package:immich_mobile/services/action.service.dart';
|
||||||
import 'package:immich_mobile/services/download.service.dart';
|
import 'package:immich_mobile/services/download.service.dart';
|
||||||
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/semver.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart';
|
import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
@@ -463,11 +465,17 @@ class ActionNotifier extends Notifier<void> {
|
|||||||
ActionSource source,
|
ActionSource source,
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
Completer<void>? cancelCompleter,
|
Completer<void>? cancelCompleter,
|
||||||
|
void Function(double progress)? onAssetDownloadProgress,
|
||||||
}) async {
|
}) async {
|
||||||
final ids = _getAssets(source).toList(growable: false);
|
final ids = _getAssets(source).toList(growable: false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _service.shareAssets(ids, context, cancelCompleter: cancelCompleter);
|
await _service.shareAssets(
|
||||||
|
ids,
|
||||||
|
context,
|
||||||
|
cancelCompleter: cancelCompleter,
|
||||||
|
onAssetDownloadProgress: onAssetDownloadProgress,
|
||||||
|
);
|
||||||
return ActionResult(count: ids.length, success: true);
|
return ActionResult(count: ids.length, success: true);
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
_logger.severe('Failed to share assets', error, stack);
|
_logger.severe('Failed to share assets', error, stack);
|
||||||
@@ -536,14 +544,22 @@ class ActionNotifier extends Notifier<void> {
|
|||||||
return ActionResult(count: ids.length, success: false, error: 'Expected single asset for applying edits');
|
return ActionResult(count: ids.length, success: false, error: 'Expected single asset for applying edits');
|
||||||
}
|
}
|
||||||
|
|
||||||
final completer = ref.read(websocketProvider.notifier).waitForEvent("AssetEditReadyV1", (dynamic data) {
|
Future<void> editReady;
|
||||||
final eventAsset = SyncAssetV1.fromJson(data["asset"]);
|
if (ref.read(serverInfoProvider).serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)) {
|
||||||
return eventAsset?.id == ids.first;
|
editReady = ref.read(websocketProvider.notifier).waitForEvent("AssetEditReadyV2", (dynamic data) {
|
||||||
}, const Duration(seconds: 10));
|
final eventAsset = SyncAssetV2.fromJson(data["asset"]);
|
||||||
|
return eventAsset?.id == ids.first;
|
||||||
|
}, const Duration(seconds: 10));
|
||||||
|
} else {
|
||||||
|
editReady = ref.read(websocketProvider.notifier).waitForEvent("AssetEditReadyV1", (dynamic data) {
|
||||||
|
final eventAsset = SyncAssetV1.fromJson(data["asset"]);
|
||||||
|
return eventAsset?.id == ids.first;
|
||||||
|
}, const Duration(seconds: 10));
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _service.applyEdits(ids.first, edits);
|
await _service.applyEdits(ids.first, edits);
|
||||||
await completer;
|
await editReady;
|
||||||
return const ActionResult(count: 1, success: true);
|
return const ActionResult(count: 1, success: true);
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
_logger.severe('Failed to apply edits to assets', error, stack);
|
_logger.severe('Failed to apply edits to assets', error, stack);
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/system_config.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
|
|
||||||
final metadataProvider = Provider.autoDispose<MetadataRepository>((_) => MetadataRepository.instance);
|
|
||||||
|
|
||||||
final appConfigProvider = Provider.autoDispose<AppConfig>((ref) {
|
|
||||||
final repo = ref.watch(metadataProvider);
|
|
||||||
final subscription = repo.watchAppConfig().listen((event) => ref.state = event);
|
|
||||||
ref.onDispose(subscription.cancel);
|
|
||||||
return repo.appConfig;
|
|
||||||
});
|
|
||||||
|
|
||||||
final systemConfigProvider = Provider.autoDispose<SystemConfig>((ref) {
|
|
||||||
final repo = ref.watch(metadataProvider);
|
|
||||||
final subscription = repo.watchSystemConfig().listen((event) => ref.state = event);
|
|
||||||
ref.onDispose(subscription.cancel);
|
|
||||||
return repo.systemConfig;
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
|
|
||||||
|
final settingsProvider = Provider.autoDispose<SettingsRepository>((_) => SettingsRepository.instance);
|
||||||
|
|
||||||
|
final appConfigProvider = Provider.autoDispose<AppConfig>((ref) {
|
||||||
|
final repo = ref.watch(settingsProvider);
|
||||||
|
final subscription = repo.watchConfig().listen((event) => ref.state = event);
|
||||||
|
ref.onDispose(subscription.cancel);
|
||||||
|
return repo.appConfig;
|
||||||
|
});
|
||||||
@@ -3,7 +3,7 @@ import 'package:immich_mobile/domain/services/timeline.service.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
|
||||||
final timelineRepositoryProvider = Provider<DriftTimelineRepository>(
|
final timelineRepositoryProvider = Provider<DriftTimelineRepository>(
|
||||||
@@ -29,7 +29,7 @@ final timelineServiceProvider = Provider<TimelineService>(
|
|||||||
final timelineFactoryProvider = Provider<TimelineFactory>(
|
final timelineFactoryProvider = Provider<TimelineFactory>(
|
||||||
(ref) => TimelineFactory(
|
(ref) => TimelineFactory(
|
||||||
timelineRepository: ref.watch(timelineRepositoryProvider),
|
timelineRepository: ref.watch(timelineRepositoryProvider),
|
||||||
metadataRepository: ref.watch(metadataProvider),
|
settingsRepository: ref.watch(settingsProvider),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
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/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/models/map/map_state.model.dart';
|
import 'package:immich_mobile/models/map/map_state.model.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
|
|
||||||
final mapStateNotifierProvider = NotifierProvider<MapStateNotifier, MapState>(MapStateNotifier.new);
|
final mapStateNotifierProvider = NotifierProvider<MapStateNotifier, MapState>(MapStateNotifier.new);
|
||||||
@@ -27,12 +26,12 @@ class MapStateNotifier extends Notifier<MapState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void switchTheme(ThemeMode mode) {
|
void switchTheme(ThemeMode mode) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapThemeMode, mode);
|
ref.read(settingsProvider).write(.mapThemeMode, mode);
|
||||||
state = state.copyWith(themeMode: mode);
|
state = state.copyWith(themeMode: mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchFavoriteOnly(bool isFavoriteOnly) {
|
void switchFavoriteOnly(bool isFavoriteOnly) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapShowFavoriteOnly, isFavoriteOnly);
|
ref.read(settingsProvider).write(.mapShowFavoriteOnly, isFavoriteOnly);
|
||||||
state = state.copyWith(showFavoriteOnly: isFavoriteOnly, shouldRefetchMarkers: true);
|
state = state.copyWith(showFavoriteOnly: isFavoriteOnly, shouldRefetchMarkers: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,17 +40,17 @@ class MapStateNotifier extends Notifier<MapState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void switchIncludeArchived(bool isIncludeArchived) {
|
void switchIncludeArchived(bool isIncludeArchived) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapIncludeArchived, isIncludeArchived);
|
ref.read(settingsProvider).write(.mapIncludeArchived, isIncludeArchived);
|
||||||
state = state.copyWith(includeArchived: isIncludeArchived, shouldRefetchMarkers: true);
|
state = state.copyWith(includeArchived: isIncludeArchived, shouldRefetchMarkers: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchWithPartners(bool isWithPartners) {
|
void switchWithPartners(bool isWithPartners) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapWithPartners, isWithPartners);
|
ref.read(settingsProvider).write(.mapWithPartners, isWithPartners);
|
||||||
state = state.copyWith(withPartners: isWithPartners, shouldRefetchMarkers: true);
|
state = state.copyWith(withPartners: isWithPartners, shouldRefetchMarkers: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setRelativeTime(int relativeTime) {
|
void setRelativeTime(int relativeTime) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapRelativeDate, relativeTime);
|
ref.read(settingsProvider).write(.mapRelativeDate, relativeTime);
|
||||||
state = state.copyWith(relativeTime: relativeTime, shouldRefetchMarkers: true);
|
state = state.copyWith(relativeTime: relativeTime, shouldRefetchMarkers: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/theme/color_scheme.dart';
|
import 'package:immich_mobile/theme/color_scheme.dart';
|
||||||
import 'package:immich_mobile/theme/dynamic_theme.dart';
|
import 'package:immich_mobile/theme/dynamic_theme.dart';
|
||||||
import 'package:immich_mobile/theme/theme_data.dart';
|
import 'package:immich_mobile/theme/theme_data.dart';
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import 'package:immich_mobile/infrastructure/repositories/network.repository.dar
|
|||||||
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/utils/debounce.dart';
|
import 'package:immich_mobile/utils/debounce.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
@@ -193,7 +193,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final isSyncAlbumEnabled = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
final isSyncAlbumEnabled = _ref.read(appConfigProvider).backup.syncAlbums;
|
||||||
try {
|
try {
|
||||||
unawaited(
|
unawaited(
|
||||||
_ref.read(backgroundSyncProvider).syncWebsocketBatchV1(_batchedAssetUploadReady.toList()).then((_) {
|
_ref.read(backgroundSyncProvider).syncWebsocketBatchV1(_batchedAssetUploadReady.toList()).then((_) {
|
||||||
@@ -214,7 +214,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final isSyncAlbumEnabled = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
final isSyncAlbumEnabled = _ref.read(appConfigProvider).backup.syncAlbums;
|
||||||
try {
|
try {
|
||||||
unawaited(
|
unawaited(
|
||||||
_ref.read(backgroundSyncProvider).syncWebsocketBatchV2(_batchedAssetUploadReady.toList()).then((_) {
|
_ref.read(backgroundSyncProvider).syncWebsocketBatchV2(_batchedAssetUploadReady.toList()).then((_) {
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/response_extensions.dart';
|
|
||||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
final assetMediaRepositoryProvider = Provider(
|
final assetMediaRepositoryProvider = Provider((ref) => AssetMediaRepository(ref.watch(nativeSyncApiProvider)));
|
||||||
(ref) => AssetMediaRepository(ref.watch(assetApiRepositoryProvider), ref.watch(nativeSyncApiProvider)),
|
|
||||||
);
|
|
||||||
|
|
||||||
class AssetMediaRepository {
|
class AssetMediaRepository {
|
||||||
final AssetApiRepository _assetApiRepository;
|
|
||||||
final NativeSyncApi _nativeSyncApi;
|
final NativeSyncApi _nativeSyncApi;
|
||||||
static final Logger _log = Logger("AssetMediaRepository");
|
static final Logger _log = Logger("AssetMediaRepository");
|
||||||
|
|
||||||
const AssetMediaRepository(this._assetApiRepository, this._nativeSyncApi);
|
const AssetMediaRepository(this._nativeSyncApi);
|
||||||
|
|
||||||
Future<bool> _androidSupportsTrash() async {
|
Future<bool> _androidSupportsTrash() async {
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
@@ -107,10 +105,29 @@ class AssetMediaRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make this more efficient
|
Future<int> shareAssets(
|
||||||
Future<int> shareAssets(List<BaseAsset> assets, BuildContext context, {Completer<void>? cancelCompleter}) async {
|
List<BaseAsset> assets,
|
||||||
|
BuildContext context, {
|
||||||
|
Completer<void>? cancelCompleter,
|
||||||
|
void Function(double progress)? onAssetDownloadProgress,
|
||||||
|
}) async {
|
||||||
final downloadedXFiles = <XFile>[];
|
final downloadedXFiles = <XFile>[];
|
||||||
final tempFiles = <File>[];
|
final tempFiles = <File>[];
|
||||||
|
final totalAssets = assets.length;
|
||||||
|
var processedAssets = 0;
|
||||||
|
|
||||||
|
void updateProgress([double currentAssetProgress = 0.0]) {
|
||||||
|
if (totalAssets <= 0) {
|
||||||
|
onAssetDownloadProgress?.call(1.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final normalizedAssetProgress = currentAssetProgress.clamp(0.0, 1.0);
|
||||||
|
final overallProgress = ((processedAssets + normalizedAssetProgress) / totalAssets).clamp(0.0, 1.0);
|
||||||
|
onAssetDownloadProgress?.call(overallProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress();
|
||||||
|
|
||||||
for (var asset in assets) {
|
for (var asset in assets) {
|
||||||
if (cancelCompleter != null && cancelCompleter.isCompleted) {
|
if (cancelCompleter != null && cancelCompleter.isCompleted) {
|
||||||
@@ -127,6 +144,8 @@ class AssetMediaRepository {
|
|||||||
if (localId != null && !asset.isEdited) {
|
if (localId != null && !asset.isEdited) {
|
||||||
File? f = await AssetEntity(id: localId, width: 1, height: 1, typeInt: 0).originFile;
|
File? f = await AssetEntity(id: localId, width: 1, height: 1, typeInt: 0).originFile;
|
||||||
downloadedXFiles.add(XFile(f!.path));
|
downloadedXFiles.add(XFile(f!.path));
|
||||||
|
processedAssets++;
|
||||||
|
updateProgress();
|
||||||
if (CurrentPlatform.isIOS) {
|
if (CurrentPlatform.isIOS) {
|
||||||
tempFiles.add(f);
|
tempFiles.add(f);
|
||||||
}
|
}
|
||||||
@@ -134,22 +153,50 @@ class AssetMediaRepository {
|
|||||||
final remoteId = (asset is RemoteAsset) ? asset.id : asset.remoteId;
|
final remoteId = (asset is RemoteAsset) ? asset.id : asset.remoteId;
|
||||||
if (remoteId == null) {
|
if (remoteId == null) {
|
||||||
_log.warning("Asset has no remote ID for sharing: $asset");
|
_log.warning("Asset has no remote ID for sharing: $asset");
|
||||||
|
processedAssets++;
|
||||||
|
updateProgress();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final tempDir = await getTemporaryDirectory();
|
final taskId = 'share-$remoteId-${DateTime.now().microsecondsSinceEpoch}';
|
||||||
final name = asset.name;
|
final sanitizedFilename = asset.name.replaceAll(RegExp(r'[\\/]'), '_');
|
||||||
final tempFile = await File('${tempDir.path}/$name').create();
|
final task = DownloadTask(
|
||||||
final res = await _assetApiRepository.downloadAsset(remoteId, edited: true);
|
taskId: taskId,
|
||||||
|
url: getOriginalUrlForRemoteId(remoteId, edited: asset.isEdited),
|
||||||
|
headers: ApiService.getRequestHeaders(),
|
||||||
|
filename: sanitizedFilename,
|
||||||
|
baseDirectory: BaseDirectory.temporary,
|
||||||
|
group: kShareDownloadGroup,
|
||||||
|
updates: Updates.statusAndProgress,
|
||||||
|
);
|
||||||
|
final statusUpdate = await FileDownloader().download(
|
||||||
|
task,
|
||||||
|
onProgress: (value) {
|
||||||
|
if (cancelCompleter != null && cancelCompleter.isCompleted) {
|
||||||
|
unawaited(FileDownloader().cancelTaskWithId(taskId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateProgress(value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (res.statusCode != 200) {
|
if (cancelCompleter != null && cancelCompleter.isCompleted) {
|
||||||
_log.severe("Download for $name failed", res.toLoggerString());
|
await _cleanupTempFiles(tempFiles);
|
||||||
continue;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
await tempFile.writeAsBytes(res.bodyBytes);
|
if (statusUpdate.status == TaskStatus.complete) {
|
||||||
downloadedXFiles.add(XFile(tempFile.path));
|
final filePath = await task.filePath();
|
||||||
tempFiles.add(tempFile);
|
final file = File(filePath);
|
||||||
|
tempFiles.add(file);
|
||||||
|
downloadedXFiles.add(XFile(filePath));
|
||||||
|
processedAssets++;
|
||||||
|
updateProgress();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_log.severe("Download for ${asset.name} failed with status ${statusUpdate.status}", statusUpdate.exception);
|
||||||
|
processedAssets++;
|
||||||
|
updateProgress();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +1,38 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
||||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
|
|
||||||
final authRepositoryProvider = Provider<AuthRepository>(
|
final authRepositoryProvider = Provider<AuthRepository>(
|
||||||
(ref) => AuthRepository(ref.watch(driftProvider), ref.watch(metadataProvider)),
|
(ref) => AuthRepository(ref.watch(driftProvider), ref.watch(appConfigProvider)),
|
||||||
);
|
);
|
||||||
|
|
||||||
class AuthRepository {
|
class AuthRepository {
|
||||||
final Drift _drift;
|
final Drift _drift;
|
||||||
final MetadataRepository _metadata;
|
final AppConfig _config;
|
||||||
|
|
||||||
const AuthRepository(this._drift, this._metadata);
|
const AuthRepository(this._drift, this._config);
|
||||||
|
|
||||||
Future<void> clearLocalData() async {
|
Future<void> clearLocalData() async {
|
||||||
await SyncStreamRepository(_drift).reset();
|
await SyncStreamRepository(_drift).reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getEndpointSwitchingFeature() {
|
bool getEndpointSwitchingFeature() {
|
||||||
return _metadata.systemConfig.network.autoEndpointSwitching;
|
return _config.network.autoEndpointSwitching;
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getPreferredWifiName() {
|
String? getPreferredWifiName() {
|
||||||
return _metadata.systemConfig.network.preferredWifiName;
|
return _config.network.preferredWifiName;
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getLocalEndpoint() {
|
String? getLocalEndpoint() {
|
||||||
return _metadata.systemConfig.network.localEndpoint;
|
return _config.network.localEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<AuxilaryEndpoint> getExternalEndpointList() {
|
List<AuxilaryEndpoint> getExternalEndpointList() {
|
||||||
return _metadata.systemConfig.network.externalEndpointList
|
return _config.network.externalEndpointList.map((url) => AuxilaryEndpoint(url: url, status: .valid)).toList();
|
||||||
.map((url) => AuxilaryEndpoint(url: url, status: .valid))
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -269,8 +269,18 @@ class ActionService {
|
|||||||
await _assetApiRepository.unStack(stackIds);
|
await _assetApiRepository.unStack(stackIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> shareAssets(List<BaseAsset> assets, BuildContext context, {Completer<void>? cancelCompleter}) {
|
Future<int> shareAssets(
|
||||||
return _assetMediaRepository.shareAssets(assets, context, cancelCompleter: cancelCompleter);
|
List<BaseAsset> assets,
|
||||||
|
BuildContext context, {
|
||||||
|
Completer<void>? cancelCompleter,
|
||||||
|
void Function(double progress)? onAssetDownloadProgress,
|
||||||
|
}) {
|
||||||
|
return _assetMediaRepository.shareAssets(
|
||||||
|
assets,
|
||||||
|
context,
|
||||||
|
cancelCompleter: cancelCompleter,
|
||||||
|
onAssetDownloadProgress: onAssetDownloadProgress,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<bool>> downloadAll(List<RemoteAsset> assets) {
|
Future<List<bool>> downloadAll(List<RemoteAsset> assets) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'dart:io';
|
|||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:immich_mobile/utils/url_helper.dart';
|
import 'package:immich_mobile/utils/url_helper.dart';
|
||||||
@@ -13,7 +13,7 @@ import 'package:logging/logging.dart';
|
|||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
late ApiClient _apiClient;
|
final ApiClient _apiClient = ApiClient(basePath: '');
|
||||||
|
|
||||||
late UsersApi usersApi;
|
late UsersApi usersApi;
|
||||||
late AuthenticationApi authenticationApi;
|
late AuthenticationApi authenticationApi;
|
||||||
@@ -54,7 +54,7 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setEndpoint(String endpoint) {
|
setEndpoint(String endpoint) {
|
||||||
_apiClient = ApiClient(basePath: endpoint);
|
_apiClient.basePath = endpoint;
|
||||||
_apiClient.client = NetworkRepository.client;
|
_apiClient.client = NetworkRepository.client;
|
||||||
usersApi = UsersApi(_apiClient);
|
usersApi = UsersApi(_apiClient);
|
||||||
authenticationApi = AuthenticationApi(_apiClient);
|
authenticationApi = AuthenticationApi(_apiClient);
|
||||||
@@ -177,9 +177,9 @@ class ApiService {
|
|||||||
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
||||||
urls.add(serverEndpoint);
|
urls.add(serverEndpoint);
|
||||||
}
|
}
|
||||||
final network = MetadataRepository.instance.systemConfig.network;
|
final network = SettingsRepository.instance.appConfig.network;
|
||||||
final localEndpoint = network.localEndpoint;
|
final localEndpoint = network.localEndpoint;
|
||||||
if (localEndpoint != null) {
|
if (localEndpoint.isNotEmpty) {
|
||||||
urls.add(localEndpoint);
|
urls.add(localEndpoint);
|
||||||
}
|
}
|
||||||
for (final url in network.externalEndpointList) {
|
for (final url in network.externalEndpointList) {
|
||||||
@@ -191,7 +191,7 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Map<String, String> getRequestHeaders() {
|
static Map<String, String> getRequestHeaders() {
|
||||||
return MetadataRepository.instance.systemConfig.network.customHeaders;
|
return SettingsRepository.instance.appConfig.network.customHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiClient get apiClient => _apiClient;
|
ApiClient get apiClient => _apiClient;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
import 'package:immich_mobile/domain/models/settings_key.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||||
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
||||||
@@ -100,7 +100,7 @@ class AuthService {
|
|||||||
_log.severe("Error clearing local data", error, stackTrace);
|
_log.severe("Error clearing local data", error, stackTrace);
|
||||||
});
|
});
|
||||||
|
|
||||||
await MetadataRepository.instance.write(MetadataKey.backupEnabled, false);
|
await SettingsRepository.instance.write(SettingsKey.backupEnabled, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ class AuthService {
|
|||||||
/// - Authentication repository data
|
/// - Authentication repository data
|
||||||
/// - Current user information
|
/// - Current user information
|
||||||
/// - Access token
|
/// - Access token
|
||||||
/// - Asset ETag
|
/// - Server-specific endpoint configuration
|
||||||
///
|
///
|
||||||
/// All deletions are executed in parallel using [Future.wait].
|
/// All deletions are executed in parallel using [Future.wait].
|
||||||
Future<void> clearLocalData() async {
|
Future<void> clearLocalData() async {
|
||||||
@@ -120,6 +120,12 @@ class AuthService {
|
|||||||
_authRepository.clearLocalData(),
|
_authRepository.clearLocalData(),
|
||||||
Store.delete(StoreKey.currentUser),
|
Store.delete(StoreKey.currentUser),
|
||||||
Store.delete(StoreKey.accessToken),
|
Store.delete(StoreKey.accessToken),
|
||||||
|
SettingsRepository.instance.clear(const [
|
||||||
|
.networkAutoEndpointSwitching,
|
||||||
|
.networkPreferredWifiName,
|
||||||
|
.networkLocalEndpoint,
|
||||||
|
.networkExternalEndpointList,
|
||||||
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
|||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
||||||
@@ -359,7 +359,7 @@ class BackgroundUploadService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldRequireWiFi(LocalAsset asset) {
|
bool _shouldRequireWiFi(LocalAsset asset) {
|
||||||
final backup = MetadataRepository.instance.appConfig.backup;
|
final backup = SettingsRepository.instance.appConfig.backup;
|
||||||
if (asset.isVideo && backup.useCellularForVideos) {
|
if (asset.isVideo && backup.useCellularForVideos) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import 'package:immich_mobile/extensions/network_capability_extensions.dart';
|
|||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
import 'package:immich_mobile/platform/connectivity_api.g.dart';
|
import 'package:immich_mobile/platform/connectivity_api.g.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
@@ -451,7 +451,7 @@ class ForegroundUploadService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldRequireWiFi(LocalAsset asset) {
|
bool _shouldRequireWiFi(LocalAsset asset) {
|
||||||
final backup = MetadataRepository.instance.appConfig.backup;
|
final backup = SettingsRepository.instance.appConfig.backup;
|
||||||
if (asset.isVideo && backup.useCellularForVideos) {
|
if (asset.isVideo && backup.useCellularForVideos) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:immich_mobile/extensions/translate_extensions.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
@@ -49,11 +49,11 @@ abstract final class Bootstrap {
|
|||||||
|
|
||||||
await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates);
|
await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates);
|
||||||
|
|
||||||
final metadataRepo = await MetadataRepository.ensureInitialized(drift);
|
final settingsRepo = await SettingsRepository.ensureInitialized(drift);
|
||||||
|
|
||||||
await LogService.init(
|
await LogService.init(
|
||||||
logRepository: LogRepository(logDb),
|
logRepository: LogRepository(logDb),
|
||||||
metadataRepository: metadataRepo,
|
settingsRepository: settingsRepo,
|
||||||
shouldBuffer: shouldBufferLogs,
|
shouldBuffer: shouldBufferLogs,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user