Compare commits

...

90 Commits

Author SHA1 Message Date
bwees
97329d488b chore: code review changes from @shenlong-tanwen 2025-06-24 09:18:11 -05:00
bwees
c6cd4dce78 fix: include port in baseURL 2025-06-24 09:17:42 -05:00
bwees
0a90f490f2 cleanly migrate the server URL value to a JSON array (ie coming from 1.135.0 app) 2025-06-23 17:55:41 -05:00
bwees
88e008c4c2 fix: remove duplicate endpoints 2025-06-23 17:44:51 -05:00
bwees
99a9914da2 feat: ios widget supports alternate server URLs 2025-06-23 17:41:55 -05:00
Alex
1923f1a887 refactor(mobile): interfaces refactor (#19415)
* refactor(mobile): interfaces refactor

* generate files
2025-06-23 11:27:44 -05:00
Daimolean
ce14324c97 fix(web): oauth quota display (#19417)
* fix(web): oauth quota display

* fix(web): oauth quota display
2025-06-23 11:00:41 -04:00
Daimolean
6a309129b7 fix(web): timeline timezone (#19418) 2025-06-23 09:06:26 -05:00
Matthew Momjian
bcb1bf4692 fix(docs): portainer absolute paths (#19459)
absolute paths
2025-06-23 08:54:26 -05:00
Daimolean
7f89999abe fix(web): toggle favorite (#19453) 2025-06-23 08:36:30 -05:00
Daimolean
813186e618 fix(web): undefined release (#19455)
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2025-06-23 12:13:08 +00:00
fmis13
20d9204ada feat(mobile): enable Croatian (hr) translations (#19428) 2025-06-23 12:18:04 +01:00
Min Idzelis
3a9e79a452 chore: optimize playwright gha (#19435) 2025-06-23 12:11:52 +01:00
Mert
03966146fe fix(server): filter parameters by database and role (#19392) 2025-06-23 12:10:00 +01:00
Daniel Dietzler
ecc58a8971 chore: migrate version announcement modal (#19381) 2025-06-22 21:56:41 -05:00
Yaros
c705a7b280 fix(web): map broken after redirect from details (#19424)
* fix(web): map broken after redirect from details

* chore: use globalThis instead of window
2025-06-22 21:55:21 -05:00
Brandon Wees
ef278b4fb0 fix: storage template onboarding save (#19405)
* fix: storage template onboarding save

* no need for async/await
2025-06-22 21:54:29 -05:00
Daimolean
4cd633dc68 fix(web): download icon color (#19427) 2025-06-22 21:52:19 -05:00
Ben McCann
a18c6fa910 chore: fix undeclared dependencies (#19440)
* chore: fix undeclared dependencies

* Add express/multer

---------

Co-authored-by: Min Idzelis <min123@gmail.com>
2025-06-22 19:01:30 -04:00
Daimolean
90aa0dc14d fix(web): map cluster (#19433) 2025-06-22 15:06:45 +00:00
Alex
ce8c80dad0 refactor(mobile): cast_destination.interface.dart (#19346) 2025-06-21 21:56:49 -05:00
Alex
81eb98d4e5 refactor(mobile): album.interface.dart (#19354) 2025-06-21 23:32:06 +00:00
Alex
2b03802e9c refactor(mobile): asset_api.interface.dart (#19353) 2025-06-21 23:06:38 +00:00
Alex
484311e9bb refactor(mobile): asset-media.interface.dart (#19352)
* refactor(mobile): asset-media.interface.dart

* refactor(mobile): asset-media.interface.dart

* refactor: asset media repo

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-06-21 22:47:16 +00:00
Alex
366539bc4c refactor(mobile): asset.interface.dart (#19351)
* refactor(mobile): asset.interface.dart

* refactor(mobile): asset.interface.dart
2025-06-21 22:41:02 +00:00
Alex
69b1331026 refactor(mobile): auth_api.interface.dart (#19350) 2025-06-21 22:20:58 +00:00
Alex
af30d97668 refactor(mobile): auth.interface.dart (#19349) 2025-06-21 22:09:12 +00:00
Alex
9b047d30e4 refactor(mobile): backup_album.interface.dart (#19348) 2025-06-21 21:58:02 +00:00
Alex
6a5597b36b refactor(mobile): etag.interface.dart (#19344)
* refactor(mobile): etag.interface.dart

* merge main
2025-06-21 21:46:38 +00:00
Alex
c10b795e99 refactor(mobile): file_media.interface.dart (#19343)
* refactor(mobile): file_media.interface.dart

* merge main
2025-06-21 19:24:59 +00:00
Alex
b606d4fe73 refactor(mobile): local_file_manager.interface.dart (#19340) 2025-06-21 18:35:30 +00:00
Alex
4c2ad44303 refactor(mobile): folder_api.interface.dart (#19342) 2025-06-21 13:14:14 -05:00
Daniel Dietzler
698d3004b4 refactor: tag create/update modal (#19389)
refactor: tag modals
2025-06-21 12:28:21 +00:00
Daniel Dietzler
fe4d6edbdc refactor: album picker modal (#19383)
Co-authored-by: Jason Rasmussen <jason@rasm.me>
2025-06-21 08:18:54 -04:00
Daimolean
798debfde3 fix(server): duplicate column storage (#19385) 2025-06-20 15:52:25 -04:00
github-actions
6563fa608a chore: version v1.135.3 2025-06-20 19:48:18 +00:00
Jason Rasmussen
1a90fc8e58 feat: test for non-standard database name (#19386) 2025-06-20 19:31:16 +00:00
Alex
c707f9cef4 refactor(mobile): partner.interface.dart (#19338) 2025-06-20 18:37:59 +00:00
dotlambda
6fda863c08 fix(server): don't hardcode database name in migration (#19376) 2025-06-20 21:33:34 +03:00
Daniel Dietzler
373b654156 chore: migrate profile picture cropper modal (#19378) 2025-06-20 18:16:10 +00:00
Daniel Dietzler
a5d84ba552 chore: consistent modal footer spacing (#19377) 2025-06-20 18:05:39 +00:00
Daniel Dietzler
1dc8fa2979 chore: rename edit album form modal (#19375) 2025-06-20 13:51:14 -04:00
Alex
0426699f13 refactor(mobile): partner_api.interface.dart (#19337)
* refactor(mobile): partner_api.interface.dart

* merge main
2025-06-20 17:04:15 +00:00
Alex
8154ec29df refactor(mobile): person_api.interface.dart (#19336) 2025-06-20 11:45:31 -05:00
Alex
3024cd343b refactor(mobile): timeline.interface.dart (#19331)
refactor(mobile): timeline.repository.dart
2025-06-20 11:44:02 -05:00
Zack Pollard
0b44d4b6f2 fix: partner and album backfill acks (#19371)
fix: partner sync being entirely broken
2025-06-20 16:14:36 +00:00
github-actions
a04c6ed80d chore: version v1.135.2 2025-06-20 14:52:47 +00:00
Brandon Wees
1c50e19894 fix: use icons instead of toggles for admin user features view (#19369)
* fix: use icons instead of toggles for admin user features view

* fix: use red for X icon
2025-06-20 14:48:18 +00:00
Alex
e61d7f2616 refactor(mobile): album_media.interface.dart (#19355)
* refactor(mobile): album_media.interface.dart

* refactor: album_media repo

* make dcm happy

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-06-20 14:23:43 +00:00
Alex
a6b0869714 refactor(mobile): secure_storage.interface.dart (#19335)
* refactor(mobile): secure_storage.interface.dart

* fix: lint
2025-06-20 09:12:07 -05:00
Alex
9c25b8ba7d refactor(mobile): download.interface.dart (#19345) 2025-06-20 09:11:52 -05:00
Alex
3c72f489d8 refactor(mobile): sessions_api.interface.dart (#19334)
refactor(mobile): sessions_api.repository.dart
2025-06-20 09:11:33 -05:00
Alex
1f2c779b36 refactor(mobile): shared_handler.interface.dart (#19333)
refactor(mobile): shared_handler.repository.dart
2025-06-20 09:11:21 -05:00
Alex
5c74f634b7 refactor(mobile): network.interface.dart (#19341)
* refactor(mobile): network.interface.dart

* refactor(mobile): network.interface.dart
2025-06-20 09:11:03 -05:00
Alex
ecc99bfd16 refactor(mobile): biometric.interface.dart (#19347) 2025-06-20 09:10:53 -05:00
Alex
ff4d70e351 refactor(mobile): album_api.interface.dart (#19356) 2025-06-20 09:08:23 -05:00
Alex
42c2389eb5 refactor(mobile): activity_api.interface.dart (#19357) 2025-06-20 09:08:12 -05:00
Jason Rasmussen
33c9f88ba4 fix: time bucket grouping (#19329) 2025-06-20 09:46:30 -04:00
Mert
11c469907f fix(server): migration failing on pg15+ (#19363)
* reset params

* unused parameter
2025-06-20 08:36:07 -05:00
Mert
7c43e6c3c8 fix: bump vchord default to 0.4.3 (#19365)
bump default to 0.4.3
2025-06-20 08:35:32 -05:00
Zack Pollard
00aa385972 fix: people ordering by asset count (#19366) 2025-06-20 07:34:04 -05:00
Min Idzelis
a5ed453929 chore: unused deps (#19256)
remove joi
2025-06-20 00:30:23 -04:00
Jason Rasmussen
dd8969cb7d fix: container padding (#19316) 2025-06-19 21:33:12 -05:00
Alex
bce4f93c90 refactor(mobile): (1) user.interface.dart (#19322)
* refactor(mobile): user.interface.dart

* generate files

* refactor(mobile): (2) user_api.interface.dart (#19323)

* refactor(mobile): (2) user_api.interface.dart

* generate files

* refactor(mobile): (3) sync_stream.interface.dart (#19325)
2025-06-19 23:25:18 +00:00
Alex
a4c0dc5007 chore: post release tasks (#19311) 2025-06-19 15:35:25 -04:00
Matthew Momjian
d233a7d97a fix(server): remove excessive inactivity log (#19306) 2025-06-19 19:13:13 +00:00
Jason Rasmussen
5cdbb65d28 feat: better contrast for checkmark indicator (#19312)
feat: better constrast
2025-06-19 13:20:57 -05:00
github-actions
3434544864 chore: version v1.135.1 2025-06-19 17:37:39 +00:00
Brandon Wees
269bf4b344 fix: iOS 17.0 target version for widget (#19308) 2025-06-19 17:00:54 +00:00
Zack Pollard
f9435a538b revert: fix(web): wrap long names with textarea (#19305)
Revert "fix(web): wrap long names with textarea (#19301)"

This reverts commit 747a72120e.
2025-06-19 16:28:10 +00:00
Alex
10e2ec2841 chore: skip truncating table in this release (#19300) 2025-06-19 16:11:18 +00:00
Zack Pollard
fe91b44ab9 fix: people ordering incorrect (#19298) 2025-06-19 16:05:03 +00:00
Jin Xuan
747a72120e fix(web): wrap long names with textarea (#19301) 2025-06-19 15:57:54 +00:00
Jason Rasmussen
910661e75c chore: remove unused mocks (#19299) 2025-06-19 10:35:09 -05:00
Alex
c8a135a7ae fix: .find() iterator api combat (#19293)
* fix: .find() iterator api combar

* Update web/src/lib/managers/timeline-manager/month-group.svelte.ts

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>

---------

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2025-06-19 14:59:14 +00:00
xCJPECKOVERx
08d1cf5bde fix(web): Stack assets in asset-viewer cut off on the left (#19253)
* - move overflow and scrollbar to stack-slideshow inner div

* - format
2025-06-19 09:20:25 -05:00
Alex
3e62497fd0 fix: local network permission (#19285) 2025-06-19 14:18:51 +00:00
Alex
a1bc862a32 chore: post release tasks (#19249) 2025-06-19 09:11:24 -05:00
Alex
75bf3aa1be chore: correct vchord version in docker-compose.yml (#19251) 2025-06-19 09:11:15 -05:00
Dag Stuan
38e68d16f9 fix(web): exit slideshow when exiting fullscreen. (#19247)
Exit slideshow when exiting fullscreen.

Browsers do not send a keyboard event when exiting fullscreen, so if
the user exits fullscreen with the escape key, the slideshow
remains open, requiring another escape key press to close it. Fix this
by listening for the fullscreenchange event and closing the slideshow
when exiting fullscreen.
2025-06-19 14:10:10 +00:00
Daniel Dietzler
caf11fbb96 fix: album asset viewer (#19252) 2025-06-19 09:09:23 -05:00
Mert
f99c6feac5 fix(server): unset prewarm dim parameter (#19271)
unset prewarm dim
2025-06-19 09:04:52 -05:00
Matthew Momjian
5122512f19 fix(docs): REINDEX vchord on upgrade (#19282)
* reindex

* lint

* collapse migrations

* remove title

* reformat
2025-06-19 09:04:18 -05:00
Mert
49ed212af8 fix(server): drop vector indices before updating extension (#19283)
drop indices before updating
2025-06-19 09:03:40 -05:00
Paul Larsen
e29103b69f fix album list CSS margins (#19262) 2025-06-19 14:03:14 +00:00
Min Idzelis
14b771d7c7 fix: devcontainer in codespaces (#19259)
* fix: devcontainer perms

* Fix host based auth

* use path tricks to get to volume mount, but remain compat with current meaning of variables

* eureka, i think

* bit of cleanup
2025-06-19 08:29:22 -05:00
Daniel Dietzler
07aa51638c fix: panning interrupted while moving around the map (#19276) 2025-06-19 11:28:53 +00:00
SGT
0a9a520ed2 feat(server): sql-tools support for class level composite fk (#19242)
* feat: support for class level composite fk

* chore: clean up

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2025-06-18 14:30:39 -04:00
Daniel Dietzler
de81006367 fix: album share modal navigation (#19245) 2025-06-18 16:10:35 +00:00
Jason Rasmussen
e0144b4ece feat: backfill album users (#19234) 2025-06-18 10:48:11 -04:00
289 changed files with 4525 additions and 3703 deletions

View File

@@ -55,7 +55,7 @@
"userEnvProbe": "loginInteractiveShell", "userEnvProbe": "loginInteractiveShell",
"remoteEnv": { "remoteEnv": {
// The location where your uploaded files are stored // The location where your uploaded files are stored
"UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:upload-devcontainer-volume}", "UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./library}",
// Connection secret for postgres. You should change it to a random password // Connection secret for postgres. You should change it to a random password
// Please use only the characters `A-Za-z0-9`, without special characters or spaces // Please use only the characters `A-Za-z0-9`, without special characters or spaces
"DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}", "DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}",

View File

@@ -51,14 +51,19 @@ fix_permissions() {
run_cmd sudo find "${IMMICH_WORKSPACE}/server/upload" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres/*" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres" -exec chown node {} + run_cmd sudo find "${IMMICH_WORKSPACE}/server/upload" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres/*" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres" -exec chown node {} +
run_cmd sudo chown node -R "${IMMICH_WORKSPACE}/.vscode" \ # Change ownership for directories that exist
for dir in "${IMMICH_WORKSPACE}/.vscode" \
"${IMMICH_WORKSPACE}/cli/node_modules" \ "${IMMICH_WORKSPACE}/cli/node_modules" \
"${IMMICH_WORKSPACE}/e2e/node_modules" \ "${IMMICH_WORKSPACE}/e2e/node_modules" \
"${IMMICH_WORKSPACE}/open-api/typescript-sdk/node_modules" \ "${IMMICH_WORKSPACE}/open-api/typescript-sdk/node_modules" \
"${IMMICH_WORKSPACE}/server/node_modules" \ "${IMMICH_WORKSPACE}/server/node_modules" \
"${IMMICH_WORKSPACE}/server/dist" \ "${IMMICH_WORKSPACE}/server/dist" \
"${IMMICH_WORKSPACE}/web/node_modules" \ "${IMMICH_WORKSPACE}/web/node_modules" \
"${IMMICH_WORKSPACE}/web/dist" "${IMMICH_WORKSPACE}/web/dist"; do
if [ -d "$dir" ]; then
run_cmd sudo chown node -R "$dir"
fi
done
log "" log ""
} }

View File

@@ -12,8 +12,8 @@ services:
- open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules - open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules
- server_node_modules:/workspaces/immich/server/node_modules - server_node_modules:/workspaces/immich/server/node_modules
- web_node_modules:/workspaces/immich/web/node_modules - web_node_modules:/workspaces/immich/web/node_modules
- ${UPLOAD_LOCATION-./Library}/photos:/workspaces/immich/server/upload - ${UPLOAD_LOCATION:-upload1-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/workspaces/immich/server/upload
- ${UPLOAD_LOCATION-./Library}/photos/upload:/workspaces/immich/server/upload/upload - ${UPLOAD_LOCATION:-upload2-devcontainer-volume}${UPLOAD_LOCATION:+/photos/upload}:/workspaces/immich/server/upload/upload
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
immich-web: immich-web:
@@ -29,8 +29,9 @@ services:
POSTGRES_USER: ${DB_USERNAME-postgres} POSTGRES_USER: ${DB_USERNAME-postgres}
POSTGRES_DB: ${DB_DATABASE_NAME-immich} POSTGRES_DB: ${DB_DATABASE_NAME-immich}
POSTGRES_INITDB_ARGS: '--data-checksums' POSTGRES_INITDB_ARGS: '--data-checksums'
volumes: POSTGRES_HOST_AUTH_METHOD: md5
- ${UPLOAD_LOCATION-./Library}/postgres:/var/lib/postgresql/data volumes:
- ${UPLOAD_LOCATION:-postgres-devcontainer-volume}${UPLOAD_LOCATION:+/postgres}:/var/lib/postgresql/data
redis: redis:
env_file: !reset [] env_file: !reset []
@@ -42,4 +43,6 @@ volumes:
open_api_node_modules: open_api_node_modules:
server_node_modules: server_node_modules:
web_node_modules: web_node_modules:
upload-devcontainer-volume: upload1-devcontainer-volume:
upload2-devcontainer-volume:
postgres-devcontainer-volume:

View File

@@ -461,7 +461,7 @@ jobs:
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Install Playwright Browsers - name: Install Playwright Browsers
run: npx playwright install --with-deps chromium run: npx playwright install chromium --only-shell
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Docker build - name: Docker build
@@ -644,7 +644,7 @@ jobs:
contents: read contents: read
services: services:
postgres: postgres:
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3
env: env:
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres POSTGRES_USER: postgres

6
cli/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.69", "version": "2.2.72",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.69", "version": "2.2.72",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
@@ -54,7 +54,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.0", "version": "1.135.3",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.69", "version": "2.2.72",
"description": "Command Line Interface (CLI) for Immich", "description": "Command Line Interface (CLI) for Immich",
"type": "module", "type": "module",
"exports": "./dist/index.js", "exports": "./dist/index.js",

View File

@@ -16,7 +16,7 @@ name: immich-dev
services: services:
immich-server: immich-server:
container_name: immich_server container_name: immich_server
command: [ '/usr/src/app/bin/immich-dev' ] command: ['/usr/src/app/bin/immich-dev']
image: immich-server-dev:latest image: immich-server-dev:latest
# extends: # extends:
# file: hwaccel.transcoding.yml # file: hwaccel.transcoding.yml
@@ -70,7 +70,7 @@ services:
# user: 0:0 # user: 0:0
build: build:
context: ../web context: ../web
command: [ '/usr/src/app/bin/immich-web' ] command: ['/usr/src/app/bin/immich-web']
env_file: env_file:
- .env - .env
ports: ports:
@@ -122,7 +122,7 @@ services:
database: database:
container_name: immich_postgres container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
env_file: env_file:
- .env - .env
environment: environment:

View File

@@ -63,7 +63,7 @@ services:
database: database:
container_name: immich_postgres container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
env_file: env_file:
- .env - .env
environment: environment:
@@ -91,7 +91,7 @@ services:
# add data source for http://immich-prometheus:9090 to get started # add data source for http://immich-prometheus:9090 to get started
immich-grafana: immich-grafana:
container_name: immich_grafana container_name: immich_grafana
command: [ './run.sh', '-disable-reporting' ] command: ['./run.sh', '-disable-reporting']
ports: ports:
- 3000:3000 - 3000:3000
image: grafana/grafana:12.0.1-ubuntu@sha256:65575bb9c761335e2ff30e364f21d38632e3b2e75f5f81d83cc92f44b9bbc055 image: grafana/grafana:12.0.1-ubuntu@sha256:65575bb9c761335e2ff30e364f21d38632e3b2e75f5f81d83cc92f44b9bbc055

View File

@@ -56,7 +56,7 @@ services:
database: database:
container_name: immich_postgres container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
environment: environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME} POSTGRES_USER: ${DB_USERNAME}

View File

@@ -64,7 +64,13 @@ COMMIT;
### Updating VectorChord ### Updating VectorChord
When installing a new version of VectorChord, you will need to manually update the extension by connecting to the Immich database and running `ALTER EXTENSION vchord UPDATE;`. When installing a new version of VectorChord, you will need to manually update the extension and reindex by connecting to the Immich database and running:
```
ALTER EXTENSION vchord UPDATE;
REINDEX INDEX face_index;
REINDEX INDEX clip_index;
```
## Migrating to VectorChord ## Migrating to VectorChord
@@ -76,6 +82,8 @@ Support for pgvecto.rs will be dropped in a later release, hence we recommend al
The easiest option is to have both extensions installed during the migration: The easiest option is to have both extensions installed during the migration:
<details>
<summary>Migration steps (automatic)</summary>
1. Ensure you still have pgvecto.rs installed 1. Ensure you still have pgvecto.rs installed
2. Install `pgvector` (`>= 0.7.0, < 1.0.0`). The easiest way to do this is on Debian/Ubuntu by adding the [PostgreSQL Apt repository][pg-apt] and then running `apt install postgresql-NN-pgvector`, where `NN` is your Postgres version (e.g., `16`) 2. Install `pgvector` (`>= 0.7.0, < 1.0.0`). The easiest way to do this is on Debian/Ubuntu by adding the [PostgreSQL Apt repository][pg-apt] and then running `apt install postgresql-NN-pgvector`, where `NN` is your Postgres version (e.g., `16`)
3. [Install VectorChord][vchord-install] 3. [Install VectorChord][vchord-install]
@@ -89,8 +97,12 @@ The easiest option is to have both extensions installed during the migration:
11. Restart the Postgres database 11. Restart the Postgres database
12. Uninstall pgvecto.rs (e.g. `apt-get purge vectors-pg14` on Debian-based environments, replacing `pg14` as appropriate). `pgvector` must remain installed as it provides the data types used by `vchord` 12. Uninstall pgvecto.rs (e.g. `apt-get purge vectors-pg14` on Debian-based environments, replacing `pg14` as appropriate). `pgvector` must remain installed as it provides the data types used by `vchord`
</details>
If it is not possible to have both VectorChord and pgvecto.rs installed at the same time, you can perform the migration with more manual steps: If it is not possible to have both VectorChord and pgvecto.rs installed at the same time, you can perform the migration with more manual steps:
<details>
<summary>Migration steps (manual)</summary>
1. While pgvecto.rs is still installed, run the following SQL command using psql or your choice of database client. Take note of the number outputted by this command as you will need it later 1. While pgvecto.rs is still installed, run the following SQL command using psql or your choice of database client. Take note of the number outputted by this command as you will need it later
```sql ```sql
@@ -123,14 +135,20 @@ ALTER TABLE face_search ALTER COLUMN embedding SET DATA TYPE vector(512);
5. Start Immich and let it create new indices using VectorChord 5. Start Immich and let it create new indices using VectorChord
</details>
### Migrating from pgvector ### Migrating from pgvector
<details>
<summary>Migration steps</summary>
1. Ensure you have at least 0.7.0 of pgvector installed. If it is below that, please upgrade it and run the SQL command `ALTER EXTENSION vector UPDATE;` using psql or your choice of database client 1. Ensure you have at least 0.7.0 of pgvector installed. If it is below that, please upgrade it and run the SQL command `ALTER EXTENSION vector UPDATE;` using psql or your choice of database client
2. Follow the Prerequisites to install VectorChord 2. Follow the Prerequisites to install VectorChord
3. If Immich does not have superuser permissions, run the SQL command `CREATE EXTENSION vchord CASCADE;` 3. If Immich does not have superuser permissions, run the SQL command `CREATE EXTENSION vchord CASCADE;`
4. Remove the `DB_VECTOR_EXTENSION=pgvector` environmental variable as it will make Immich still use pgvector if set 4. Remove the `DB_VECTOR_EXTENSION=pgvector` environmental variable as it will make Immich still use pgvector if set
5. Start Immich and let it create new indices using VectorChord 5. Start Immich and let it create new indices using VectorChord
</details>
Note that VectorChord itself uses pgvector types, so you should not uninstall pgvector after following these steps. Note that VectorChord itself uses pgvector types, so you should not uninstall pgvector after following these steps.
[vchord-install]: https://docs.vectorchord.ai/vectorchord/getting-started/installation.html [vchord-install]: https://docs.vectorchord.ai/vectorchord/getting-started/installation.html

View File

@@ -39,8 +39,8 @@ alt="Dot Env Example"
/> />
- Change the default `DB_PASSWORD`, and add custom database connection information if necessary. - Change the default `DB_PASSWORD`, and add custom database connection information if necessary.
- Change `DB_DATA_LOCATION` to a folder where the database will be saved to disk. - Change `DB_DATA_LOCATION` to a folder (absolute path) where the database will be saved to disk.
- Change `UPLOAD_LOCATION` to a folder where media (uploaded and generated) will be stored. - Change `UPLOAD_LOCATION` to a folder (absolute path) where media (uploaded and generated) will be stored.
11. Click on "**Deploy the stack**". 11. Click on "**Deploy the stack**".

View File

@@ -1,4 +1,16 @@
[ [
{
"label": "v1.135.3",
"url": "https://v1.135.3.archive.immich.app"
},
{
"label": "v1.135.2",
"url": "https://v1.135.2.archive.immich.app"
},
{
"label": "v1.135.1",
"url": "https://v1.135.1.archive.immich.app"
},
{ {
"label": "v1.135.0", "label": "v1.135.0",
"url": "https://v1.135.0.archive.immich.app" "url": "https://v1.135.0.archive.immich.app"

8
e2e/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.135.0", "version": "1.135.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.135.0", "version": "1.135.3",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
@@ -44,7 +44,7 @@
}, },
"../cli": { "../cli": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.69", "version": "2.2.72",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
@@ -93,7 +93,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.0", "version": "1.135.3",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.135.0", "version": "1.135.3",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",

View File

@@ -14,7 +14,11 @@ describe('/people', () => {
let nameAlicePerson: PersonResponseDto; let nameAlicePerson: PersonResponseDto;
let nameBobPerson: PersonResponseDto; let nameBobPerson: PersonResponseDto;
let nameCharliePerson: PersonResponseDto; let nameCharliePerson: PersonResponseDto;
let nameNullPerson: PersonResponseDto; let nameNullPerson4Assets: PersonResponseDto;
let nameNullPerson3Assets: PersonResponseDto;
let nameNullPerson1Asset: PersonResponseDto;
let nameBillPersonFavourite: PersonResponseDto;
let nameFreddyPersonFavourite: PersonResponseDto;
beforeAll(async () => { beforeAll(async () => {
await utils.resetDatabase(); await utils.resetDatabase();
@@ -27,7 +31,11 @@ describe('/people', () => {
nameCharliePerson, nameCharliePerson,
nameBobPerson, nameBobPerson,
nameAlicePerson, nameAlicePerson,
nameNullPerson, nameNullPerson4Assets,
nameNullPerson3Assets,
nameNullPerson1Asset,
nameBillPersonFavourite,
nameFreddyPersonFavourite,
] = await Promise.all([ ] = await Promise.all([
utils.createPerson(admin.accessToken, { utils.createPerson(admin.accessToken, {
name: 'visible_person', name: 'visible_person',
@@ -52,11 +60,26 @@ describe('/people', () => {
utils.createPerson(admin.accessToken, { utils.createPerson(admin.accessToken, {
name: '', name: '',
}), }),
utils.createPerson(admin.accessToken, {
name: '',
}),
utils.createPerson(admin.accessToken, {
name: '',
}),
utils.createPerson(admin.accessToken, {
name: 'Bill',
isFavorite: true,
}),
utils.createPerson(admin.accessToken, {
name: 'Freddy',
isFavorite: true,
}),
]); ]);
const asset1 = await utils.createAsset(admin.accessToken); const asset1 = await utils.createAsset(admin.accessToken);
const asset2 = await utils.createAsset(admin.accessToken); const asset2 = await utils.createAsset(admin.accessToken);
const asset3 = await utils.createAsset(admin.accessToken); const asset3 = await utils.createAsset(admin.accessToken);
const asset4 = await utils.createAsset(admin.accessToken);
await Promise.all([ await Promise.all([
utils.createFace({ assetId: asset1.id, personId: visiblePerson.id }), utils.createFace({ assetId: asset1.id, personId: visiblePerson.id }),
@@ -64,15 +87,27 @@ describe('/people', () => {
utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }), utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }),
utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }), utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }),
utils.createFace({ assetId: asset2.id, personId: multipleAssetsPerson.id }), utils.createFace({ assetId: asset2.id, personId: multipleAssetsPerson.id }),
utils.createFace({ assetId: asset3.id, personId: multipleAssetsPerson.id }), utils.createFace({ assetId: asset3.id, personId: multipleAssetsPerson.id }), // 4 assets
// Named persons // Named persons
utils.createFace({ assetId: asset1.id, personId: nameCharliePerson.id }), // 1 asset utils.createFace({ assetId: asset1.id, personId: nameCharliePerson.id }), // 1 asset
utils.createFace({ assetId: asset1.id, personId: nameBobPerson.id }), utils.createFace({ assetId: asset1.id, personId: nameBobPerson.id }),
utils.createFace({ assetId: asset2.id, personId: nameBobPerson.id }), // 2 assets utils.createFace({ assetId: asset2.id, personId: nameBobPerson.id }), // 2 assets
utils.createFace({ assetId: asset1.id, personId: nameAlicePerson.id }), // 1 asset utils.createFace({ assetId: asset1.id, personId: nameAlicePerson.id }), // 1 asset
// Null-named person // Null-named person 4 assets
utils.createFace({ assetId: asset1.id, personId: nameNullPerson.id }), utils.createFace({ assetId: asset1.id, personId: nameNullPerson4Assets.id }),
utils.createFace({ assetId: asset2.id, personId: nameNullPerson.id }), // 2 assets utils.createFace({ assetId: asset2.id, personId: nameNullPerson4Assets.id }),
utils.createFace({ assetId: asset3.id, personId: nameNullPerson4Assets.id }),
utils.createFace({ assetId: asset4.id, personId: nameNullPerson4Assets.id }), // 4 assets
// Null-named person 3 assets
utils.createFace({ assetId: asset1.id, personId: nameNullPerson3Assets.id }),
utils.createFace({ assetId: asset2.id, personId: nameNullPerson3Assets.id }),
utils.createFace({ assetId: asset3.id, personId: nameNullPerson3Assets.id }), // 3 assets
// Null-named person 1 asset
utils.createFace({ assetId: asset3.id, personId: nameNullPerson1Asset.id }),
// Favourite People
utils.createFace({ assetId: asset1.id, personId: nameFreddyPersonFavourite.id }),
utils.createFace({ assetId: asset2.id, personId: nameFreddyPersonFavourite.id }),
utils.createFace({ assetId: asset1.id, personId: nameBillPersonFavourite.id }),
]); ]);
}); });
@@ -87,15 +122,19 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({
hasNextPage: false, hasNextPage: false,
total: 7, total: 11,
hidden: 1, hidden: 1,
people: [ people: [
expect.objectContaining({ name: 'Freddy' }),
expect.objectContaining({ name: 'Bill' }),
expect.objectContaining({ name: 'multiple_assets_person' }), expect.objectContaining({ name: 'multiple_assets_person' }),
expect.objectContaining({ name: 'Bob' }), expect.objectContaining({ name: 'Bob' }),
expect.objectContaining({ name: 'Alice' }), expect.objectContaining({ name: 'Alice' }),
expect.objectContaining({ name: 'Charlie' }), expect.objectContaining({ name: 'Charlie' }),
expect.objectContaining({ name: 'visible_person' }), expect.objectContaining({ name: 'visible_person' }),
expect.objectContaining({ name: 'hidden_person' }), expect.objectContaining({ id: nameNullPerson4Assets.id, name: '' }),
expect.objectContaining({ id: nameNullPerson3Assets.id, name: '' }),
expect.objectContaining({ name: 'hidden_person' }), // Should really be before the null names
], ],
}); });
}); });
@@ -105,17 +144,21 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body.hasNextPage).toBe(false); expect(body.hasNextPage).toBe(false);
expect(body.total).toBe(7); // All persons expect(body.total).toBe(11); // All persons
expect(body.hidden).toBe(1); // 'hidden_person' expect(body.hidden).toBe(1); // 'hidden_person'
const people = body.people as PersonResponseDto[]; const people = body.people as PersonResponseDto[];
expect(people.map((p) => p.id)).toEqual([ expect(people.map((p) => p.id)).toEqual([
nameFreddyPersonFavourite.id, // name: 'Freddy', count: 2
nameBillPersonFavourite.id, // name: 'Bill', count: 1
multipleAssetsPerson.id, // name: 'multiple_assets_person', count: 3 multipleAssetsPerson.id, // name: 'multiple_assets_person', count: 3
nameBobPerson.id, // name: 'Bob', count: 2 nameBobPerson.id, // name: 'Bob', count: 2
nameAlicePerson.id, // name: 'Alice', count: 1 nameAlicePerson.id, // name: 'Alice', count: 1
nameCharliePerson.id, // name: 'Charlie', count: 1 nameCharliePerson.id, // name: 'Charlie', count: 1
visiblePerson.id, // name: 'visible_person', count: 1 visiblePerson.id, // name: 'visible_person', count: 1
nameNullPerson4Assets.id, // name: '', count: 4
nameNullPerson3Assets.id, // name: '', count: 3
]); ]);
expect(people.some((p) => p.id === hiddenPerson.id)).toBe(false); expect(people.some((p) => p.id === hiddenPerson.id)).toBe(false);
@@ -127,14 +170,18 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({
hasNextPage: false, hasNextPage: false,
total: 7, total: 11,
hidden: 1, hidden: 1,
people: [ people: [
expect.objectContaining({ name: 'Freddy' }),
expect.objectContaining({ name: 'Bill' }),
expect.objectContaining({ name: 'multiple_assets_person' }), expect.objectContaining({ name: 'multiple_assets_person' }),
expect.objectContaining({ name: 'Bob' }), expect.objectContaining({ name: 'Bob' }),
expect.objectContaining({ name: 'Alice' }), expect.objectContaining({ name: 'Alice' }),
expect.objectContaining({ name: 'Charlie' }), expect.objectContaining({ name: 'Charlie' }),
expect.objectContaining({ name: 'visible_person' }), expect.objectContaining({ name: 'visible_person' }),
expect.objectContaining({ id: nameNullPerson4Assets.id, name: '' }),
expect.objectContaining({ id: nameNullPerson3Assets.id, name: '' }),
], ],
}); });
}); });
@@ -148,9 +195,9 @@ describe('/people', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual({ expect(body).toEqual({
hasNextPage: true, hasNextPage: true,
total: 7, total: 11,
hidden: 1, hidden: 1,
people: [expect.objectContaining({ name: 'visible_person' })], people: [expect.objectContaining({ name: 'Alice' })],
}); });
}); });
}); });

View File

@@ -464,7 +464,6 @@
"assets": "Assets", "assets": "Assets",
"assets_added_count": "Added {count, plural, one {# asset} other {# assets}}", "assets_added_count": "Added {count, plural, one {# asset} other {# assets}}",
"assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album", "assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album",
"assets_added_to_name_count": "Added {count, plural, one {# asset} other {# assets}} to {hasName, select, true {<b>{name}</b>} other {new album}}",
"assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} cannot be added to the album", "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} cannot be added to the album",
"assets_count": "{count, plural, one {# asset} other {# assets}}", "assets_count": "{count, plural, one {# asset} other {# assets}}",
"assets_deleted_permanently": "{count} asset(s) deleted permanently", "assets_deleted_permanently": "{count} asset(s) deleted permanently",

View File

@@ -244,7 +244,6 @@ async def load(model: InferenceModel) -> InferenceModel:
async def idle_shutdown_task() -> None: async def idle_shutdown_task() -> None:
while True: while True:
log.debug("Checking for inactivity...")
if ( if (
last_called is not None last_called is not None
and not active_requests and not active_requests

View File

@@ -35,8 +35,8 @@ platform :android do
task: 'bundle', task: 'bundle',
build_type: 'Release', build_type: 'Release',
properties: { properties: {
"android.injected.version.code" => 201, "android.injected.version.code" => 204,
"android.injected.version.name" => "1.135.0", "android.injected.version.name" => "1.135.3",
} }
) )
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')

View File

@@ -346,6 +346,7 @@
}; };
F0B57D372DF764BD00DC5BCC = { F0B57D372DF764BD00DC5BCC = {
CreatedOnToolsVersion = 16.4; CreatedOnToolsVersion = 16.4;
ProvisioningStyle = Automatic;
}; };
FAC6F88F2D287C890078CB2F = { FAC6F88F2D287C890078CB2F = {
CreatedOnToolsVersion = 16.0; CreatedOnToolsVersion = 16.0;
@@ -648,7 +649,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -792,7 +793,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -822,7 +823,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -856,7 +857,7 @@
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 210;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -864,7 +865,7 @@
INFOPLIST_FILE = WidgetExtension/Info.plist; INFOPLIST_FILE = WidgetExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Widget; INFOPLIST_KEY_CFBundleDisplayName = Widget;
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.5; IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@@ -899,7 +900,7 @@
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 210;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -907,7 +908,7 @@
INFOPLIST_FILE = WidgetExtension/Info.plist; INFOPLIST_FILE = WidgetExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Widget; INFOPLIST_KEY_CFBundleDisplayName = Widget;
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.5; IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@@ -939,7 +940,7 @@
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 210;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -947,7 +948,7 @@
INFOPLIST_FILE = WidgetExtension/Info.plist; INFOPLIST_FILE = WidgetExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Widget; INFOPLIST_KEY_CFBundleDisplayName = Widget;
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.5; IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@@ -978,7 +979,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -1022,7 +1023,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -1063,7 +1064,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;

View File

@@ -78,7 +78,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.134.0</string> <string>1.135.1</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@@ -93,7 +93,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>208</string> <string>210</string>
<key>FLTEnableImpeller</key> <key>FLTEnableImpeller</key>
<true /> <true />
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
@@ -115,8 +115,8 @@
</dict> </dict>
<key>NSBonjourServices</key> <key>NSBonjourServices</key>
<array> <array>
<string>_googlecast._tcp</string> <string>_googlecast._tcp</string>
<string>_CC1AD845._googlecast._tcp</string> <string>_CC1AD845._googlecast._tcp</string>
</array> </array>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>We need to access the camera to let you take beautiful video using this app</string> <string>We need to access the camera to let you take beautiful video using this app</string>
@@ -168,5 +168,8 @@
<true /> <true />
<key>NSFaceIDUsageDescription</key> <key>NSFaceIDUsageDescription</key>
<string>We need to use FaceID to allow access to your locked folder</string> <string>We need to use FaceID to allow access to your locked folder</string>
<key>NSLocalNetworkUsageDescription</key>
<string>We need local network permission to connect to the local server using IP address and
allow the casting feature to work</string>
</dict> </dict>
</plist> </plist>

View File

@@ -17,6 +17,14 @@ enum AssetType: String, Codable {
case other = "OTHER" case other = "OTHER"
} }
struct ServerWellKnown: Codable {
struct APIInfo: Codable{
let endpoint: String
}
let api: APIInfo
}
struct SearchResult: Codable { struct SearchResult: Codable {
let id: String let id: String
let type: AssetType let type: AssetType
@@ -57,7 +65,7 @@ class ImmichAPI {
init() async throws { init() async throws {
// fetch the credentials from the UserDefaults store that dart placed here // fetch the credentials from the UserDefaults store that dart placed here
guard let defaults = UserDefaults(suiteName: "group.app.immich.share"), guard let defaults = UserDefaults(suiteName: "group.app.immich.share"),
let serverURL = defaults.string(forKey: "widget_server_url"), var serverURL = defaults.string(forKey: "widget_server_url"),
let sessionKey = defaults.string(forKey: "widget_auth_token") let sessionKey = defaults.string(forKey: "widget_auth_token")
else { else {
throw WidgetError.noLogin throw WidgetError.noLogin
@@ -66,13 +74,55 @@ class ImmichAPI {
if serverURL == "" || sessionKey == "" { if serverURL == "" || sessionKey == "" {
throw WidgetError.noLogin throw WidgetError.noLogin
} }
serverConfig = ServerConfig( // migrate the server list value to a JSON array if it is not already
serverEndpoint: serverURL, if !serverURL.starts(with: "[") {
sessionKey: sessionKey let newServerList = "[\"\(serverURL)\"]"
) defaults.set(newServerList, forKey: "widget_server_url")
serverURL = newServerList
}
guard let urls = try? JSONDecoder().decode([String].self, from: serverURL.data(using: .utf8)!) else {
throw WidgetError.noLogin
}
for url in urls {
guard let endpointURL = URL(string: url) else { continue }
if let apiURL = await Self.validateServer(at: endpointURL) {
serverConfig = ServerConfig(
serverEndpoint: apiURL.absoluteString,
sessionKey: sessionKey
)
return
}
}
throw WidgetError.fetchFailed
} }
private static func validateServer(at endpointURL: URL) async -> URL? {
// build a URL that is only scheme, host, and port
var components = URLComponents()
components.scheme = endpointURL.scheme
components.host = endpointURL.host
components.port = endpointURL.port
guard let baseURL = components.url else { return nil }
var pingURL = baseURL
pingURL.appendPathComponent(".well-known")
pingURL.appendPathComponent("immich")
guard let (serverPingJSON, _) = try? await URLSession.shared.data(from: pingURL) else { return nil }
guard let apiInfo = try? JSONDecoder().decode(ServerWellKnown.self, from: serverPingJSON) else { return nil }
var apiURL = baseURL
apiURL.appendPathComponent(apiInfo.api.endpoint)
return apiURL
}
private func buildRequestURL( private func buildRequestURL(
serverConfig: ServerConfig, serverConfig: ServerConfig,
endpoint: String, endpoint: String,

View File

@@ -20,8 +20,11 @@ struct ImmichMemoryProvider: TimelineProvider {
completion: @escaping @Sendable (ImageEntry) -> Void completion: @escaping @Sendable (ImageEntry) -> Void
) { ) {
Task { Task {
guard let api = try? await ImmichAPI() else { var api: ImmichAPI
completion(ImageEntry(date: Date(), image: nil, error: .noLogin)) do {
api = try await ImmichAPI()
} catch let error as WidgetError {
completion(ImageEntry(date: Date(), image: nil, error: error))
return return
} }
@@ -79,9 +82,13 @@ struct ImmichMemoryProvider: TimelineProvider {
Task { Task {
var entries: [ImageEntry] = [] var entries: [ImageEntry] = []
let now = Date() let now = Date()
guard let api = try? await ImmichAPI() else {
entries.append(ImageEntry(date: now, image: nil, error: .noLogin)) var api: ImmichAPI
do {
api = try await ImmichAPI()
} catch let error as WidgetError {
entries.append(ImageEntry(date: now, image: nil, error: error))
completion(Timeline(entries: entries, policy: .atEnd)) completion(Timeline(entries: entries, policy: .atEnd))
return return
} }

View File

@@ -43,7 +43,7 @@ struct RandomConfigurationAppIntent: WidgetConfigurationIntent {
"Choose an album to show images from" "Choose an album to show images from"
} }
@Parameter(title: "Album", default: NO_ALBUM) @Parameter(title: "Album")
var album: Album? var album: Album?
@Parameter(title: "Show Album Name", default: false) @Parameter(title: "Show Album Name", default: false)
@@ -63,10 +63,15 @@ struct ImmichRandomProvider: AppIntentTimelineProvider {
) async ) async
-> ImageEntry -> ImageEntry
{ {
guard let api = try? await ImmichAPI() else { var api: ImmichAPI
return ImageEntry(date: Date(), image: nil, error: .noLogin) do {
api = try await ImmichAPI()
} catch let error as WidgetError {
return ImageEntry(date: Date(), image: nil, error: error)
} catch {
return ImageEntry(date: Date(), image: nil, error: .fetchFailed)
} }
guard guard
let randomImage = try? await api.fetchSearchResults( let randomImage = try? await api.fetchSearchResults(
with: SearchFilters(size: 1) with: SearchFilters(size: 1)
@@ -100,15 +105,21 @@ struct ImmichRandomProvider: AppIntentTimelineProvider {
let now = Date() let now = Date()
// If we don't have a server config, return an entry with an error // If we don't have a server config, return an entry with an error
guard let api = try? await ImmichAPI() else { var api: ImmichAPI
entries.append(ImageEntry(date: now, image: nil, error: .noLogin)) do {
api = try await ImmichAPI()
} catch let error as WidgetError {
entries.append(ImageEntry(date: now, image: nil, error: error))
return Timeline(entries: entries, policy: .atEnd)
} catch {
entries.append(ImageEntry(date: now, image: nil, error: .fetchFailed))
return Timeline(entries: entries, policy: .atEnd) return Timeline(entries: entries, policy: .atEnd)
} }
// nil if album is NONE or nil // nil if album is NONE or nil
let albumId = let albumId =
configuration.album?.id != "NONE" ? configuration.album?.id : nil configuration.album?.id != "NONE" ? configuration.album?.id : nil
var albumName: String? = albumId != nil ? configuration.album?.albumName : nil let albumName: String? = albumId != nil ? configuration.album?.albumName : nil
if albumId != nil { if albumId != nil {
// make sure the album exists on server, otherwise show error // make sure the album exists on server, otherwise show error

View File

@@ -22,7 +22,7 @@ platform :ios do
path: "./Runner.xcodeproj", path: "./Runner.xcodeproj",
) )
increment_version_number( increment_version_number(
version_number: "1.135.0" version_number: "1.135.3"
) )
increment_build_number( increment_build_number(
build_number: latest_testflight_build_number + 1, build_number: latest_testflight_build_number + 1,

View File

@@ -10,3 +10,5 @@ enum TextSearchType {
} }
enum AssetVisibilityEnum { timeline, hidden, archive, locked } enum AssetVisibilityEnum { timeline, hidden, archive, locked }
enum SortUserBy { id }

View File

@@ -11,6 +11,7 @@ const Map<String, Locale> locales = {
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'SIMPLIFIED'), Locale.fromSubtags(languageCode: 'zh', scriptCode: 'SIMPLIFIED'),
'Chinese Traditional (zh_TW)': 'Chinese Traditional (zh_TW)':
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'), Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'),
'Croatian (hr)': Locale('hr'),
'Czech (cs)': Locale('cs'), 'Czech (cs)': Locale('cs'),
'Danish (da)': Locale('da'), 'Danish (da)': Locale('da'),
'Dutch (nl)': Locale('nl'), 'Dutch (nl)': Locale('nl'),

View File

@@ -1,10 +0,0 @@
import 'dart:typed_data';
import 'dart:ui';
abstract interface class IAssetMediaRepository {
Future<Uint8List?> getThumbnail(
String id, {
int quality = 80,
Size size = const Size.square(256),
});
}

View File

@@ -1,12 +0,0 @@
import 'dart:async';
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:immich_mobile/domain/models/device_asset.model.dart';
abstract interface class IDeviceAssetRepository implements IDatabaseRepository {
Future<bool> updateAll(List<DeviceAsset> assetHash);
Future<List<DeviceAsset>> getByIds(List<String> localIds);
Future<void> deleteIds(List<String> ids);
}

View File

@@ -1,14 +0,0 @@
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:immich_mobile/domain/models/exif.model.dart';
abstract interface class IExifInfoRepository implements IDatabaseRepository {
Future<ExifInfo?> get(int assetId);
Future<ExifInfo> update(ExifInfo exifInfo);
Future<List<ExifInfo>> updateAll(List<ExifInfo> exifInfos);
Future<void> delete(int assetId);
Future<void> deleteAll();
}

View File

@@ -1,33 +0,0 @@
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/local_album.model.dart';
abstract interface class ILocalAlbumRepository implements IDatabaseRepository {
Future<List<LocalAlbum>> getAll({Set<SortLocalAlbumsBy> sortBy = const {}});
Future<List<LocalAsset>> getAssets(String albumId);
Future<List<String>> getAssetIds(String albumId);
Future<void> upsert(
LocalAlbum album, {
Iterable<LocalAsset> toUpsert = const [],
Iterable<String> toDelete = const [],
});
Future<void> updateAll(Iterable<LocalAlbum> albums);
Future<void> delete(String albumId);
Future<void> processDelta({
required List<LocalAsset> updates,
required List<String> deletes,
required Map<String, List<String>> assetAlbums,
});
Future<void> syncDeletes(String albumId, Iterable<String> assetIdsToKeep);
Future<List<LocalAsset>> getAssetsToHash(String albumId);
}
enum SortLocalAlbumsBy { id, backupSelection, isIosSharedAlbum }

View File

@@ -1,6 +0,0 @@
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
abstract interface class ILocalAssetRepository implements IDatabaseRepository {
Future<void> updateHashes(Iterable<LocalAsset> hashes);
}

View File

@@ -1,17 +0,0 @@
import 'dart:async';
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:immich_mobile/domain/models/log.model.dart';
abstract interface class ILogRepository implements IDatabaseRepository {
Future<bool> insert(LogMessage log);
Future<bool> insertAll(Iterable<LogMessage> logs);
Future<List<LogMessage>> getAll();
Future<bool> deleteAll();
/// Truncates the logs to the most recent [limit]. Defaults to recent 250 logs
Future<void> truncate({int limit = 250});
}

View File

@@ -1,7 +0,0 @@
import 'dart:io';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
abstract interface class IStorageRepository {
Future<File?> getFileForAsset(LocalAsset asset);
}

View File

@@ -1,20 +0,0 @@
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
abstract interface class IStoreRepository implements IDatabaseRepository {
Future<bool> insert<T>(StoreKey<T> key, T value);
Future<T?> tryGet<T>(StoreKey<T> key);
Future<List<StoreDto<Object>>> getAll();
Stream<T?> watch<T>(StoreKey<T> key);
Stream<StoreDto<Object>> watchAll();
Future<bool> update<T>(StoreKey<T> key, T value);
Future<void> delete<T>(StoreKey<T> key);
Future<void> deleteAll();
}

View File

@@ -1,12 +0,0 @@
import 'package:http/http.dart' as http;
import 'package:immich_mobile/domain/models/sync_event.model.dart';
abstract interface class ISyncApiRepository {
Future<void> ack(List<String> data);
Future<void> streamChanges(
Function(List<SyncEvent>, Function() abort) onData, {
int batchSize,
http.Client? httpClient,
});
}

View File

@@ -1,18 +0,0 @@
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:openapi/api.dart';
abstract interface class ISyncStreamRepository implements IDatabaseRepository {
Future<void> updateUsersV1(Iterable<SyncUserV1> data);
Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data);
Future<void> updatePartnerV1(Iterable<SyncPartnerV1> data);
Future<void> deletePartnerV1(Iterable<SyncPartnerDeleteV1> data);
Future<void> updateAssetsV1(Iterable<SyncAssetV1> data);
Future<void> deleteAssetsV1(Iterable<SyncAssetDeleteV1> data);
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data);
Future<void> updatePartnerAssetsV1(Iterable<SyncAssetV1> data);
Future<void> deletePartnerAssetsV1(Iterable<SyncAssetDeleteV1> data);
Future<void> updatePartnerAssetsExifV1(Iterable<SyncAssetExifV1> data);
}

View File

@@ -1,27 +0,0 @@
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
abstract interface class ITimelineRepository implements IDatabaseRepository {
Stream<List<Bucket>> watchMainBucket(
List<String> timelineUsers, {
GroupAssetsBy groupBy = GroupAssetsBy.day,
});
Future<List<BaseAsset>> getMainBucketAssets(
List<String> timelineUsers, {
required int offset,
required int count,
});
Stream<List<Bucket>> watchLocalBucket(
String albumId, {
GroupAssetsBy groupBy = GroupAssetsBy.day,
});
Future<List<BaseAsset>> getLocalBucketAssets(
String albumId, {
required int offset,
required int count,
});
}

View File

@@ -1,22 +0,0 @@
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
abstract interface class IUserRepository implements IDatabaseRepository {
Future<bool> insert(UserDto user);
Future<UserDto?> getByUserId(String id);
Future<List<UserDto?>> getByUserIds(List<String> ids);
Future<List<UserDto>> getAll({SortUserBy? sortBy});
Future<bool> updateAll(List<UserDto> users);
Future<UserDto> update(UserDto user);
Future<void> delete(List<String> ids);
Future<void> deleteAll();
}
enum SortUserBy { id }

View File

@@ -1,15 +0,0 @@
import 'dart:typed_data';
import 'package:immich_mobile/domain/models/user.model.dart';
abstract interface class IUserApiRepository {
Future<UserDto?> getMyUser();
Future<List<UserDto>> getAll();
/// Saves the [data] in the server and uses it as the current users profile image
Future<String> createProfileImage({
required String name,
required Uint8List data,
});
}

View File

@@ -1,11 +1,5 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert'; import 'dart:convert';
abstract interface class IPersonApiRepository {
Future<List<Person>> getAll();
Future<Person> update(String id, {String? name});
}
class Person { class Person {
Person({ Person({
required this.id, required this.id,

View File

@@ -1,10 +1,10 @@
import 'dart:convert'; import 'dart:convert';
import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/interfaces/local_album.interface.dart';
import 'package:immich_mobile/domain/interfaces/local_asset.interface.dart';
import 'package:immich_mobile/domain/interfaces/storage.interface.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/local_album.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/storage.repository.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/presentation/pages/dev/dev_logger.dart'; import 'package:immich_mobile/presentation/pages/dev/dev_logger.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@@ -12,16 +12,16 @@ import 'package:logging/logging.dart';
class HashService { class HashService {
final int batchSizeLimit; final int batchSizeLimit;
final int batchFileLimit; final int batchFileLimit;
final ILocalAlbumRepository _localAlbumRepository; final DriftLocalAlbumRepository _localAlbumRepository;
final ILocalAssetRepository _localAssetRepository; final DriftLocalAssetRepository _localAssetRepository;
final IStorageRepository _storageRepository; final StorageRepository _storageRepository;
final NativeSyncApi _nativeSyncApi; final NativeSyncApi _nativeSyncApi;
final _log = Logger('HashService'); final _log = Logger('HashService');
HashService({ HashService({
required ILocalAlbumRepository localAlbumRepository, required DriftLocalAlbumRepository localAlbumRepository,
required ILocalAssetRepository localAssetRepository, required DriftLocalAssetRepository localAssetRepository,
required IStorageRepository storageRepository, required StorageRepository storageRepository,
required NativeSyncApi nativeSyncApi, required NativeSyncApi nativeSyncApi,
this.batchSizeLimit = kBatchHashSizeLimit, this.batchSizeLimit = kBatchHashSizeLimit,
this.batchFileLimit = kBatchHashFileLimit, this.batchFileLimit = kBatchHashFileLimit,

View File

@@ -2,11 +2,11 @@ import 'dart:async';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:immich_mobile/domain/interfaces/local_album.interface.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/local_album.model.dart'; import 'package:immich_mobile/domain/models/local_album.model.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.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/presentation/pages/dev/dev_logger.dart'; import 'package:immich_mobile/presentation/pages/dev/dev_logger.dart';
import 'package:immich_mobile/utils/diff.dart'; import 'package:immich_mobile/utils/diff.dart';
@@ -14,14 +14,14 @@ import 'package:logging/logging.dart';
import 'package:platform/platform.dart'; import 'package:platform/platform.dart';
class LocalSyncService { class LocalSyncService {
final ILocalAlbumRepository _localAlbumRepository; final DriftLocalAlbumRepository _localAlbumRepository;
final NativeSyncApi _nativeSyncApi; final NativeSyncApi _nativeSyncApi;
final Platform _platform; final Platform _platform;
final StoreService _storeService; final StoreService _storeService;
final Logger _log = Logger("DeviceSyncService"); final Logger _log = Logger("DeviceSyncService");
LocalSyncService({ LocalSyncService({
required ILocalAlbumRepository localAlbumRepository, required DriftLocalAlbumRepository localAlbumRepository,
required NativeSyncApi nativeSyncApi, required NativeSyncApi nativeSyncApi,
required StoreService storeService, required StoreService storeService,
Platform? platform, Platform? platform,

View File

@@ -2,10 +2,10 @@ import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/interfaces/log.interface.dart';
import 'package:immich_mobile/domain/interfaces/store.interface.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/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
/// Service responsible for handling application logging. /// Service responsible for handling application logging.
@@ -14,8 +14,8 @@ import 'package:logging/logging.dart';
/// writes them to a persistent [ILogRepository], and manages log levels /// writes them to a persistent [ILogRepository], and manages log levels
/// via [IStoreRepository] /// via [IStoreRepository]
class LogService { class LogService {
final ILogRepository _logRepository; final IsarLogRepository _logRepository;
final IStoreRepository _storeRepository; final IsarStoreRepository _storeRepository;
final List<LogMessage> _msgBuffer = []; final List<LogMessage> _msgBuffer = [];
@@ -37,8 +37,8 @@ class LogService {
} }
static Future<LogService> init({ static Future<LogService> init({
required ILogRepository logRepository, required IsarLogRepository logRepository,
required IStoreRepository storeRepository, required IsarStoreRepository storeRepository,
bool shouldBuffer = true, bool shouldBuffer = true,
}) async { }) async {
_instance ??= await create( _instance ??= await create(
@@ -50,8 +50,8 @@ class LogService {
} }
static Future<LogService> create({ static Future<LogService> create({
required ILogRepository logRepository, required IsarLogRepository logRepository,
required IStoreRepository storeRepository, required IsarStoreRepository storeRepository,
bool shouldBuffer = true, bool shouldBuffer = true,
}) async { }) async {
final instance = LogService._(logRepository, storeRepository, shouldBuffer); final instance = LogService._(logRepository, storeRepository, shouldBuffer);

View File

@@ -1,18 +1,18 @@
import 'dart:async'; import 'dart:async';
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
/// Provides access to a persistent key-value store with an in-memory cache. /// Provides access to a persistent key-value store with an in-memory cache.
/// Listens for repository changes to keep the cache updated. /// Listens for repository changes to keep the cache updated.
class StoreService { class StoreService {
final IStoreRepository _storeRepository; final IsarStoreRepository _storeRepository;
/// In-memory cache. Keys are [StoreKey.id] /// In-memory cache. Keys are [StoreKey.id]
final Map<int, Object?> _cache = {}; final Map<int, Object?> _cache = {};
late final StreamSubscription<StoreDto> _storeUpdateSubscription; late final StreamSubscription<StoreDto> _storeUpdateSubscription;
StoreService._({required IStoreRepository storeRepository}) StoreService._({required IsarStoreRepository storeRepository})
: _storeRepository = storeRepository; : _storeRepository = storeRepository;
// TODO: Temporary typedef to make minimal changes. Remove this and make the presentation layer access store through a provider // TODO: Temporary typedef to make minimal changes. Remove this and make the presentation layer access store through a provider
@@ -26,14 +26,14 @@ class StoreService {
// TODO: Replace the implementation with the one from create after removing the typedef // TODO: Replace the implementation with the one from create after removing the typedef
static Future<StoreService> init({ static Future<StoreService> init({
required IStoreRepository storeRepository, required IsarStoreRepository storeRepository,
}) async { }) async {
_instance ??= await create(storeRepository: storeRepository); _instance ??= await create(storeRepository: storeRepository);
return _instance!; return _instance!;
} }
static Future<StoreService> create({ static Future<StoreService> create({
required IStoreRepository storeRepository, required IsarStoreRepository storeRepository,
}) async { }) async {
final instance = StoreService._(storeRepository: storeRepository); final instance = StoreService._(storeRepository: storeRepository);
await instance._populateCache(); await instance._populateCache();

View File

@@ -1,21 +1,21 @@
import 'dart:async'; import 'dart:async';
import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/sync_stream.interface.dart';
import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class SyncStreamService { class SyncStreamService {
final Logger _logger = Logger('SyncStreamService'); final Logger _logger = Logger('SyncStreamService');
final ISyncApiRepository _syncApiRepository; final SyncApiRepository _syncApiRepository;
final ISyncStreamRepository _syncStreamRepository; final SyncStreamRepository _syncStreamRepository;
final bool Function()? _cancelChecker; final bool Function()? _cancelChecker;
SyncStreamService({ SyncStreamService({
required ISyncApiRepository syncApiRepository, required SyncApiRepository syncApiRepository,
required ISyncStreamRepository syncStreamRepository, required SyncStreamRepository syncStreamRepository,
bool Function()? cancelChecker, bool Function()? cancelChecker,
}) : _syncApiRepository = syncApiRepository, }) : _syncApiRepository = syncApiRepository,
_syncStreamRepository = syncStreamRepository, _syncStreamRepository = syncStreamRepository,

View File

@@ -3,11 +3,11 @@ import 'dart:math' as math;
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/interfaces/timeline.interface.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/domain/models/setting.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/services/setting.service.dart'; import 'package:immich_mobile/domain/services/setting.service.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';
typedef TimelineAssetSource = Future<List<BaseAsset>> Function( typedef TimelineAssetSource = Future<List<BaseAsset>> Function(
@@ -18,11 +18,11 @@ typedef TimelineAssetSource = Future<List<BaseAsset>> Function(
typedef TimelineBucketSource = Stream<List<Bucket>> Function(); typedef TimelineBucketSource = Stream<List<Bucket>> Function();
class TimelineFactory { class TimelineFactory {
final ITimelineRepository _timelineRepository; final DriftTimelineRepository _timelineRepository;
final SettingsService _settingsService; final SettingsService _settingsService;
const TimelineFactory({ const TimelineFactory({
required ITimelineRepository timelineRepository, required DriftTimelineRepository timelineRepository,
required SettingsService settingsService, required SettingsService settingsService,
}) : _timelineRepository = timelineRepository, }) : _timelineRepository = timelineRepository,
_settingsService = settingsService; _settingsService = settingsService;

View File

@@ -1,24 +1,24 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/interfaces/user_api.interface.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/store.service.dart'; import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
class UserService { class UserService {
final Logger _log = Logger("UserService"); final Logger _log = Logger("UserService");
final IUserRepository _userRepository; final IsarUserRepository _isarUserRepository;
final IUserApiRepository _userApiRepository; final UserApiRepository _userApiRepository;
final StoreService _storeService; final StoreService _storeService;
UserService({ UserService({
required IUserRepository userRepository, required IsarUserRepository isarUserRepository,
required IUserApiRepository userApiRepository, required UserApiRepository userApiRepository,
required StoreService storeService, required StoreService storeService,
}) : _userRepository = userRepository, }) : _isarUserRepository = isarUserRepository,
_userApiRepository = userApiRepository, _userApiRepository = userApiRepository,
_storeService = storeService; _storeService = storeService;
@@ -38,7 +38,7 @@ class UserService {
final user = await _userApiRepository.getMyUser(); final user = await _userApiRepository.getMyUser();
if (user == null) return null; if (user == null) return null;
await _storeService.put(StoreKey.currentUser, user); await _storeService.put(StoreKey.currentUser, user);
await _userRepository.update(user); await _isarUserRepository.update(user);
return user; return user;
} }
@@ -50,7 +50,7 @@ class UserService {
); );
final updatedUser = getMyUser().copyWith(profileImagePath: path); final updatedUser = getMyUser().copyWith(profileImagePath: path);
await _storeService.put(StoreKey.currentUser, updatedUser); await _storeService.put(StoreKey.currentUser, updatedUser);
await _userRepository.update(updatedUser); await _isarUserRepository.update(updatedUser);
return path; return path;
} catch (e) { } catch (e) {
_log.warning("Failed to upload profile image", e); _log.warning("Failed to upload profile image", e);
@@ -59,10 +59,10 @@ class UserService {
} }
Future<List<UserDto>> getAll() async { Future<List<UserDto>> getAll() async {
return await _userRepository.getAll(); return await _isarUserRepository.getAll();
} }
Future<void> deleteAll() { Future<void> deleteAll() {
return _userRepository.deleteAll(); return _isarUserRepository.deleteAll();
} }
} }

View File

@@ -1,13 +1,11 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui'; import 'dart:ui';
import 'package:immich_mobile/domain/interfaces/asset_media.interface.dart';
import 'package:photo_manager/photo_manager.dart'; import 'package:photo_manager/photo_manager.dart';
class AssetMediaRepository implements IAssetMediaRepository { class AssetMediaRepository {
const AssetMediaRepository(); const AssetMediaRepository();
@override
Future<Uint8List?> getThumbnail( Future<Uint8List?> getThumbnail(
String id, { String id, {
int quality = 80, int quality = 80,

View File

@@ -1,23 +1,19 @@
import 'package:immich_mobile/domain/interfaces/device_asset.interface.dart';
import 'package:immich_mobile/domain/models/device_asset.model.dart'; import 'package:immich_mobile/domain/models/device_asset.model.dart';
import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
class IsarDeviceAssetRepository extends IsarDatabaseRepository class IsarDeviceAssetRepository extends IsarDatabaseRepository {
implements IDeviceAssetRepository {
final Isar _db; final Isar _db;
const IsarDeviceAssetRepository(this._db) : super(_db); const IsarDeviceAssetRepository(this._db) : super(_db);
@override
Future<void> deleteIds(List<String> ids) { Future<void> deleteIds(List<String> ids) {
return transaction(() async { return transaction(() async {
await _db.deviceAssetEntitys.deleteAllByAssetId(ids.toList()); await _db.deviceAssetEntitys.deleteAllByAssetId(ids.toList());
}); });
} }
@override
Future<List<DeviceAsset>> getByIds(List<String> localIds) { Future<List<DeviceAsset>> getByIds(List<String> localIds) {
return _db.deviceAssetEntitys return _db.deviceAssetEntitys
.where() .where()
@@ -26,7 +22,6 @@ class IsarDeviceAssetRepository extends IsarDatabaseRepository
.then((value) => value.map((e) => e.toModel()).toList()); .then((value) => value.map((e) => e.toModel()).toList());
} }
@override
Future<bool> updateAll(List<DeviceAsset> assetHash) { Future<bool> updateAll(List<DeviceAsset> assetHash) {
return transaction(() async { return transaction(() async {
await _db.deviceAssetEntitys await _db.deviceAssetEntitys

View File

@@ -1,36 +1,30 @@
import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/domain/models/exif.model.dart';
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart' import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'
as entity; as entity;
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
class IsarExifRepository extends IsarDatabaseRepository class IsarExifRepository extends IsarDatabaseRepository {
implements IExifInfoRepository {
final Isar _db; final Isar _db;
const IsarExifRepository(this._db) : super(_db); const IsarExifRepository(this._db) : super(_db);
@override
Future<void> delete(int assetId) async { Future<void> delete(int assetId) async {
await transaction(() async { await transaction(() async {
await _db.exifInfos.delete(assetId); await _db.exifInfos.delete(assetId);
}); });
} }
@override
Future<void> deleteAll() async { Future<void> deleteAll() async {
await transaction(() async { await transaction(() async {
await _db.exifInfos.clear(); await _db.exifInfos.clear();
}); });
} }
@override
Future<ExifInfo?> get(int assetId) async { Future<ExifInfo?> get(int assetId) async {
return (await _db.exifInfos.get(assetId))?.toDto(); return (await _db.exifInfos.get(assetId))?.toDto();
} }
@override
Future<ExifInfo> update(ExifInfo exifInfo) { Future<ExifInfo> update(ExifInfo exifInfo) {
return transaction(() async { return transaction(() async {
await _db.exifInfos.put(entity.ExifInfo.fromDto(exifInfo)); await _db.exifInfos.put(entity.ExifInfo.fromDto(exifInfo));
@@ -38,7 +32,6 @@ class IsarExifRepository extends IsarDatabaseRepository
}); });
} }
@override
Future<List<ExifInfo>> updateAll(List<ExifInfo> exifInfos) { Future<List<ExifInfo>> updateAll(List<ExifInfo> exifInfos) {
return transaction(() async { return transaction(() async {
await _db.exifInfos.putAll( await _db.exifInfos.putAll(

View File

@@ -1,5 +1,4 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/interfaces/local_album.interface.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/local_album.model.dart'; import 'package:immich_mobile/domain/models/local_album.model.dart';
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
@@ -8,15 +7,15 @@ import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.d
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:platform/platform.dart'; import 'package:platform/platform.dart';
class DriftLocalAlbumRepository extends DriftDatabaseRepository enum SortLocalAlbumsBy { id, backupSelection, isIosSharedAlbum }
implements ILocalAlbumRepository {
class DriftLocalAlbumRepository extends DriftDatabaseRepository {
final Drift _db; final Drift _db;
final Platform _platform; final Platform _platform;
const DriftLocalAlbumRepository(this._db, {Platform? platform}) const DriftLocalAlbumRepository(this._db, {Platform? platform})
: _platform = platform ?? const LocalPlatform(), : _platform = platform ?? const LocalPlatform(),
super(_db); super(_db);
@override
Future<List<LocalAlbum>> getAll({Set<SortLocalAlbumsBy> sortBy = const {}}) { Future<List<LocalAlbum>> getAll({Set<SortLocalAlbumsBy> sortBy = const {}}) {
final assetCount = _db.localAlbumAssetEntity.assetId.count(); final assetCount = _db.localAlbumAssetEntity.assetId.count();
@@ -56,7 +55,6 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
.get(); .get();
} }
@override
Future<void> delete(String albumId) => transaction(() async { Future<void> delete(String albumId) => transaction(() async {
// Remove all assets that are only in this particular album // Remove all assets that are only in this particular album
// We cannot remove all assets in the album because they might be in other albums in iOS // We cannot remove all assets in the album because they might be in other albums in iOS
@@ -72,7 +70,6 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
.delete(); .delete();
}); });
@override
Future<void> syncDeletes( Future<void> syncDeletes(
String albumId, String albumId,
Iterable<String> assetIdsToKeep, Iterable<String> assetIdsToKeep,
@@ -101,7 +98,6 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
await deleteSmt.go(); await deleteSmt.go();
} }
@override
Future<void> upsert( Future<void> upsert(
LocalAlbum localAlbum, { LocalAlbum localAlbum, {
Iterable<LocalAsset> toUpsert = const [], Iterable<LocalAsset> toUpsert = const [],
@@ -134,7 +130,6 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
}); });
} }
@override
Future<void> updateAll(Iterable<LocalAlbum> albums) { Future<void> updateAll(Iterable<LocalAlbum> albums) {
return _db.transaction(() async { return _db.transaction(() async {
await _db.localAlbumEntity await _db.localAlbumEntity
@@ -185,7 +180,6 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
}); });
} }
@override
Future<List<LocalAsset>> getAssets(String albumId) { Future<List<LocalAsset>> getAssets(String albumId) {
final query = _db.localAlbumAssetEntity.select().join( final query = _db.localAlbumAssetEntity.select().join(
[ [
@@ -202,7 +196,6 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
.get(); .get();
} }
@override
Future<List<String>> getAssetIds(String albumId) { Future<List<String>> getAssetIds(String albumId) {
final query = _db.localAlbumAssetEntity.selectOnly() final query = _db.localAlbumAssetEntity.selectOnly()
..addColumns([_db.localAlbumAssetEntity.assetId]) ..addColumns([_db.localAlbumAssetEntity.assetId])
@@ -212,7 +205,6 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
.get(); .get();
} }
@override
Future<void> processDelta({ Future<void> processDelta({
required List<LocalAsset> updates, required List<LocalAsset> updates,
required List<String> deletes, required List<String> deletes,
@@ -253,7 +245,6 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
}); });
} }
@override
Future<List<LocalAsset>> getAssetsToHash(String albumId) { Future<List<LocalAsset>> getAssetsToHash(String albumId) {
final query = _db.localAlbumAssetEntity.select().join( final query = _db.localAlbumAssetEntity.select().join(
[ [

View File

@@ -1,15 +1,12 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/interfaces/local_asset.interface.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/entities/local_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
class DriftLocalAssetRepository extends DriftDatabaseRepository class DriftLocalAssetRepository extends DriftDatabaseRepository {
implements ILocalAssetRepository {
final Drift _db; final Drift _db;
const DriftLocalAssetRepository(this._db) : super(_db); const DriftLocalAssetRepository(this._db) : super(_db);
@override
Future<void> updateHashes(Iterable<LocalAsset> hashes) { Future<void> updateHashes(Iterable<LocalAsset> hashes) {
if (hashes.isEmpty) { if (hashes.isEmpty) {
return Future.value(); return Future.value();

View File

@@ -1,28 +1,23 @@
import 'package:immich_mobile/domain/interfaces/log.interface.dart';
import 'package:immich_mobile/domain/models/log.model.dart'; import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/infrastructure/entities/log.entity.dart'; import 'package:immich_mobile/infrastructure/entities/log.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
class IsarLogRepository extends IsarDatabaseRepository class IsarLogRepository extends IsarDatabaseRepository {
implements ILogRepository {
final Isar _db; final Isar _db;
const IsarLogRepository(super.db) : _db = db; const IsarLogRepository(super.db) : _db = db;
@override
Future<bool> deleteAll() async { Future<bool> deleteAll() async {
await transaction(() async => await _db.loggerMessages.clear()); await transaction(() async => await _db.loggerMessages.clear());
return true; return true;
} }
@override
Future<List<LogMessage>> getAll() async { Future<List<LogMessage>> getAll() async {
final logs = final logs =
await _db.loggerMessages.where().sortByCreatedAtDesc().findAll(); await _db.loggerMessages.where().sortByCreatedAtDesc().findAll();
return logs.map((l) => l.toDto()).toList(); return logs.map((l) => l.toDto()).toList();
} }
@override
Future<bool> insert(LogMessage log) async { Future<bool> insert(LogMessage log) async {
final logEntity = LoggerMessage.fromDto(log); final logEntity = LoggerMessage.fromDto(log);
await transaction(() async { await transaction(() async {
@@ -31,7 +26,6 @@ class IsarLogRepository extends IsarDatabaseRepository
return true; return true;
} }
@override
Future<bool> insertAll(Iterable<LogMessage> logs) async { Future<bool> insertAll(Iterable<LogMessage> logs) async {
await transaction(() async { await transaction(() async {
final logEntities = final logEntities =
@@ -41,7 +35,6 @@ class IsarLogRepository extends IsarDatabaseRepository
return true; return true;
} }
@override
Future<void> truncate({int limit = 250}) async { Future<void> truncate({int limit = 250}) async {
await transaction(() async { await transaction(() async {
final count = await _db.loggerMessages.count(); final count = await _db.loggerMessages.count();

View File

@@ -1,14 +1,12 @@
import 'dart:io'; import 'dart:io';
import 'package:immich_mobile/domain/interfaces/storage.interface.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:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:photo_manager/photo_manager.dart'; import 'package:photo_manager/photo_manager.dart';
class StorageRepository implements IStorageRepository { class StorageRepository {
final _log = Logger('StorageRepository'); final _log = Logger('StorageRepository');
@override
Future<File?> getFileForAsset(LocalAsset asset) async { Future<File?> getFileForAsset(LocalAsset asset) async {
File? file; File? file;
try { try {

View File

@@ -1,4 +1,3 @@
import 'package:immich_mobile/domain/interfaces/store.interface.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/infrastructure/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
@@ -6,14 +5,12 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
class IsarStoreRepository extends IsarDatabaseRepository class IsarStoreRepository extends IsarDatabaseRepository {
implements IStoreRepository {
final Isar _db; final Isar _db;
final validStoreKeys = StoreKey.values.map((e) => e.id).toSet(); final validStoreKeys = StoreKey.values.map((e) => e.id).toSet();
IsarStoreRepository(super.db) : _db = db; IsarStoreRepository(super.db) : _db = db;
@override
Future<bool> deleteAll() async { Future<bool> deleteAll() async {
return await transaction(() async { return await transaction(() async {
await _db.storeValues.clear(); await _db.storeValues.clear();
@@ -21,7 +18,6 @@ class IsarStoreRepository extends IsarDatabaseRepository
}); });
} }
@override
Stream<StoreDto<Object>> watchAll() { Stream<StoreDto<Object>> watchAll() {
return _db.storeValues return _db.storeValues
.filter() .filter()
@@ -34,12 +30,10 @@ class IsarStoreRepository extends IsarDatabaseRepository
); );
} }
@override
Future<void> delete<T>(StoreKey<T> key) async { Future<void> delete<T>(StoreKey<T> key) async {
return await transaction(() async => await _db.storeValues.delete(key.id)); return await transaction(() async => await _db.storeValues.delete(key.id));
} }
@override
Future<bool> insert<T>(StoreKey<T> key, T value) async { Future<bool> insert<T>(StoreKey<T> key, T value) async {
return await transaction(() async { return await transaction(() async {
await _db.storeValues.put(await _fromValue(key, value)); await _db.storeValues.put(await _fromValue(key, value));
@@ -47,7 +41,6 @@ class IsarStoreRepository extends IsarDatabaseRepository
}); });
} }
@override
Future<T?> tryGet<T>(StoreKey<T> key) async { Future<T?> tryGet<T>(StoreKey<T> key) async {
final entity = (await _db.storeValues.get(key.id)); final entity = (await _db.storeValues.get(key.id));
if (entity == null) { if (entity == null) {
@@ -56,7 +49,6 @@ class IsarStoreRepository extends IsarDatabaseRepository
return await _toValue(key, entity); return await _toValue(key, entity);
} }
@override
Future<bool> update<T>(StoreKey<T> key, T value) async { Future<bool> update<T>(StoreKey<T> key, T value) async {
return await transaction(() async { return await transaction(() async {
await _db.storeValues.put(await _fromValue(key, value)); await _db.storeValues.put(await _fromValue(key, value));
@@ -64,7 +56,6 @@ class IsarStoreRepository extends IsarDatabaseRepository
}); });
} }
@override
Stream<T?> watch<T>(StoreKey<T> key) async* { Stream<T?> watch<T>(StoreKey<T> key) async* {
yield* _db.storeValues yield* _db.storeValues
.watchObject(key.id, fireImmediately: true) .watchObject(key.id, fireImmediately: true)
@@ -109,7 +100,6 @@ class IsarStoreRepository extends IsarDatabaseRepository
return StoreValue(key.id, intValue: intValue, strValue: strValue); return StoreValue(key.id, intValue: intValue, strValue: strValue);
} }
@override
Future<List<StoreDto<Object>>> getAll() async { Future<List<StoreDto<Object>>> getAll() async {
final entities = await _db.storeValues final entities = await _db.storeValues
.filter() .filter()

View File

@@ -3,24 +3,21 @@ import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart';
import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart';
import 'package:immich_mobile/presentation/pages/dev/dev_logger.dart'; import 'package:immich_mobile/presentation/pages/dev/dev_logger.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class SyncApiRepository implements ISyncApiRepository { class SyncApiRepository {
final Logger _logger = Logger('SyncApiRepository'); final Logger _logger = Logger('SyncApiRepository');
final ApiService _api; final ApiService _api;
SyncApiRepository(this._api); SyncApiRepository(this._api);
@override
Future<void> ack(List<String> data) { Future<void> ack(List<String> data) {
return _api.syncApi.sendSyncAck(SyncAckSetDto(acks: data)); return _api.syncApi.sendSyncAck(SyncAckSetDto(acks: data));
} }
@override
Future<void> streamChanges( Future<void> streamChanges(
Function(List<SyncEvent>, Function() abort) onData, { Function(List<SyncEvent>, Function() abort) onData, {
int batchSize = kSyncEventBatchSize, int batchSize = kSyncEventBatchSize,

View File

@@ -1,5 +1,4 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/interfaces/sync_stream.interface.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/entities/exif.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart';
@@ -10,14 +9,12 @@ import 'package:logging/logging.dart';
import 'package:openapi/api.dart' as api show AssetVisibility; import 'package:openapi/api.dart' as api show AssetVisibility;
import 'package:openapi/api.dart' hide AssetVisibility; import 'package:openapi/api.dart' hide AssetVisibility;
class DriftSyncStreamRepository extends DriftDatabaseRepository class SyncStreamRepository extends DriftDatabaseRepository {
implements ISyncStreamRepository {
final Logger _logger = Logger('DriftSyncStreamRepository'); final Logger _logger = Logger('DriftSyncStreamRepository');
final Drift _db; final Drift _db;
DriftSyncStreamRepository(super.db) : _db = db; SyncStreamRepository(super.db) : _db = db;
@override
Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data) async { Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
@@ -34,7 +31,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updateUsersV1(Iterable<SyncUserV1> data) async { Future<void> updateUsersV1(Iterable<SyncUserV1> data) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
@@ -57,7 +53,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> deletePartnerV1(Iterable<SyncPartnerDeleteV1> data) async { Future<void> deletePartnerV1(Iterable<SyncPartnerDeleteV1> data) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
@@ -77,7 +72,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updatePartnerV1(Iterable<SyncPartnerV1> data) async { Future<void> updatePartnerV1(Iterable<SyncPartnerV1> data) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
@@ -101,7 +95,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> deleteAssetsV1(Iterable<SyncAssetDeleteV1> data) async { Future<void> deleteAssetsV1(Iterable<SyncAssetDeleteV1> data) async {
try { try {
await _deleteAssetsV1(data); await _deleteAssetsV1(data);
@@ -111,7 +104,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updateAssetsV1(Iterable<SyncAssetV1> data) async { Future<void> updateAssetsV1(Iterable<SyncAssetV1> data) async {
try { try {
await _updateAssetsV1(data); await _updateAssetsV1(data);
@@ -121,7 +113,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> deletePartnerAssetsV1(Iterable<SyncAssetDeleteV1> data) async { Future<void> deletePartnerAssetsV1(Iterable<SyncAssetDeleteV1> data) async {
try { try {
await _deleteAssetsV1(data); await _deleteAssetsV1(data);
@@ -131,7 +122,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updatePartnerAssetsV1(Iterable<SyncAssetV1> data) async { Future<void> updatePartnerAssetsV1(Iterable<SyncAssetV1> data) async {
try { try {
await _updateAssetsV1(data); await _updateAssetsV1(data);
@@ -141,7 +131,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data) async { Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data) async {
try { try {
await _updateAssetExifV1(data); await _updateAssetExifV1(data);
@@ -151,7 +140,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updatePartnerAssetsExifV1(Iterable<SyncAssetExifV1> data) async { Future<void> updatePartnerAssetsExifV1(Iterable<SyncAssetExifV1> data) async {
try { try {
await _updateAssetExifV1(data); await _updateAssetExifV1(data);

View File

@@ -3,15 +3,13 @@ import 'dart:async';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/interfaces/timeline.interface.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/timeline.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:stream_transform/stream_transform.dart'; import 'package:stream_transform/stream_transform.dart';
class DriftTimelineRepository extends DriftDatabaseRepository class DriftTimelineRepository extends DriftDatabaseRepository {
implements ITimelineRepository {
final Drift _db; final Drift _db;
const DriftTimelineRepository(super._db) : _db = _db; const DriftTimelineRepository(super._db) : _db = _db;
@@ -28,7 +26,6 @@ class DriftTimelineRepository extends DriftDatabaseRepository
return buckets; return buckets;
} }
@override
Stream<List<Bucket>> watchMainBucket( Stream<List<Bucket>> watchMainBucket(
List<String> userIds, { List<String> userIds, {
GroupAssetsBy groupBy = GroupAssetsBy.day, GroupAssetsBy groupBy = GroupAssetsBy.day,
@@ -49,7 +46,6 @@ class DriftTimelineRepository extends DriftDatabaseRepository
.throttle(const Duration(seconds: 3), trailing: true); .throttle(const Duration(seconds: 3), trailing: true);
} }
@override
Future<List<BaseAsset>> getMainBucketAssets( Future<List<BaseAsset>> getMainBucketAssets(
List<String> userIds, { List<String> userIds, {
required int offset, required int offset,
@@ -90,7 +86,6 @@ class DriftTimelineRepository extends DriftDatabaseRepository
.get(); .get();
} }
@override
Stream<List<Bucket>> watchLocalBucket( Stream<List<Bucket>> watchLocalBucket(
String albumId, { String albumId, {
GroupAssetsBy groupBy = GroupAssetsBy.day, GroupAssetsBy groupBy = GroupAssetsBy.day,
@@ -124,7 +119,6 @@ class DriftTimelineRepository extends DriftDatabaseRepository
}).watch(); }).watch();
} }
@override
Future<List<BaseAsset>> getLocalBucketAssets( Future<List<BaseAsset>> getLocalBucketAssets(
String albumId, { String albumId, {
required int offset, required int offset,

View File

@@ -1,30 +1,26 @@
import 'package:immich_mobile/domain/interfaces/user.interface.dart'; import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
as entity; as entity;
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
class IsarUserRepository extends IsarDatabaseRepository class IsarUserRepository extends IsarDatabaseRepository {
implements IUserRepository {
final Isar _db; final Isar _db;
const IsarUserRepository(super.db) : _db = db; const IsarUserRepository(super.db) : _db = db;
@override
Future<void> delete(List<String> ids) async { Future<void> delete(List<String> ids) async {
await transaction(() async { await transaction(() async {
await _db.users.deleteAllById(ids); await _db.users.deleteAllById(ids);
}); });
} }
@override
Future<void> deleteAll() async { Future<void> deleteAll() async {
await transaction(() async { await transaction(() async {
await _db.users.clear(); await _db.users.clear();
}); });
} }
@override
Future<List<UserDto>> getAll({SortUserBy? sortBy}) async { Future<List<UserDto>> getAll({SortUserBy? sortBy}) async {
return (await _db.users return (await _db.users
.where() .where()
@@ -39,17 +35,14 @@ class IsarUserRepository extends IsarDatabaseRepository
.toList(); .toList();
} }
@override
Future<UserDto?> getByUserId(String id) async { Future<UserDto?> getByUserId(String id) async {
return (await _db.users.getById(id))?.toDto(); return (await _db.users.getById(id))?.toDto();
} }
@override
Future<List<UserDto?>> getByUserIds(List<String> ids) async { Future<List<UserDto?>> getByUserIds(List<String> ids) async {
return (await _db.users.getAllById(ids)).map((u) => u?.toDto()).toList(); return (await _db.users.getAllById(ids)).map((u) => u?.toDto()).toList();
} }
@override
Future<bool> insert(UserDto user) async { Future<bool> insert(UserDto user) async {
await transaction(() async { await transaction(() async {
await _db.users.put(entity.User.fromDto(user)); await _db.users.put(entity.User.fromDto(user));
@@ -57,7 +50,6 @@ class IsarUserRepository extends IsarDatabaseRepository
return true; return true;
} }
@override
Future<UserDto> update(UserDto user) async { Future<UserDto> update(UserDto user) async {
await transaction(() async { await transaction(() async {
await _db.users.put(entity.User.fromDto(user)); await _db.users.put(entity.User.fromDto(user));
@@ -65,7 +57,6 @@ class IsarUserRepository extends IsarDatabaseRepository
return user; return user;
} }
@override
Future<bool> updateAll(List<UserDto> users) async { Future<bool> updateAll(List<UserDto> users) async {
await transaction(() async { await transaction(() async {
await _db.users.putAll(users.map(entity.User.fromDto).toList()); await _db.users.putAll(users.map(entity.User.fromDto).toList());

View File

@@ -1,17 +1,15 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:immich_mobile/domain/interfaces/user_api.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/repositories/api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/api.repository.dart';
import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class UserApiRepository extends ApiRepository implements IUserApiRepository { class UserApiRepository extends ApiRepository {
final UsersApi _api; final UsersApi _api;
const UserApiRepository(this._api); const UserApiRepository(this._api);
@override
Future<UserDto?> getMyUser() async { Future<UserDto?> getMyUser() async {
final (adminDto, preferenceDto) = final (adminDto, preferenceDto) =
await (_api.getMyUser(), _api.getMyPreferences()).wait; await (_api.getMyUser(), _api.getMyPreferences()).wait;
@@ -20,7 +18,6 @@ class UserApiRepository extends ApiRepository implements IUserApiRepository {
return UserConverter.fromAdminDto(adminDto, preferenceDto); return UserConverter.fromAdminDto(adminDto, preferenceDto);
} }
@override
Future<String> createProfileImage({ Future<String> createProfileImage({
required String name, required String name,
required Uint8List data, required Uint8List data,
@@ -33,7 +30,6 @@ class UserApiRepository extends ApiRepository implements IUserApiRepository {
return res.profileImagePath; return res.profileImagePath;
} }
@override
Future<List<UserDto>> getAll() async { Future<List<UserDto>> getAll() async {
final dto = await checkNull(_api.searchUsers()); final dto = await checkNull(_api.searchUsers());
return dto.map(UserConverter.fromSimpleUserDto).toList(); return dto.map(UserConverter.fromSimpleUserDto).toList();

View File

@@ -1,16 +0,0 @@
import 'package:immich_mobile/models/activities/activity.model.dart';
abstract interface class IActivityApiRepository {
Future<List<Activity>> getAll(
String albumId, {
String? assetId,
});
Future<Activity> create(
String albumId,
ActivityType type, {
String? assetId,
String? comment,
});
Future<void> delete(String id);
Future<ActivityStats> getStats(String albumId, {String? assetId});
}

View File

@@ -1,55 +0,0 @@
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/database.interface.dart';
import 'package:immich_mobile/models/albums/album_search.model.dart';
abstract interface class IAlbumRepository implements IDatabaseRepository {
Future<Album> create(Album album);
Future<Album?> get(int id);
Future<Album?> getByName(
String name, {
bool? shared,
bool? remote,
bool? owner,
});
Future<List<Album>> getAll({
bool? shared,
bool? remote,
int? ownerId,
AlbumSort? sortBy,
});
Future<Album> update(Album album);
Future<void> delete(int albumId);
Future<void> deleteAllLocal();
Future<int> count({bool? local});
Future<void> addUsers(Album album, List<UserDto> users);
Future<void> removeUsers(Album album, List<UserDto> users);
Future<void> addAssets(Album album, List<Asset> assets);
Future<void> removeAssets(Album album, List<Asset> assets);
Future<Album> recalculateMetadata(Album album);
Future<List<Album>> search(String searchTerm, QuickFilterMode filterMode);
Stream<List<Album>> watchRemoteAlbums();
Stream<List<Album>> watchLocalAlbums();
Stream<Album?> watchAlbum(int id);
Future<void> clearTable();
}
enum AlbumSort { remoteId, localId }

View File

@@ -1,42 +0,0 @@
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/entities/album.entity.dart';
abstract interface class IAlbumApiRepository {
Future<Album> get(String id);
Future<List<Album>> getAll({bool? shared});
Future<Album> create(
String name, {
required Iterable<String> assetIds,
Iterable<String> sharedUserIds = const [],
});
Future<Album> update(
String albumId, {
String? name,
String? thumbnailAssetId,
String? description,
bool? activityEnabled,
SortOrder? sortOrder,
});
Future<void> delete(String albumId);
Future<({List<String> added, List<String> duplicates})> addAssets(
String albumId,
Iterable<String> assetIds,
);
Future<({List<String> removed, List<String> failed})> removeAssets(
String albumId,
Iterable<String> assetIds,
);
Future<Album> addUsers(
String albumId,
Iterable<String> userIds,
);
Future<void> removeUser(String albumId, {required String userId});
}

View File

@@ -1,21 +0,0 @@
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
abstract interface class IAlbumMediaRepository {
Future<List<Album>> getAll();
Future<List<String>> getAssetIds(String albumId);
Future<int> getAssetCount(String albumId);
Future<List<Asset>> getAssets(
String albumId, {
int start = 0,
int end = 0x7fffffffffffffff,
DateTime? modifiedFrom,
DateTime? modifiedUntil,
bool orderByModificationDate = false,
});
Future<Album> get(String id);
}

View File

@@ -1,68 +0,0 @@
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/database.interface.dart';
abstract interface class IAssetRepository implements IDatabaseRepository {
Future<Asset?> getByRemoteId(String id);
Future<Asset?> getByOwnerIdChecksum(int ownerId, String checksum);
Future<List<Asset>> getAllByRemoteId(
Iterable<String> ids, {
AssetState? state,
});
Future<List<Asset?>> getAllByOwnerIdChecksum(
List<int> ids,
List<String> checksums,
);
Future<List<Asset>> getAll({
required String ownerId,
AssetState? state,
AssetSort? sortBy,
int? limit,
});
Future<List<Asset>> getAllLocal();
Future<List<Asset>> getByAlbum(
Album album, {
Iterable<String> notOwnedBy = const [],
String? ownerId,
AssetState? state,
AssetSort? sortBy,
});
Future<Asset> update(Asset asset);
Future<List<Asset>> updateAll(List<Asset> assets);
Future<void> deleteAllByRemoteId(List<String> ids, {AssetState? state});
Future<void> deleteByIds(List<int> ids);
Future<List<Asset>> getMatches({
required List<Asset> assets,
required String ownerId,
AssetState? state,
int limit = 100,
});
Future<void> upsertDuplicatedAssets(Iterable<String> duplicatedAssets);
Future<List<String>> getAllDuplicatedAssetIds();
Future<List<Asset>> getStackAssets(String stackId);
Future<void> clearTable();
Stream<Asset?> watchAsset(int id, {bool fireImmediately = false});
Future<List<Asset>> getTrashAssets(String userId);
Future<List<Asset>> getRecentlyTakenAssets(String userId);
Future<List<Asset>> getMotionAssets(String userId);
}
enum AssetSort { checksum, ownerIdChecksum }

View File

@@ -1,26 +0,0 @@
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
abstract interface class IAssetApiRepository {
// Future<Asset> get(String id);
// Future<List<Asset>> getAll();
// Future<Asset> create(Asset asset);
Future<Asset> update(
String id, {
String? description,
});
// Future<void> delete(String id);
Future<List<Asset>> search({List<String> personIds = const []});
Future<void> updateVisibility(
List<String> list,
AssetVisibilityEnum visibility,
);
Future<String?> getAssetMIMEType(String id);
}

View File

@@ -1,10 +0,0 @@
import 'package:immich_mobile/entities/asset.entity.dart';
abstract interface class IAssetMediaRepository {
Future<List<String>> deleteAll(List<String> ids);
Future<Asset?> get(String id);
/// Obtaining the correct original filename of the asset
Future<String?> getOriginalFilename(String id);
}

View File

@@ -1,11 +0,0 @@
import 'package:immich_mobile/interfaces/database.interface.dart';
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
abstract interface class IAuthRepository implements IDatabaseRepository {
Future<void> clearLocalData();
String getAccessToken();
bool getEndpointSwitchingFeature();
String? getPreferredWifiName();
String? getLocalEndpoint();
List<AuxilaryEndpoint> getExternalEndpointList();
}

View File

@@ -1,14 +0,0 @@
import 'package:immich_mobile/models/auth/login_response.model.dart';
abstract interface class IAuthApiRepository {
Future<LoginResponse> login(String email, String password);
Future<void> logout();
Future<void> changePassword(String newPassword);
Future<bool> unlockPinCode(String pinCode);
Future<void> lockPinCode();
Future<void> setupPinCode(String pinCode);
}

View File

@@ -1,16 +0,0 @@
import 'package:immich_mobile/entities/backup_album.entity.dart';
import 'package:immich_mobile/interfaces/database.interface.dart';
abstract interface class IBackupAlbumRepository implements IDatabaseRepository {
Future<List<BackupAlbum>> getAll({BackupAlbumSort? sort});
Future<List<String>> getIdsBySelection(BackupSelection backup);
Future<List<BackupAlbum>> getAllBySelection(BackupSelection backup);
Future<void> updateAll(List<BackupAlbum> backupAlbums);
Future<void> deleteAll(List<int> ids);
}
enum BackupAlbumSort { id }

View File

@@ -1,6 +0,0 @@
import 'package:immich_mobile/models/auth/biometric_status.model.dart';
abstract interface class IBiometricRepository {
Future<BiometricStatus> getStatus();
Future<bool> authenticate(String? message);
}

View File

@@ -1,27 +0,0 @@
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/models/cast/cast_manager_state.dart';
abstract interface class ICastDestinationService {
Future<bool> initialize();
CastDestinationType getType();
void Function(bool)? onConnectionState;
void Function(Duration)? onCurrentTime;
void Function(Duration)? onDuration;
void Function(String)? onReceiverName;
void Function(CastState)? onCastState;
Future<void> connect(dynamic device);
void loadMedia(Asset asset, bool reload);
void play();
void pause();
void seekTo(Duration position);
void stop();
Future<void> disconnect();
Future<List<(String, CastDestinationType, dynamic)>> getDevices();
}

View File

@@ -1,15 +0,0 @@
import 'package:background_downloader/background_downloader.dart';
abstract interface class IDownloadRepository {
void Function(TaskStatusUpdate)? onImageDownloadStatus;
void Function(TaskStatusUpdate)? onVideoDownloadStatus;
void Function(TaskStatusUpdate)? onLivePhotoDownloadStatus;
void Function(TaskProgressUpdate)? onTaskProgress;
Future<List<TaskRecord>> getLiveVideoTasks();
Future<List<bool>> downloadAll(List<DownloadTask> tasks);
Future<bool> cancel(String id);
Future<void> deleteAllTrackingRecords();
Future<void> deleteRecordsWithIds(List<String> id);
}

View File

@@ -1,16 +0,0 @@
import 'package:immich_mobile/entities/etag.entity.dart';
import 'package:immich_mobile/interfaces/database.interface.dart';
abstract interface class IETagRepository implements IDatabaseRepository {
Future<ETag?> get(String id);
Future<ETag?> getById(String id);
Future<List<String>> getAllIds();
Future<void> upsertAll(List<ETag> etags);
Future<void> deleteByIds(List<String> ids);
Future<void> clearTable();
}

View File

@@ -1,36 +0,0 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:immich_mobile/entities/asset.entity.dart';
abstract interface class IFileMediaRepository {
Future<Asset?> saveImage(
Uint8List data, {
required String title,
String? relativePath,
});
Future<Asset?> saveImageWithFile(
String filePath, {
String? title,
String? relativePath,
});
Future<Asset?> saveVideo(
File file, {
required String title,
String? relativePath,
});
Future<Asset?> saveLivePhoto({
required File image,
required File video,
required String title,
});
Future<void> clearFileCache();
Future<void> enableBackgroundAccess();
Future<void> requestExtendedPermissions();
}

View File

@@ -1,6 +0,0 @@
import 'package:immich_mobile/entities/asset.entity.dart';
abstract interface class IFolderApiRepository {
Future<List<String>> getAllUniquePaths();
Future<List<Asset>> getAssetsForPath(String? path);
}

View File

@@ -1,5 +0,0 @@
abstract interface class ILocalFilesManager {
Future<bool> moveToTrash(List<String> mediaUrls);
Future<bool> restoreFromTrash(String fileName, int type);
Future<bool> requestManageMediaPermission();
}

View File

@@ -1,4 +0,0 @@
abstract interface class INetworkRepository {
Future<String?> getWifiName();
Future<String?> getWifiIp();
}

View File

@@ -1,8 +0,0 @@
import 'package:immich_mobile/domain/models/user.model.dart';
abstract class IPartnerRepository {
Future<List<UserDto>> getSharedWith();
Future<List<UserDto>> getSharedBy();
Stream<List<UserDto>> watchSharedWith();
Stream<List<UserDto>> watchSharedBy();
}

View File

@@ -1,13 +0,0 @@
import 'package:immich_mobile/domain/models/user.model.dart';
abstract interface class IPartnerApiRepository {
Future<List<UserDto>> getAll(Direction direction);
Future<UserDto> create(String id);
Future<UserDto> update(String id, {required bool inTimeline});
Future<void> delete(String id);
}
enum Direction {
sharedWithMe,
sharedByMe,
}

View File

@@ -1,5 +0,0 @@
abstract interface class ISecureStorageRepository {
Future<String?> read(String key);
Future<void> write(String key, String value);
Future<void> delete(String key);
}

View File

@@ -1,9 +0,0 @@
import 'package:immich_mobile/models/sessions/session_create_response.model.dart';
abstract interface class ISessionAPIRepository {
Future<SessionCreateResponse> createSession(
String deviceName,
String deviceOS, {
int? duration,
});
}

View File

@@ -1,7 +0,0 @@
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
abstract interface class IShareHandlerRepository {
void Function(List<ShareIntentAttachment>)? onSharedMedia;
Future<void> init();
}

View File

@@ -1,39 +0,0 @@
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
abstract class ITimelineRepository {
Future<List<String>> getTimelineUserIds(String id);
Stream<List<String>> watchTimelineUsers(String id);
Stream<RenderList> watchArchiveTimeline(String userId);
Stream<RenderList> watchFavoriteTimeline(String userId);
Stream<RenderList> watchTrashTimeline(String userId);
Stream<RenderList> watchAlbumTimeline(
Album album,
GroupAssetsBy groupAssetsBy,
);
Stream<RenderList> watchAllVideosTimeline(String userId);
Stream<RenderList> watchHomeTimeline(
String userId,
GroupAssetsBy groupAssetsBy,
);
Stream<RenderList> watchMultiUsersTimeline(
List<String> userIds,
GroupAssetsBy groupAssetsBy,
);
Future<RenderList> getTimelineFromAssets(
List<Asset> assets,
GroupAssetsBy getGroupByOption,
);
Stream<RenderList> watchAssetSelectionTimeline(String userId);
Stream<RenderList> watchLockedTimeline(
String userId,
GroupAssetsBy groupAssetsBy,
);
}

View File

@@ -1,11 +0,0 @@
import 'package:background_downloader/background_downloader.dart';
abstract interface class IUploadRepository {
void Function(TaskStatusUpdate)? onUploadStatus;
void Function(TaskProgressUpdate)? onTaskProgress;
Future<bool> upload(UploadTask task);
Future<bool> cancel(String id);
Future<void> deleteAllTrackingRecords();
Future<void> deleteRecordsWithIds(List<String> id);
}

View File

@@ -1,5 +0,0 @@
abstract interface class IWidgetRepository {
Future<void> saveData(String key, String value);
Future<void> refresh(String name);
Future<void> setAppGroupId(String appGroupId);
}

View File

@@ -1,8 +1,8 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first // ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert'; import 'dart:convert';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
class SearchLocationFilter { class SearchLocationFilter {
String? country; String? country;

View File

@@ -6,9 +6,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/providers/search/paginated_search.provider.dart'; import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'; import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';

View File

@@ -4,14 +4,13 @@ import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:immich_mobile/domain/interfaces/asset_media.interface.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/asset_media.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/asset_media.repository.dart';
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart'; import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
import 'package:immich_mobile/providers/image/cache/thumbnail_image_cache_manager.dart'; import 'package:immich_mobile/providers/image/cache/thumbnail_image_cache_manager.dart';
class LocalThumbProvider extends ImageProvider<LocalThumbProvider> { class LocalThumbProvider extends ImageProvider<LocalThumbProvider> {
final IAssetMediaRepository _assetMediaRepository = final AssetMediaRepository _assetMediaRepository =
const AssetMediaRepository(); const AssetMediaRepository();
final CacheManager? cacheManager; final CacheManager? cacheManager;

View File

@@ -118,10 +118,8 @@ class AuthNotifier extends StateNotifier<AuthState> {
}) async { }) async {
await _apiService.setAccessToken(accessToken); await _apiService.setAccessToken(accessToken);
await _widgetService.writeCredentials( await _widgetService.writeSessionKey(accessToken);
Store.get(StoreKey.serverEndpoint), await _widgetService.writeServerList();
accessToken,
);
// Get the deviceid from the store if it exists, otherwise generate a new one // Get the deviceid from the store if it exists, otherwise generate a new one
String deviceId = String deviceId =
@@ -190,6 +188,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
Future<void> saveLocalEndpoint(String url) async { Future<void> saveLocalEndpoint(String url) async {
await Store.put(StoreKey.localEndpoint, url); await Store.put(StoreKey.localEndpoint, url);
await _widgetService.writeServerList();
} }
String? getSavedWifiName() { String? getSavedWifiName() {

View File

@@ -9,9 +9,6 @@ import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/backup_album.interface.dart';
import 'package:immich_mobile/interfaces/file_media.interface.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/backup/available_album.model.dart'; import 'package:immich_mobile/models/backup/available_album.model.dart';
import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
@@ -25,6 +22,7 @@ import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart';
import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/repositories/album_media.repository.dart'; import 'package:immich_mobile/repositories/album_media.repository.dart';
import 'package:immich_mobile/repositories/backup.repository.dart';
import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/services/background.service.dart'; import 'package:immich_mobile/services/background.service.dart';
import 'package:immich_mobile/services/backup.service.dart'; import 'package:immich_mobile/services/backup.service.dart';
@@ -108,8 +106,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
final AuthState _authState; final AuthState _authState;
final BackgroundService _backgroundService; final BackgroundService _backgroundService;
final GalleryPermissionNotifier _galleryPermissionNotifier; final GalleryPermissionNotifier _galleryPermissionNotifier;
final IAlbumMediaRepository _albumMediaRepository; final AlbumMediaRepository _albumMediaRepository;
final IFileMediaRepository _fileMediaRepository; final FileMediaRepository _fileMediaRepository;
final BackupAlbumService _backupAlbumService; final BackupAlbumService _backupAlbumService;
final Ref ref; final Ref ref;

View File

@@ -1,6 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/cast_destination_service.interface.dart';
import 'package:immich_mobile/models/cast/cast_manager_state.dart'; import 'package:immich_mobile/models/cast/cast_manager_state.dart';
import 'package:immich_mobile/services/gcast.service.dart'; import 'package:immich_mobile/services/gcast.service.dart';
@@ -10,7 +9,7 @@ final castProvider = StateNotifierProvider<CastNotifier, CastManagerState>(
class CastNotifier extends StateNotifier<CastManagerState> { class CastNotifier extends StateNotifier<CastManagerState> {
// more cast providers can be added here (ie Fcast) // more cast providers can be added here (ie Fcast)
final ICastDestinationService _gCastService; final GCastService _gCastService;
List<(String, CastDestinationType, dynamic)> discovered = List.empty(); List<(String, CastDestinationType, dynamic)> discovered = List.empty();

View File

@@ -1,8 +1,7 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/interfaces/local_album.interface.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
final localAlbumRepository = Provider<ILocalAlbumRepository>( final localAlbumRepository = Provider<DriftLocalAlbumRepository>(
(ref) => DriftLocalAlbumRepository(ref.watch(driftProvider)), (ref) => DriftLocalAlbumRepository(ref.watch(driftProvider)),
); );

View File

@@ -1,8 +1,7 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/interfaces/local_asset.interface.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/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
final localAssetRepository = Provider<ILocalAssetRepository>( final localAssetRepository = Provider<DriftLocalAssetRepository>(
(ref) => DriftLocalAssetRepository(ref.watch(driftProvider)), (ref) => DriftLocalAssetRepository(ref.watch(driftProvider)),
); );

View File

@@ -1,8 +1,7 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/interfaces/device_asset.interface.dart';
import 'package:immich_mobile/infrastructure/repositories/device_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/device_asset.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
final deviceAssetRepositoryProvider = Provider<IDeviceAssetRepository>( final deviceAssetRepositoryProvider = Provider<IsarDeviceAssetRepository>(
(ref) => IsarDeviceAssetRepository(ref.watch(isarProvider)), (ref) => IsarDeviceAssetRepository(ref.watch(isarProvider)),
); );

View File

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

View File

@@ -6,11 +6,11 @@ part of 'exif.provider.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$exifRepositoryHash() => r'f0abe778ed61fbb257001fdf2ac6e17814011fee'; String _$exifRepositoryHash() => r'bf4a3f6a50d954a23d317659b4f3e2f381066463';
/// See also [exifRepository]. /// See also [exifRepository].
@ProviderFor(exifRepository) @ProviderFor(exifRepository)
final exifRepositoryProvider = Provider<IExifInfoRepository>.internal( final exifRepositoryProvider = Provider<IsarExifRepository>.internal(
exifRepository, exifRepository,
name: r'exifRepositoryProvider', name: r'exifRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
@@ -22,6 +22,6 @@ final exifRepositoryProvider = Provider<IExifInfoRepository>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef ExifRepositoryRef = ProviderRef<IExifInfoRepository>; typedef ExifRepositoryRef = ProviderRef<IsarExifRepository>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -1,7 +1,6 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/interfaces/storage.interface.dart';
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
final storageRepositoryProvider = Provider<IStorageRepository>( final storageRepositoryProvider = Provider<StorageRepository>(
(ref) => StorageRepository(), (ref) => StorageRepository(),
); );

View File

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

View File

@@ -6,11 +6,11 @@ part of 'store.provider.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$storeRepositoryHash() => r'99d24875d30c5e86b1c6caa352a0026167114e62'; String _$storeRepositoryHash() => r'659cb134466e4b0d5f04e2fc93e426350d99545f';
/// See also [storeRepository]. /// See also [storeRepository].
@ProviderFor(storeRepository) @ProviderFor(storeRepository)
final storeRepositoryProvider = Provider<IStoreRepository>.internal( final storeRepositoryProvider = Provider<IsarStoreRepository>.internal(
storeRepository, storeRepository,
name: r'storeRepositoryProvider', name: r'storeRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
@@ -22,7 +22,7 @@ final storeRepositoryProvider = Provider<IStoreRepository>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef StoreRepositoryRef = ProviderRef<IStoreRepository>; typedef StoreRepositoryRef = ProviderRef<IsarStoreRepository>;
String _$storeServiceHash() => r'250e10497c42df360e9e1f9a618d0b19c1b5b0a0'; String _$storeServiceHash() => r'250e10497c42df360e9e1f9a618d0b19c1b5b0a0';
/// See also [storeService]. /// See also [storeService].

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