mirror of
https://github.com/immich-app/immich.git
synced 2026-02-03 18:48:01 -08:00
Compare commits
1 Commits
feat/widge
...
fix/map-pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c7a29b2f4 |
@@ -55,7 +55,7 @@
|
||||
"userEnvProbe": "loginInteractiveShell",
|
||||
"remoteEnv": {
|
||||
// The location where your uploaded files are stored
|
||||
"UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./library}",
|
||||
"UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:upload-devcontainer-volume}",
|
||||
// Connection secret for postgres. You should change it to a random password
|
||||
// Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
||||
"DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}",
|
||||
|
||||
@@ -51,19 +51,14 @@ 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 {} +
|
||||
|
||||
# Change ownership for directories that exist
|
||||
for dir in "${IMMICH_WORKSPACE}/.vscode" \
|
||||
run_cmd sudo chown node -R "${IMMICH_WORKSPACE}/.vscode" \
|
||||
"${IMMICH_WORKSPACE}/cli/node_modules" \
|
||||
"${IMMICH_WORKSPACE}/e2e/node_modules" \
|
||||
"${IMMICH_WORKSPACE}/open-api/typescript-sdk/node_modules" \
|
||||
"${IMMICH_WORKSPACE}/server/node_modules" \
|
||||
"${IMMICH_WORKSPACE}/server/dist" \
|
||||
"${IMMICH_WORKSPACE}/web/node_modules" \
|
||||
"${IMMICH_WORKSPACE}/web/dist"; do
|
||||
if [ -d "$dir" ]; then
|
||||
run_cmd sudo chown node -R "$dir"
|
||||
fi
|
||||
done
|
||||
"${IMMICH_WORKSPACE}/web/dist"
|
||||
|
||||
log ""
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ services:
|
||||
- open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules
|
||||
- server_node_modules:/workspaces/immich/server/node_modules
|
||||
- web_node_modules:/workspaces/immich/web/node_modules
|
||||
- ${UPLOAD_LOCATION:-upload1-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/workspaces/immich/server/upload
|
||||
- ${UPLOAD_LOCATION:-upload2-devcontainer-volume}${UPLOAD_LOCATION:+/photos/upload}:/workspaces/immich/server/upload/upload
|
||||
- ${UPLOAD_LOCATION-./Library}/photos:/workspaces/immich/server/upload
|
||||
- ${UPLOAD_LOCATION-./Library}/photos/upload:/workspaces/immich/server/upload/upload
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
|
||||
immich-web:
|
||||
@@ -29,9 +29,8 @@ services:
|
||||
POSTGRES_USER: ${DB_USERNAME-postgres}
|
||||
POSTGRES_DB: ${DB_DATABASE_NAME-immich}
|
||||
POSTGRES_INITDB_ARGS: '--data-checksums'
|
||||
POSTGRES_HOST_AUTH_METHOD: md5
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION:-postgres-devcontainer-volume}${UPLOAD_LOCATION:+/postgres}:/var/lib/postgresql/data
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION-./Library}/postgres:/var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
env_file: !reset []
|
||||
@@ -43,6 +42,4 @@ volumes:
|
||||
open_api_node_modules:
|
||||
server_node_modules:
|
||||
web_node_modules:
|
||||
upload1-devcontainer-volume:
|
||||
upload2-devcontainer-volume:
|
||||
postgres-devcontainer-volume:
|
||||
upload-devcontainer-volume:
|
||||
|
||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -461,7 +461,7 @@ jobs:
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install chromium --only-shell
|
||||
run: npx playwright install --with-deps chromium
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Docker build
|
||||
@@ -644,7 +644,7 @@ jobs:
|
||||
contents: read
|
||||
services:
|
||||
postgres:
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
|
||||
6
cli/package-lock.json
generated
6
cli/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.72",
|
||||
"version": "2.2.69",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.72",
|
||||
"version": "2.2.69",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.3",
|
||||
@@ -54,7 +54,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.135.3",
|
||||
"version": "1.135.0",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.72",
|
||||
"version": "2.2.69",
|
||||
"description": "Command Line Interface (CLI) for Immich",
|
||||
"type": "module",
|
||||
"exports": "./dist/index.js",
|
||||
|
||||
@@ -16,7 +16,7 @@ name: immich-dev
|
||||
services:
|
||||
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
|
||||
# extends:
|
||||
# file: hwaccel.transcoding.yml
|
||||
@@ -70,7 +70,7 @@ services:
|
||||
# user: 0:0
|
||||
build:
|
||||
context: ../web
|
||||
command: ['/usr/src/app/bin/immich-web']
|
||||
command: [ '/usr/src/app/bin/immich-web' ]
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
@@ -122,7 +122,7 @@ services:
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
|
||||
@@ -63,7 +63,7 @@ services:
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
@@ -91,7 +91,7 @@ services:
|
||||
# add data source for http://immich-prometheus:9090 to get started
|
||||
immich-grafana:
|
||||
container_name: immich_grafana
|
||||
command: ['./run.sh', '-disable-reporting']
|
||||
command: [ './run.sh', '-disable-reporting' ]
|
||||
ports:
|
||||
- 3000:3000
|
||||
image: grafana/grafana:12.0.1-ubuntu@sha256:65575bb9c761335e2ff30e364f21d38632e3b2e75f5f81d83cc92f44b9bbc055
|
||||
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_USER: ${DB_USERNAME}
|
||||
|
||||
@@ -64,13 +64,7 @@ COMMIT;
|
||||
|
||||
### Updating VectorChord
|
||||
|
||||
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;
|
||||
```
|
||||
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;`.
|
||||
|
||||
## Migrating to VectorChord
|
||||
|
||||
@@ -82,8 +76,6 @@ 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:
|
||||
|
||||
<details>
|
||||
<summary>Migration steps (automatic)</summary>
|
||||
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`)
|
||||
3. [Install VectorChord][vchord-install]
|
||||
@@ -97,12 +89,8 @@ The easiest option is to have both extensions installed during the migration:
|
||||
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`
|
||||
|
||||
</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:
|
||||
|
||||
<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
|
||||
|
||||
```sql
|
||||
@@ -135,20 +123,14 @@ ALTER TABLE face_search ALTER COLUMN embedding SET DATA TYPE vector(512);
|
||||
|
||||
5. Start Immich and let it create new indices using VectorChord
|
||||
|
||||
</details>
|
||||
|
||||
### 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
|
||||
2. Follow the Prerequisites to install VectorChord
|
||||
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
|
||||
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.
|
||||
|
||||
[vchord-install]: https://docs.vectorchord.ai/vectorchord/getting-started/installation.html
|
||||
|
||||
@@ -39,8 +39,8 @@ alt="Dot Env Example"
|
||||
/>
|
||||
|
||||
- Change the default `DB_PASSWORD`, and add custom database connection information if necessary.
|
||||
- Change `DB_DATA_LOCATION` to a folder (absolute path) where the database will be saved to disk.
|
||||
- Change `UPLOAD_LOCATION` to a folder (absolute path) where media (uploaded and generated) will be stored.
|
||||
- Change `DB_DATA_LOCATION` to a folder where the database will be saved to disk.
|
||||
- Change `UPLOAD_LOCATION` to a folder where media (uploaded and generated) will be stored.
|
||||
|
||||
11. Click on "**Deploy the stack**".
|
||||
|
||||
|
||||
12
docs/static/archived-versions.json
vendored
12
docs/static/archived-versions.json
vendored
@@ -1,16 +1,4 @@
|
||||
[
|
||||
{
|
||||
"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",
|
||||
"url": "https://v1.135.0.archive.immich.app"
|
||||
|
||||
8
e2e/package-lock.json
generated
8
e2e/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.135.3",
|
||||
"version": "1.135.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich-e2e",
|
||||
"version": "1.135.3",
|
||||
"version": "1.135.0",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
@@ -44,7 +44,7 @@
|
||||
},
|
||||
"../cli": {
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.72",
|
||||
"version": "2.2.69",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
@@ -93,7 +93,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.135.3",
|
||||
"version": "1.135.0",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.135.3",
|
||||
"version": "1.135.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -14,11 +14,7 @@ describe('/people', () => {
|
||||
let nameAlicePerson: PersonResponseDto;
|
||||
let nameBobPerson: PersonResponseDto;
|
||||
let nameCharliePerson: PersonResponseDto;
|
||||
let nameNullPerson4Assets: PersonResponseDto;
|
||||
let nameNullPerson3Assets: PersonResponseDto;
|
||||
let nameNullPerson1Asset: PersonResponseDto;
|
||||
let nameBillPersonFavourite: PersonResponseDto;
|
||||
let nameFreddyPersonFavourite: PersonResponseDto;
|
||||
let nameNullPerson: PersonResponseDto;
|
||||
|
||||
beforeAll(async () => {
|
||||
await utils.resetDatabase();
|
||||
@@ -31,11 +27,7 @@ describe('/people', () => {
|
||||
nameCharliePerson,
|
||||
nameBobPerson,
|
||||
nameAlicePerson,
|
||||
nameNullPerson4Assets,
|
||||
nameNullPerson3Assets,
|
||||
nameNullPerson1Asset,
|
||||
nameBillPersonFavourite,
|
||||
nameFreddyPersonFavourite,
|
||||
nameNullPerson,
|
||||
] = await Promise.all([
|
||||
utils.createPerson(admin.accessToken, {
|
||||
name: 'visible_person',
|
||||
@@ -60,26 +52,11 @@ describe('/people', () => {
|
||||
utils.createPerson(admin.accessToken, {
|
||||
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 asset2 = await utils.createAsset(admin.accessToken);
|
||||
const asset3 = await utils.createAsset(admin.accessToken);
|
||||
const asset4 = await utils.createAsset(admin.accessToken);
|
||||
|
||||
await Promise.all([
|
||||
utils.createFace({ assetId: asset1.id, personId: visiblePerson.id }),
|
||||
@@ -87,27 +64,15 @@ describe('/people', () => {
|
||||
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: asset3.id, personId: multipleAssetsPerson.id }), // 4 assets
|
||||
utils.createFace({ assetId: asset3.id, personId: multipleAssetsPerson.id }),
|
||||
// Named persons
|
||||
utils.createFace({ assetId: asset1.id, personId: nameCharliePerson.id }), // 1 asset
|
||||
utils.createFace({ assetId: asset1.id, personId: nameBobPerson.id }),
|
||||
utils.createFace({ assetId: asset2.id, personId: nameBobPerson.id }), // 2 assets
|
||||
utils.createFace({ assetId: asset1.id, personId: nameAlicePerson.id }), // 1 asset
|
||||
// Null-named person 4 assets
|
||||
utils.createFace({ assetId: asset1.id, personId: nameNullPerson4Assets.id }),
|
||||
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 }),
|
||||
// Null-named person
|
||||
utils.createFace({ assetId: asset1.id, personId: nameNullPerson.id }),
|
||||
utils.createFace({ assetId: asset2.id, personId: nameNullPerson.id }), // 2 assets
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -122,19 +87,15 @@ describe('/people', () => {
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual({
|
||||
hasNextPage: false,
|
||||
total: 11,
|
||||
total: 7,
|
||||
hidden: 1,
|
||||
people: [
|
||||
expect.objectContaining({ name: 'Freddy' }),
|
||||
expect.objectContaining({ name: 'Bill' }),
|
||||
expect.objectContaining({ name: 'multiple_assets_person' }),
|
||||
expect.objectContaining({ name: 'Bob' }),
|
||||
expect.objectContaining({ name: 'Alice' }),
|
||||
expect.objectContaining({ name: 'Charlie' }),
|
||||
expect.objectContaining({ name: 'visible_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
|
||||
expect.objectContaining({ name: 'hidden_person' }),
|
||||
],
|
||||
});
|
||||
});
|
||||
@@ -144,21 +105,17 @@ describe('/people', () => {
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body.hasNextPage).toBe(false);
|
||||
expect(body.total).toBe(11); // All persons
|
||||
expect(body.total).toBe(7); // All persons
|
||||
expect(body.hidden).toBe(1); // 'hidden_person'
|
||||
|
||||
const people = body.people as PersonResponseDto[];
|
||||
|
||||
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
|
||||
nameBobPerson.id, // name: 'Bob', count: 2
|
||||
nameAlicePerson.id, // name: 'Alice', count: 1
|
||||
nameCharliePerson.id, // name: 'Charlie', 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);
|
||||
@@ -170,18 +127,14 @@ describe('/people', () => {
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual({
|
||||
hasNextPage: false,
|
||||
total: 11,
|
||||
total: 7,
|
||||
hidden: 1,
|
||||
people: [
|
||||
expect.objectContaining({ name: 'Freddy' }),
|
||||
expect.objectContaining({ name: 'Bill' }),
|
||||
expect.objectContaining({ name: 'multiple_assets_person' }),
|
||||
expect.objectContaining({ name: 'Bob' }),
|
||||
expect.objectContaining({ name: 'Alice' }),
|
||||
expect.objectContaining({ name: 'Charlie' }),
|
||||
expect.objectContaining({ name: 'visible_person' }),
|
||||
expect.objectContaining({ id: nameNullPerson4Assets.id, name: '' }),
|
||||
expect.objectContaining({ id: nameNullPerson3Assets.id, name: '' }),
|
||||
],
|
||||
});
|
||||
});
|
||||
@@ -195,9 +148,9 @@ describe('/people', () => {
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual({
|
||||
hasNextPage: true,
|
||||
total: 11,
|
||||
total: 7,
|
||||
hidden: 1,
|
||||
people: [expect.objectContaining({ name: 'Alice' })],
|
||||
people: [expect.objectContaining({ name: 'visible_person' })],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -464,6 +464,7 @@
|
||||
"assets": "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_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_count": "{count, plural, one {# asset} other {# assets}}",
|
||||
"assets_deleted_permanently": "{count} asset(s) deleted permanently",
|
||||
|
||||
@@ -244,6 +244,7 @@ async def load(model: InferenceModel) -> InferenceModel:
|
||||
|
||||
async def idle_shutdown_task() -> None:
|
||||
while True:
|
||||
log.debug("Checking for inactivity...")
|
||||
if (
|
||||
last_called is not None
|
||||
and not active_requests
|
||||
|
||||
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 204,
|
||||
"android.injected.version.name" => "1.135.3",
|
||||
"android.injected.version.code" => 201,
|
||||
"android.injected.version.name" => "1.135.0",
|
||||
}
|
||||
)
|
||||
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')
|
||||
|
||||
@@ -346,7 +346,6 @@
|
||||
};
|
||||
F0B57D372DF764BD00DC5BCC = {
|
||||
CreatedOnToolsVersion = 16.4;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
FAC6F88F2D287C890078CB2F = {
|
||||
CreatedOnToolsVersion = 16.0;
|
||||
@@ -649,7 +648,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 210;
|
||||
CURRENT_PROJECT_VERSION = 208;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -793,7 +792,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 210;
|
||||
CURRENT_PROJECT_VERSION = 208;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -823,7 +822,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 210;
|
||||
CURRENT_PROJECT_VERSION = 208;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -857,7 +856,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 210;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -865,7 +864,7 @@
|
||||
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Widget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -900,7 +899,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 210;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -908,7 +907,7 @@
|
||||
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Widget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -940,7 +939,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 210;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -948,7 +947,7 @@
|
||||
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Widget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -979,7 +978,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 210;
|
||||
CURRENT_PROJECT_VERSION = 208;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -1023,7 +1022,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 210;
|
||||
CURRENT_PROJECT_VERSION = 208;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -1064,7 +1063,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 210;
|
||||
CURRENT_PROJECT_VERSION = 208;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.135.1</string>
|
||||
<string>1.134.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@@ -93,7 +93,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>210</string>
|
||||
<string>208</string>
|
||||
<key>FLTEnableImpeller</key>
|
||||
<true />
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
@@ -115,8 +115,8 @@
|
||||
</dict>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_googlecast._tcp</string>
|
||||
<string>_CC1AD845._googlecast._tcp</string>
|
||||
<string>_googlecast._tcp</string>
|
||||
<string>_CC1AD845._googlecast._tcp</string>
|
||||
</array>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>We need to access the camera to let you take beautiful video using this app</string>
|
||||
@@ -168,8 +168,5 @@
|
||||
<true />
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<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>
|
||||
</plist>
|
||||
</plist>
|
||||
|
||||
@@ -17,14 +17,6 @@ enum AssetType: String, Codable {
|
||||
case other = "OTHER"
|
||||
}
|
||||
|
||||
struct ServerWellKnown: Codable {
|
||||
struct APIInfo: Codable{
|
||||
let endpoint: String
|
||||
}
|
||||
|
||||
let api: APIInfo
|
||||
}
|
||||
|
||||
struct SearchResult: Codable {
|
||||
let id: String
|
||||
let type: AssetType
|
||||
@@ -65,7 +57,7 @@ class ImmichAPI {
|
||||
init() async throws {
|
||||
// fetch the credentials from the UserDefaults store that dart placed here
|
||||
guard let defaults = UserDefaults(suiteName: "group.app.immich.share"),
|
||||
var serverURL = defaults.string(forKey: "widget_server_url"),
|
||||
let serverURL = defaults.string(forKey: "widget_server_url"),
|
||||
let sessionKey = defaults.string(forKey: "widget_auth_token")
|
||||
else {
|
||||
throw WidgetError.noLogin
|
||||
@@ -74,55 +66,13 @@ class ImmichAPI {
|
||||
if serverURL == "" || sessionKey == "" {
|
||||
throw WidgetError.noLogin
|
||||
}
|
||||
|
||||
// migrate the server list value to a JSON array if it is not already
|
||||
if !serverURL.starts(with: "[") {
|
||||
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
|
||||
|
||||
serverConfig = ServerConfig(
|
||||
serverEndpoint: serverURL,
|
||||
sessionKey: sessionKey
|
||||
)
|
||||
}
|
||||
|
||||
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(
|
||||
serverConfig: ServerConfig,
|
||||
endpoint: String,
|
||||
|
||||
@@ -20,11 +20,8 @@ struct ImmichMemoryProvider: TimelineProvider {
|
||||
completion: @escaping @Sendable (ImageEntry) -> Void
|
||||
) {
|
||||
Task {
|
||||
var api: ImmichAPI
|
||||
do {
|
||||
api = try await ImmichAPI()
|
||||
} catch let error as WidgetError {
|
||||
completion(ImageEntry(date: Date(), image: nil, error: error))
|
||||
guard let api = try? await ImmichAPI() else {
|
||||
completion(ImageEntry(date: Date(), image: nil, error: .noLogin))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -82,13 +79,9 @@ struct ImmichMemoryProvider: TimelineProvider {
|
||||
Task {
|
||||
var entries: [ImageEntry] = []
|
||||
let now = Date()
|
||||
|
||||
|
||||
var api: ImmichAPI
|
||||
do {
|
||||
api = try await ImmichAPI()
|
||||
} catch let error as WidgetError {
|
||||
entries.append(ImageEntry(date: now, image: nil, error: error))
|
||||
|
||||
guard let api = try? await ImmichAPI() else {
|
||||
entries.append(ImageEntry(date: now, image: nil, error: .noLogin))
|
||||
completion(Timeline(entries: entries, policy: .atEnd))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ struct RandomConfigurationAppIntent: WidgetConfigurationIntent {
|
||||
"Choose an album to show images from"
|
||||
}
|
||||
|
||||
@Parameter(title: "Album")
|
||||
@Parameter(title: "Album", default: NO_ALBUM)
|
||||
var album: Album?
|
||||
|
||||
@Parameter(title: "Show Album Name", default: false)
|
||||
@@ -63,15 +63,10 @@ struct ImmichRandomProvider: AppIntentTimelineProvider {
|
||||
) async
|
||||
-> ImageEntry
|
||||
{
|
||||
var api: ImmichAPI
|
||||
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 let api = try? await ImmichAPI() else {
|
||||
return ImageEntry(date: Date(), image: nil, error: .noLogin)
|
||||
}
|
||||
|
||||
|
||||
guard
|
||||
let randomImage = try? await api.fetchSearchResults(
|
||||
with: SearchFilters(size: 1)
|
||||
@@ -105,21 +100,15 @@ struct ImmichRandomProvider: AppIntentTimelineProvider {
|
||||
let now = Date()
|
||||
|
||||
// If we don't have a server config, return an entry with an error
|
||||
var api: ImmichAPI
|
||||
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))
|
||||
guard let api = try? await ImmichAPI() else {
|
||||
entries.append(ImageEntry(date: now, image: nil, error: .noLogin))
|
||||
return Timeline(entries: entries, policy: .atEnd)
|
||||
}
|
||||
|
||||
// nil if album is NONE or nil
|
||||
let albumId =
|
||||
configuration.album?.id != "NONE" ? configuration.album?.id : nil
|
||||
let albumName: String? = albumId != nil ? configuration.album?.albumName : nil
|
||||
var albumName: String? = albumId != nil ? configuration.album?.albumName : nil
|
||||
|
||||
if albumId != nil {
|
||||
// make sure the album exists on server, otherwise show error
|
||||
|
||||
@@ -22,7 +22,7 @@ platform :ios do
|
||||
path: "./Runner.xcodeproj",
|
||||
)
|
||||
increment_version_number(
|
||||
version_number: "1.135.3"
|
||||
version_number: "1.135.0"
|
||||
)
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number + 1,
|
||||
|
||||
@@ -10,5 +10,3 @@ enum TextSearchType {
|
||||
}
|
||||
|
||||
enum AssetVisibilityEnum { timeline, hidden, archive, locked }
|
||||
|
||||
enum SortUserBy { id }
|
||||
|
||||
@@ -11,7 +11,6 @@ const Map<String, Locale> locales = {
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'SIMPLIFIED'),
|
||||
'Chinese Traditional (zh_TW)':
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'),
|
||||
'Croatian (hr)': Locale('hr'),
|
||||
'Czech (cs)': Locale('cs'),
|
||||
'Danish (da)': Locale('da'),
|
||||
'Dutch (nl)': Locale('nl'),
|
||||
|
||||
10
mobile/lib/domain/interfaces/asset_media.interface.dart
Normal file
10
mobile/lib/domain/interfaces/asset_media.interface.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
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),
|
||||
});
|
||||
}
|
||||
12
mobile/lib/domain/interfaces/device_asset.interface.dart
Normal file
12
mobile/lib/domain/interfaces/device_asset.interface.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
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);
|
||||
}
|
||||
14
mobile/lib/domain/interfaces/exif.interface.dart
Normal file
14
mobile/lib/domain/interfaces/exif.interface.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
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();
|
||||
}
|
||||
33
mobile/lib/domain/interfaces/local_album.interface.dart
Normal file
33
mobile/lib/domain/interfaces/local_album.interface.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
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 }
|
||||
6
mobile/lib/domain/interfaces/local_asset.interface.dart
Normal file
6
mobile/lib/domain/interfaces/local_asset.interface.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
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);
|
||||
}
|
||||
17
mobile/lib/domain/interfaces/log.interface.dart
Normal file
17
mobile/lib/domain/interfaces/log.interface.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
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});
|
||||
}
|
||||
7
mobile/lib/domain/interfaces/storage.interface.dart
Normal file
7
mobile/lib/domain/interfaces/storage.interface.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
|
||||
abstract interface class IStorageRepository {
|
||||
Future<File?> getFileForAsset(LocalAsset asset);
|
||||
}
|
||||
20
mobile/lib/domain/interfaces/store.interface.dart
Normal file
20
mobile/lib/domain/interfaces/store.interface.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
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();
|
||||
}
|
||||
12
mobile/lib/domain/interfaces/sync_api.interface.dart
Normal file
12
mobile/lib/domain/interfaces/sync_api.interface.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
18
mobile/lib/domain/interfaces/sync_stream.interface.dart
Normal file
18
mobile/lib/domain/interfaces/sync_stream.interface.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
}
|
||||
27
mobile/lib/domain/interfaces/timeline.interface.dart
Normal file
27
mobile/lib/domain/interfaces/timeline.interface.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
22
mobile/lib/domain/interfaces/user.interface.dart
Normal file
22
mobile/lib/domain/interfaces/user.interface.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
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 }
|
||||
15
mobile/lib/domain/interfaces/user_api.interface.dart
Normal file
15
mobile/lib/domain/interfaces/user_api.interface.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:convert';
|
||||
|
||||
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/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/presentation/pages/dev/dev_logger.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -12,16 +12,16 @@ import 'package:logging/logging.dart';
|
||||
class HashService {
|
||||
final int batchSizeLimit;
|
||||
final int batchFileLimit;
|
||||
final DriftLocalAlbumRepository _localAlbumRepository;
|
||||
final DriftLocalAssetRepository _localAssetRepository;
|
||||
final StorageRepository _storageRepository;
|
||||
final ILocalAlbumRepository _localAlbumRepository;
|
||||
final ILocalAssetRepository _localAssetRepository;
|
||||
final IStorageRepository _storageRepository;
|
||||
final NativeSyncApi _nativeSyncApi;
|
||||
final _log = Logger('HashService');
|
||||
|
||||
HashService({
|
||||
required DriftLocalAlbumRepository localAlbumRepository,
|
||||
required DriftLocalAssetRepository localAssetRepository,
|
||||
required StorageRepository storageRepository,
|
||||
required ILocalAlbumRepository localAlbumRepository,
|
||||
required ILocalAssetRepository localAssetRepository,
|
||||
required IStorageRepository storageRepository,
|
||||
required NativeSyncApi nativeSyncApi,
|
||||
this.batchSizeLimit = kBatchHashSizeLimit,
|
||||
this.batchFileLimit = kBatchHashFileLimit,
|
||||
|
||||
@@ -2,11 +2,11 @@ import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.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/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.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/presentation/pages/dev/dev_logger.dart';
|
||||
import 'package:immich_mobile/utils/diff.dart';
|
||||
@@ -14,14 +14,14 @@ import 'package:logging/logging.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
class LocalSyncService {
|
||||
final DriftLocalAlbumRepository _localAlbumRepository;
|
||||
final ILocalAlbumRepository _localAlbumRepository;
|
||||
final NativeSyncApi _nativeSyncApi;
|
||||
final Platform _platform;
|
||||
final StoreService _storeService;
|
||||
final Logger _log = Logger("DeviceSyncService");
|
||||
|
||||
LocalSyncService({
|
||||
required DriftLocalAlbumRepository localAlbumRepository,
|
||||
required ILocalAlbumRepository localAlbumRepository,
|
||||
required NativeSyncApi nativeSyncApi,
|
||||
required StoreService storeService,
|
||||
Platform? platform,
|
||||
|
||||
@@ -2,10 +2,10 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.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/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';
|
||||
|
||||
/// 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
|
||||
/// via [IStoreRepository]
|
||||
class LogService {
|
||||
final IsarLogRepository _logRepository;
|
||||
final IsarStoreRepository _storeRepository;
|
||||
final ILogRepository _logRepository;
|
||||
final IStoreRepository _storeRepository;
|
||||
|
||||
final List<LogMessage> _msgBuffer = [];
|
||||
|
||||
@@ -37,8 +37,8 @@ class LogService {
|
||||
}
|
||||
|
||||
static Future<LogService> init({
|
||||
required IsarLogRepository logRepository,
|
||||
required IsarStoreRepository storeRepository,
|
||||
required ILogRepository logRepository,
|
||||
required IStoreRepository storeRepository,
|
||||
bool shouldBuffer = true,
|
||||
}) async {
|
||||
_instance ??= await create(
|
||||
@@ -50,8 +50,8 @@ class LogService {
|
||||
}
|
||||
|
||||
static Future<LogService> create({
|
||||
required IsarLogRepository logRepository,
|
||||
required IsarStoreRepository storeRepository,
|
||||
required ILogRepository logRepository,
|
||||
required IStoreRepository storeRepository,
|
||||
bool shouldBuffer = true,
|
||||
}) async {
|
||||
final instance = LogService._(logRepository, storeRepository, shouldBuffer);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
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/infrastructure/repositories/store.repository.dart';
|
||||
|
||||
/// Provides access to a persistent key-value store with an in-memory cache.
|
||||
/// Listens for repository changes to keep the cache updated.
|
||||
class StoreService {
|
||||
final IsarStoreRepository _storeRepository;
|
||||
final IStoreRepository _storeRepository;
|
||||
|
||||
/// In-memory cache. Keys are [StoreKey.id]
|
||||
final Map<int, Object?> _cache = {};
|
||||
late final StreamSubscription<StoreDto> _storeUpdateSubscription;
|
||||
|
||||
StoreService._({required IsarStoreRepository storeRepository})
|
||||
StoreService._({required IStoreRepository storeRepository})
|
||||
: _storeRepository = storeRepository;
|
||||
|
||||
// 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
|
||||
static Future<StoreService> init({
|
||||
required IsarStoreRepository storeRepository,
|
||||
required IStoreRepository storeRepository,
|
||||
}) async {
|
||||
_instance ??= await create(storeRepository: storeRepository);
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
static Future<StoreService> create({
|
||||
required IsarStoreRepository storeRepository,
|
||||
required IStoreRepository storeRepository,
|
||||
}) async {
|
||||
final instance = StoreService._(storeRepository: storeRepository);
|
||||
await instance._populateCache();
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
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/infrastructure/repositories/sync_api.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
class SyncStreamService {
|
||||
final Logger _logger = Logger('SyncStreamService');
|
||||
|
||||
final SyncApiRepository _syncApiRepository;
|
||||
final SyncStreamRepository _syncStreamRepository;
|
||||
final ISyncApiRepository _syncApiRepository;
|
||||
final ISyncStreamRepository _syncStreamRepository;
|
||||
final bool Function()? _cancelChecker;
|
||||
|
||||
SyncStreamService({
|
||||
required SyncApiRepository syncApiRepository,
|
||||
required SyncStreamRepository syncStreamRepository,
|
||||
required ISyncApiRepository syncApiRepository,
|
||||
required ISyncStreamRepository syncStreamRepository,
|
||||
bool Function()? cancelChecker,
|
||||
}) : _syncApiRepository = syncApiRepository,
|
||||
_syncStreamRepository = syncStreamRepository,
|
||||
|
||||
@@ -3,11 +3,11 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:collection/collection.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/setting.model.dart';
|
||||
import 'package:immich_mobile/domain/models/timeline.model.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';
|
||||
|
||||
typedef TimelineAssetSource = Future<List<BaseAsset>> Function(
|
||||
@@ -18,11 +18,11 @@ typedef TimelineAssetSource = Future<List<BaseAsset>> Function(
|
||||
typedef TimelineBucketSource = Stream<List<Bucket>> Function();
|
||||
|
||||
class TimelineFactory {
|
||||
final DriftTimelineRepository _timelineRepository;
|
||||
final ITimelineRepository _timelineRepository;
|
||||
final SettingsService _settingsService;
|
||||
|
||||
const TimelineFactory({
|
||||
required DriftTimelineRepository timelineRepository,
|
||||
required ITimelineRepository timelineRepository,
|
||||
required SettingsService settingsService,
|
||||
}) : _timelineRepository = timelineRepository,
|
||||
_settingsService = settingsService;
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import 'dart:async';
|
||||
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/user.model.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';
|
||||
|
||||
class UserService {
|
||||
final Logger _log = Logger("UserService");
|
||||
final IsarUserRepository _isarUserRepository;
|
||||
final UserApiRepository _userApiRepository;
|
||||
final IUserRepository _userRepository;
|
||||
final IUserApiRepository _userApiRepository;
|
||||
final StoreService _storeService;
|
||||
|
||||
UserService({
|
||||
required IsarUserRepository isarUserRepository,
|
||||
required UserApiRepository userApiRepository,
|
||||
required IUserRepository userRepository,
|
||||
required IUserApiRepository userApiRepository,
|
||||
required StoreService storeService,
|
||||
}) : _isarUserRepository = isarUserRepository,
|
||||
}) : _userRepository = userRepository,
|
||||
_userApiRepository = userApiRepository,
|
||||
_storeService = storeService;
|
||||
|
||||
@@ -38,7 +38,7 @@ class UserService {
|
||||
final user = await _userApiRepository.getMyUser();
|
||||
if (user == null) return null;
|
||||
await _storeService.put(StoreKey.currentUser, user);
|
||||
await _isarUserRepository.update(user);
|
||||
await _userRepository.update(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ class UserService {
|
||||
);
|
||||
final updatedUser = getMyUser().copyWith(profileImagePath: path);
|
||||
await _storeService.put(StoreKey.currentUser, updatedUser);
|
||||
await _isarUserRepository.update(updatedUser);
|
||||
await _userRepository.update(updatedUser);
|
||||
return path;
|
||||
} catch (e) {
|
||||
_log.warning("Failed to upload profile image", e);
|
||||
@@ -59,10 +59,10 @@ class UserService {
|
||||
}
|
||||
|
||||
Future<List<UserDto>> getAll() async {
|
||||
return await _isarUserRepository.getAll();
|
||||
return await _userRepository.getAll();
|
||||
}
|
||||
|
||||
Future<void> deleteAll() {
|
||||
return _isarUserRepository.deleteAll();
|
||||
return _userRepository.deleteAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:immich_mobile/domain/interfaces/asset_media.interface.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
class AssetMediaRepository {
|
||||
class AssetMediaRepository implements IAssetMediaRepository {
|
||||
const AssetMediaRepository();
|
||||
|
||||
@override
|
||||
Future<Uint8List?> getThumbnail(
|
||||
String id, {
|
||||
int quality = 80,
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import 'package:immich_mobile/domain/interfaces/device_asset.interface.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/repositories/db.repository.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
class IsarDeviceAssetRepository extends IsarDatabaseRepository {
|
||||
class IsarDeviceAssetRepository extends IsarDatabaseRepository
|
||||
implements IDeviceAssetRepository {
|
||||
final Isar _db;
|
||||
|
||||
const IsarDeviceAssetRepository(this._db) : super(_db);
|
||||
|
||||
@override
|
||||
Future<void> deleteIds(List<String> ids) {
|
||||
return transaction(() async {
|
||||
await _db.deviceAssetEntitys.deleteAllByAssetId(ids.toList());
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<DeviceAsset>> getByIds(List<String> localIds) {
|
||||
return _db.deviceAssetEntitys
|
||||
.where()
|
||||
@@ -22,6 +26,7 @@ class IsarDeviceAssetRepository extends IsarDatabaseRepository {
|
||||
.then((value) => value.map((e) => e.toModel()).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> updateAll(List<DeviceAsset> assetHash) {
|
||||
return transaction(() async {
|
||||
await _db.deviceAssetEntitys
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
|
||||
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'
|
||||
as entity;
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
class IsarExifRepository extends IsarDatabaseRepository {
|
||||
class IsarExifRepository extends IsarDatabaseRepository
|
||||
implements IExifInfoRepository {
|
||||
final Isar _db;
|
||||
|
||||
const IsarExifRepository(this._db) : super(_db);
|
||||
|
||||
@override
|
||||
Future<void> delete(int assetId) async {
|
||||
await transaction(() async {
|
||||
await _db.exifInfos.delete(assetId);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteAll() async {
|
||||
await transaction(() async {
|
||||
await _db.exifInfos.clear();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ExifInfo?> get(int assetId) async {
|
||||
return (await _db.exifInfos.get(assetId))?.toDto();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ExifInfo> update(ExifInfo exifInfo) {
|
||||
return transaction(() async {
|
||||
await _db.exifInfos.put(entity.ExifInfo.fromDto(exifInfo));
|
||||
@@ -32,6 +38,7 @@ class IsarExifRepository extends IsarDatabaseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ExifInfo>> updateAll(List<ExifInfo> exifInfos) {
|
||||
return transaction(() async {
|
||||
await _db.exifInfos.putAll(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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/local_album.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
||||
@@ -7,15 +8,15 @@ import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.d
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
enum SortLocalAlbumsBy { id, backupSelection, isIosSharedAlbum }
|
||||
|
||||
class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
||||
implements ILocalAlbumRepository {
|
||||
final Drift _db;
|
||||
final Platform _platform;
|
||||
const DriftLocalAlbumRepository(this._db, {Platform? platform})
|
||||
: _platform = platform ?? const LocalPlatform(),
|
||||
super(_db);
|
||||
|
||||
@override
|
||||
Future<List<LocalAlbum>> getAll({Set<SortLocalAlbumsBy> sortBy = const {}}) {
|
||||
final assetCount = _db.localAlbumAssetEntity.assetId.count();
|
||||
|
||||
@@ -55,6 +56,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
.get();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(String albumId) => transaction(() async {
|
||||
// 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
|
||||
@@ -70,6 +72,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
.delete();
|
||||
});
|
||||
|
||||
@override
|
||||
Future<void> syncDeletes(
|
||||
String albumId,
|
||||
Iterable<String> assetIdsToKeep,
|
||||
@@ -98,6 +101,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
await deleteSmt.go();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> upsert(
|
||||
LocalAlbum localAlbum, {
|
||||
Iterable<LocalAsset> toUpsert = const [],
|
||||
@@ -130,6 +134,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAll(Iterable<LocalAlbum> albums) {
|
||||
return _db.transaction(() async {
|
||||
await _db.localAlbumEntity
|
||||
@@ -180,6 +185,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<LocalAsset>> getAssets(String albumId) {
|
||||
final query = _db.localAlbumAssetEntity.select().join(
|
||||
[
|
||||
@@ -196,6 +202,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
.get();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> getAssetIds(String albumId) {
|
||||
final query = _db.localAlbumAssetEntity.selectOnly()
|
||||
..addColumns([_db.localAlbumAssetEntity.assetId])
|
||||
@@ -205,6 +212,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
.get();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> processDelta({
|
||||
required List<LocalAsset> updates,
|
||||
required List<String> deletes,
|
||||
@@ -245,6 +253,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<LocalAsset>> getAssetsToHash(String albumId) {
|
||||
final query = _db.localAlbumAssetEntity.select().join(
|
||||
[
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
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/infrastructure/entities/local_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
|
||||
class DriftLocalAssetRepository extends DriftDatabaseRepository {
|
||||
class DriftLocalAssetRepository extends DriftDatabaseRepository
|
||||
implements ILocalAssetRepository {
|
||||
final Drift _db;
|
||||
const DriftLocalAssetRepository(this._db) : super(_db);
|
||||
|
||||
@override
|
||||
Future<void> updateHashes(Iterable<LocalAsset> hashes) {
|
||||
if (hashes.isEmpty) {
|
||||
return Future.value();
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
import 'package:immich_mobile/domain/interfaces/log.interface.dart';
|
||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/log.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
class IsarLogRepository extends IsarDatabaseRepository {
|
||||
class IsarLogRepository extends IsarDatabaseRepository
|
||||
implements ILogRepository {
|
||||
final Isar _db;
|
||||
const IsarLogRepository(super.db) : _db = db;
|
||||
|
||||
@override
|
||||
Future<bool> deleteAll() async {
|
||||
await transaction(() async => await _db.loggerMessages.clear());
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<LogMessage>> getAll() async {
|
||||
final logs =
|
||||
await _db.loggerMessages.where().sortByCreatedAtDesc().findAll();
|
||||
return logs.map((l) => l.toDto()).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> insert(LogMessage log) async {
|
||||
final logEntity = LoggerMessage.fromDto(log);
|
||||
await transaction(() async {
|
||||
@@ -26,6 +31,7 @@ class IsarLogRepository extends IsarDatabaseRepository {
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> insertAll(Iterable<LogMessage> logs) async {
|
||||
await transaction(() async {
|
||||
final logEntities =
|
||||
@@ -35,6 +41,7 @@ class IsarLogRepository extends IsarDatabaseRepository {
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> truncate({int limit = 250}) async {
|
||||
await transaction(() async {
|
||||
final count = await _db.loggerMessages.count();
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
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:logging/logging.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
class StorageRepository {
|
||||
class StorageRepository implements IStorageRepository {
|
||||
final _log = Logger('StorageRepository');
|
||||
|
||||
@override
|
||||
Future<File?> getFileForAsset(LocalAsset asset) async {
|
||||
File? file;
|
||||
try {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||
@@ -5,12 +6,14 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
class IsarStoreRepository extends IsarDatabaseRepository {
|
||||
class IsarStoreRepository extends IsarDatabaseRepository
|
||||
implements IStoreRepository {
|
||||
final Isar _db;
|
||||
final validStoreKeys = StoreKey.values.map((e) => e.id).toSet();
|
||||
|
||||
IsarStoreRepository(super.db) : _db = db;
|
||||
|
||||
@override
|
||||
Future<bool> deleteAll() async {
|
||||
return await transaction(() async {
|
||||
await _db.storeValues.clear();
|
||||
@@ -18,6 +21,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<StoreDto<Object>> watchAll() {
|
||||
return _db.storeValues
|
||||
.filter()
|
||||
@@ -30,10 +34,12 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete<T>(StoreKey<T> key) async {
|
||||
return await transaction(() async => await _db.storeValues.delete(key.id));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> insert<T>(StoreKey<T> key, T value) async {
|
||||
return await transaction(() async {
|
||||
await _db.storeValues.put(await _fromValue(key, value));
|
||||
@@ -41,6 +47,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<T?> tryGet<T>(StoreKey<T> key) async {
|
||||
final entity = (await _db.storeValues.get(key.id));
|
||||
if (entity == null) {
|
||||
@@ -49,6 +56,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
||||
return await _toValue(key, entity);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> update<T>(StoreKey<T> key, T value) async {
|
||||
return await transaction(() async {
|
||||
await _db.storeValues.put(await _fromValue(key, value));
|
||||
@@ -56,6 +64,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<T?> watch<T>(StoreKey<T> key) async* {
|
||||
yield* _db.storeValues
|
||||
.watchObject(key.id, fireImmediately: true)
|
||||
@@ -100,6 +109,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
||||
return StoreValue(key.id, intValue: intValue, strValue: strValue);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<StoreDto<Object>>> getAll() async {
|
||||
final entities = await _db.storeValues
|
||||
.filter()
|
||||
|
||||
@@ -3,21 +3,24 @@ import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
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/presentation/pages/dev/dev_logger.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
class SyncApiRepository {
|
||||
class SyncApiRepository implements ISyncApiRepository {
|
||||
final Logger _logger = Logger('SyncApiRepository');
|
||||
final ApiService _api;
|
||||
SyncApiRepository(this._api);
|
||||
|
||||
@override
|
||||
Future<void> ack(List<String> data) {
|
||||
return _api.syncApi.sendSyncAck(SyncAckSetDto(acks: data));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> streamChanges(
|
||||
Function(List<SyncEvent>, Function() abort) onData, {
|
||||
int batchSize = kSyncEventBatchSize,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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/infrastructure/entities/exif.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart';
|
||||
@@ -9,12 +10,14 @@ import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart' as api show AssetVisibility;
|
||||
import 'package:openapi/api.dart' hide AssetVisibility;
|
||||
|
||||
class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
class DriftSyncStreamRepository extends DriftDatabaseRepository
|
||||
implements ISyncStreamRepository {
|
||||
final Logger _logger = Logger('DriftSyncStreamRepository');
|
||||
final Drift _db;
|
||||
|
||||
SyncStreamRepository(super.db) : _db = db;
|
||||
DriftSyncStreamRepository(super.db) : _db = db;
|
||||
|
||||
@override
|
||||
Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data) async {
|
||||
try {
|
||||
await _db.batch((batch) {
|
||||
@@ -31,6 +34,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateUsersV1(Iterable<SyncUserV1> data) async {
|
||||
try {
|
||||
await _db.batch((batch) {
|
||||
@@ -53,6 +57,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deletePartnerV1(Iterable<SyncPartnerDeleteV1> data) async {
|
||||
try {
|
||||
await _db.batch((batch) {
|
||||
@@ -72,6 +77,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updatePartnerV1(Iterable<SyncPartnerV1> data) async {
|
||||
try {
|
||||
await _db.batch((batch) {
|
||||
@@ -95,6 +101,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteAssetsV1(Iterable<SyncAssetDeleteV1> data) async {
|
||||
try {
|
||||
await _deleteAssetsV1(data);
|
||||
@@ -104,6 +111,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAssetsV1(Iterable<SyncAssetV1> data) async {
|
||||
try {
|
||||
await _updateAssetsV1(data);
|
||||
@@ -113,6 +121,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deletePartnerAssetsV1(Iterable<SyncAssetDeleteV1> data) async {
|
||||
try {
|
||||
await _deleteAssetsV1(data);
|
||||
@@ -122,6 +131,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updatePartnerAssetsV1(Iterable<SyncAssetV1> data) async {
|
||||
try {
|
||||
await _updateAssetsV1(data);
|
||||
@@ -131,6 +141,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data) async {
|
||||
try {
|
||||
await _updateAssetExifV1(data);
|
||||
@@ -140,6 +151,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updatePartnerAssetsExifV1(Iterable<SyncAssetExifV1> data) async {
|
||||
try {
|
||||
await _updateAssetExifV1(data);
|
||||
|
||||
@@ -3,13 +3,15 @@ import 'dart:async';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:easy_localization/easy_localization.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/timeline.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:stream_transform/stream_transform.dart';
|
||||
|
||||
class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
class DriftTimelineRepository extends DriftDatabaseRepository
|
||||
implements ITimelineRepository {
|
||||
final Drift _db;
|
||||
|
||||
const DriftTimelineRepository(super._db) : _db = _db;
|
||||
@@ -26,6 +28,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
return buckets;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<Bucket>> watchMainBucket(
|
||||
List<String> userIds, {
|
||||
GroupAssetsBy groupBy = GroupAssetsBy.day,
|
||||
@@ -46,6 +49,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
.throttle(const Duration(seconds: 3), trailing: true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<BaseAsset>> getMainBucketAssets(
|
||||
List<String> userIds, {
|
||||
required int offset,
|
||||
@@ -86,6 +90,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
.get();
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<Bucket>> watchLocalBucket(
|
||||
String albumId, {
|
||||
GroupAssetsBy groupBy = GroupAssetsBy.day,
|
||||
@@ -119,6 +124,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
}).watch();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<BaseAsset>> getLocalBucketAssets(
|
||||
String albumId, {
|
||||
required int offset,
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
|
||||
as entity;
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
class IsarUserRepository extends IsarDatabaseRepository {
|
||||
class IsarUserRepository extends IsarDatabaseRepository
|
||||
implements IUserRepository {
|
||||
final Isar _db;
|
||||
const IsarUserRepository(super.db) : _db = db;
|
||||
|
||||
@override
|
||||
Future<void> delete(List<String> ids) async {
|
||||
await transaction(() async {
|
||||
await _db.users.deleteAllById(ids);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteAll() async {
|
||||
await transaction(() async {
|
||||
await _db.users.clear();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<UserDto>> getAll({SortUserBy? sortBy}) async {
|
||||
return (await _db.users
|
||||
.where()
|
||||
@@ -35,14 +39,17 @@ class IsarUserRepository extends IsarDatabaseRepository {
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserDto?> getByUserId(String id) async {
|
||||
return (await _db.users.getById(id))?.toDto();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<UserDto?>> getByUserIds(List<String> ids) async {
|
||||
return (await _db.users.getAllById(ids)).map((u) => u?.toDto()).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> insert(UserDto user) async {
|
||||
await transaction(() async {
|
||||
await _db.users.put(entity.User.fromDto(user));
|
||||
@@ -50,6 +57,7 @@ class IsarUserRepository extends IsarDatabaseRepository {
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserDto> update(UserDto user) async {
|
||||
await transaction(() async {
|
||||
await _db.users.put(entity.User.fromDto(user));
|
||||
@@ -57,6 +65,7 @@ class IsarUserRepository extends IsarDatabaseRepository {
|
||||
return user;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> updateAll(List<UserDto> users) async {
|
||||
await transaction(() async {
|
||||
await _db.users.putAll(users.map(entity.User.fromDto).toList());
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
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/infrastructure/repositories/api.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
class UserApiRepository extends ApiRepository {
|
||||
class UserApiRepository extends ApiRepository implements IUserApiRepository {
|
||||
final UsersApi _api;
|
||||
const UserApiRepository(this._api);
|
||||
|
||||
@override
|
||||
Future<UserDto?> getMyUser() async {
|
||||
final (adminDto, preferenceDto) =
|
||||
await (_api.getMyUser(), _api.getMyPreferences()).wait;
|
||||
@@ -18,6 +20,7 @@ class UserApiRepository extends ApiRepository {
|
||||
return UserConverter.fromAdminDto(adminDto, preferenceDto);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> createProfileImage({
|
||||
required String name,
|
||||
required Uint8List data,
|
||||
@@ -30,6 +33,7 @@ class UserApiRepository extends ApiRepository {
|
||||
return res.profileImagePath;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<UserDto>> getAll() async {
|
||||
final dto = await checkNull(_api.searchUsers());
|
||||
return dto.map(UserConverter.fromSimpleUserDto).toList();
|
||||
|
||||
16
mobile/lib/interfaces/activity_api.interface.dart
Normal file
16
mobile/lib/interfaces/activity_api.interface.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
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});
|
||||
}
|
||||
55
mobile/lib/interfaces/album.interface.dart
Normal file
55
mobile/lib/interfaces/album.interface.dart
Normal file
@@ -0,0 +1,55 @@
|
||||
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 }
|
||||
42
mobile/lib/interfaces/album_api.interface.dart
Normal file
42
mobile/lib/interfaces/album_api.interface.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
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});
|
||||
}
|
||||
21
mobile/lib/interfaces/album_media.interface.dart
Normal file
21
mobile/lib/interfaces/album_media.interface.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
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);
|
||||
}
|
||||
68
mobile/lib/interfaces/asset.interface.dart
Normal file
68
mobile/lib/interfaces/asset.interface.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
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 }
|
||||
26
mobile/lib/interfaces/asset_api.interface.dart
Normal file
26
mobile/lib/interfaces/asset_api.interface.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
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);
|
||||
}
|
||||
10
mobile/lib/interfaces/asset_media.interface.dart
Normal file
10
mobile/lib/interfaces/asset_media.interface.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
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);
|
||||
}
|
||||
11
mobile/lib/interfaces/auth.interface.dart
Normal file
11
mobile/lib/interfaces/auth.interface.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
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();
|
||||
}
|
||||
14
mobile/lib/interfaces/auth_api.interface.dart
Normal file
14
mobile/lib/interfaces/auth_api.interface.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
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);
|
||||
}
|
||||
16
mobile/lib/interfaces/backup_album.interface.dart
Normal file
16
mobile/lib/interfaces/backup_album.interface.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
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 }
|
||||
6
mobile/lib/interfaces/biometric.interface.dart
Normal file
6
mobile/lib/interfaces/biometric.interface.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
import 'package:immich_mobile/models/auth/biometric_status.model.dart';
|
||||
|
||||
abstract interface class IBiometricRepository {
|
||||
Future<BiometricStatus> getStatus();
|
||||
Future<bool> authenticate(String? message);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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();
|
||||
}
|
||||
15
mobile/lib/interfaces/download.interface.dart
Normal file
15
mobile/lib/interfaces/download.interface.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
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);
|
||||
}
|
||||
16
mobile/lib/interfaces/etag.interface.dart
Normal file
16
mobile/lib/interfaces/etag.interface.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
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();
|
||||
}
|
||||
36
mobile/lib/interfaces/file_media.interface.dart
Normal file
36
mobile/lib/interfaces/file_media.interface.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
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();
|
||||
}
|
||||
6
mobile/lib/interfaces/folder_api.interface.dart
Normal file
6
mobile/lib/interfaces/folder_api.interface.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
|
||||
abstract interface class IFolderApiRepository {
|
||||
Future<List<String>> getAllUniquePaths();
|
||||
Future<List<Asset>> getAssetsForPath(String? path);
|
||||
}
|
||||
5
mobile/lib/interfaces/local_files_manager.interface.dart
Normal file
5
mobile/lib/interfaces/local_files_manager.interface.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
abstract interface class ILocalFilesManager {
|
||||
Future<bool> moveToTrash(List<String> mediaUrls);
|
||||
Future<bool> restoreFromTrash(String fileName, int type);
|
||||
Future<bool> requestManageMediaPermission();
|
||||
}
|
||||
4
mobile/lib/interfaces/network.interface.dart
Normal file
4
mobile/lib/interfaces/network.interface.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
abstract interface class INetworkRepository {
|
||||
Future<String?> getWifiName();
|
||||
Future<String?> getWifiIp();
|
||||
}
|
||||
8
mobile/lib/interfaces/partner.interface.dart
Normal file
8
mobile/lib/interfaces/partner.interface.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
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();
|
||||
}
|
||||
13
mobile/lib/interfaces/partner_api.interface.dart
Normal file
13
mobile/lib/interfaces/partner_api.interface.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
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,
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'dart:convert';
|
||||
|
||||
abstract interface class IPersonApiRepository {
|
||||
Future<List<Person>> getAll();
|
||||
Future<Person> update(String id, {String? name});
|
||||
}
|
||||
|
||||
class Person {
|
||||
Person({
|
||||
required this.id,
|
||||
5
mobile/lib/interfaces/secure_storage.interface.dart
Normal file
5
mobile/lib/interfaces/secure_storage.interface.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
abstract interface class ISecureStorageRepository {
|
||||
Future<String?> read(String key);
|
||||
Future<void> write(String key, String value);
|
||||
Future<void> delete(String key);
|
||||
}
|
||||
9
mobile/lib/interfaces/sessions_api.interface.dart
Normal file
9
mobile/lib/interfaces/sessions_api.interface.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
import 'package:immich_mobile/models/sessions/session_create_response.model.dart';
|
||||
|
||||
abstract interface class ISessionAPIRepository {
|
||||
Future<SessionCreateResponse> createSession(
|
||||
String deviceName,
|
||||
String deviceOS, {
|
||||
int? duration,
|
||||
});
|
||||
}
|
||||
7
mobile/lib/interfaces/share_handler.interface.dart
Normal file
7
mobile/lib/interfaces/share_handler.interface.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
|
||||
|
||||
abstract interface class IShareHandlerRepository {
|
||||
void Function(List<ShareIntentAttachment>)? onSharedMedia;
|
||||
|
||||
Future<void> init();
|
||||
}
|
||||
39
mobile/lib/interfaces/timeline.interface.dart
Normal file
39
mobile/lib/interfaces/timeline.interface.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
11
mobile/lib/interfaces/upload.interface.dart
Normal file
11
mobile/lib/interfaces/upload.interface.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
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);
|
||||
}
|
||||
5
mobile/lib/interfaces/widget.interface.dart
Normal file
5
mobile/lib/interfaces/widget.interface.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
abstract interface class IWidgetRepository {
|
||||
Future<void> saveData(String key, String value);
|
||||
Future<void> refresh(String name);
|
||||
Future<void> setAppGroupId(String appGroupId);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:immich_mobile/domain/models/person.model.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/interfaces/person_api.interface.dart';
|
||||
|
||||
class SearchLocationFilter {
|
||||
String? country;
|
||||
|
||||
@@ -6,9 +6,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/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/providers/search/paginated_search.provider.dart';
|
||||
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
|
||||
|
||||
@@ -4,13 +4,14 @@ import 'dart:ui';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.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/infrastructure/repositories/asset_media.repository.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
||||
import 'package:immich_mobile/providers/image/cache/thumbnail_image_cache_manager.dart';
|
||||
|
||||
class LocalThumbProvider extends ImageProvider<LocalThumbProvider> {
|
||||
final AssetMediaRepository _assetMediaRepository =
|
||||
final IAssetMediaRepository _assetMediaRepository =
|
||||
const AssetMediaRepository();
|
||||
final CacheManager? cacheManager;
|
||||
|
||||
|
||||
@@ -118,8 +118,10 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
||||
}) async {
|
||||
await _apiService.setAccessToken(accessToken);
|
||||
|
||||
await _widgetService.writeSessionKey(accessToken);
|
||||
await _widgetService.writeServerList();
|
||||
await _widgetService.writeCredentials(
|
||||
Store.get(StoreKey.serverEndpoint),
|
||||
accessToken,
|
||||
);
|
||||
|
||||
// Get the deviceid from the store if it exists, otherwise generate a new one
|
||||
String deviceId =
|
||||
@@ -188,7 +190,6 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
||||
|
||||
Future<void> saveLocalEndpoint(String url) async {
|
||||
await Store.put(StoreKey.localEndpoint, url);
|
||||
await _widgetService.writeServerList();
|
||||
}
|
||||
|
||||
String? getSavedWifiName() {
|
||||
|
||||
@@ -9,6 +9,9 @@ import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/album.entity.dart';
|
||||
import 'package:immich_mobile/entities/backup_album.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/backup/available_album.model.dart';
|
||||
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
|
||||
@@ -22,7 +25,6 @@ import 'package:immich_mobile/providers/auth.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/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/services/background.service.dart';
|
||||
import 'package:immich_mobile/services/backup.service.dart';
|
||||
@@ -106,8 +108,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
final AuthState _authState;
|
||||
final BackgroundService _backgroundService;
|
||||
final GalleryPermissionNotifier _galleryPermissionNotifier;
|
||||
final AlbumMediaRepository _albumMediaRepository;
|
||||
final FileMediaRepository _fileMediaRepository;
|
||||
final IAlbumMediaRepository _albumMediaRepository;
|
||||
final IFileMediaRepository _fileMediaRepository;
|
||||
final BackupAlbumService _backupAlbumService;
|
||||
final Ref ref;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/services/gcast.service.dart';
|
||||
|
||||
@@ -9,7 +10,7 @@ final castProvider = StateNotifierProvider<CastNotifier, CastManagerState>(
|
||||
|
||||
class CastNotifier extends StateNotifier<CastManagerState> {
|
||||
// more cast providers can be added here (ie Fcast)
|
||||
final GCastService _gCastService;
|
||||
final ICastDestinationService _gCastService;
|
||||
|
||||
List<(String, CastDestinationType, dynamic)> discovered = List.empty();
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
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/providers/infrastructure/db.provider.dart';
|
||||
|
||||
final localAlbumRepository = Provider<DriftLocalAlbumRepository>(
|
||||
final localAlbumRepository = Provider<ILocalAlbumRepository>(
|
||||
(ref) => DriftLocalAlbumRepository(ref.watch(driftProvider)),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
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/providers/infrastructure/db.provider.dart';
|
||||
|
||||
final localAssetRepository = Provider<DriftLocalAssetRepository>(
|
||||
final localAssetRepository = Provider<ILocalAssetRepository>(
|
||||
(ref) => DriftLocalAssetRepository(ref.watch(driftProvider)),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
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/providers/infrastructure/db.provider.dart';
|
||||
|
||||
final deviceAssetRepositoryProvider = Provider<IsarDeviceAssetRepository>(
|
||||
final deviceAssetRepositoryProvider = Provider<IDeviceAssetRepository>(
|
||||
(ref) => IsarDeviceAssetRepository(ref.watch(isarProvider)),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -6,5 +7,5 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'exif.provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
IsarExifRepository exifRepository(Ref ref) =>
|
||||
IExifInfoRepository exifRepository(Ref ref) =>
|
||||
IsarExifRepository(ref.watch(isarProvider));
|
||||
|
||||
@@ -6,11 +6,11 @@ part of 'exif.provider.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$exifRepositoryHash() => r'bf4a3f6a50d954a23d317659b4f3e2f381066463';
|
||||
String _$exifRepositoryHash() => r'f0abe778ed61fbb257001fdf2ac6e17814011fee';
|
||||
|
||||
/// See also [exifRepository].
|
||||
@ProviderFor(exifRepository)
|
||||
final exifRepositoryProvider = Provider<IsarExifRepository>.internal(
|
||||
final exifRepositoryProvider = Provider<IExifInfoRepository>.internal(
|
||||
exifRepository,
|
||||
name: r'exifRepositoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
@@ -22,6 +22,6 @@ final exifRepositoryProvider = Provider<IsarExifRepository>.internal(
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef ExifRepositoryRef = ProviderRef<IsarExifRepository>;
|
||||
typedef ExifRepositoryRef = ProviderRef<IExifInfoRepository>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
|
||||
final storageRepositoryProvider = Provider<StorageRepository>(
|
||||
final storageRepositoryProvider = Provider<IStorageRepository>(
|
||||
(ref) => StorageRepository(),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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/infrastructure/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
@@ -7,7 +8,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'store.provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
IsarStoreRepository storeRepository(Ref ref) =>
|
||||
IStoreRepository storeRepository(Ref ref) =>
|
||||
IsarStoreRepository(ref.watch(isarProvider));
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
|
||||
@@ -6,11 +6,11 @@ part of 'store.provider.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$storeRepositoryHash() => r'659cb134466e4b0d5f04e2fc93e426350d99545f';
|
||||
String _$storeRepositoryHash() => r'99d24875d30c5e86b1c6caa352a0026167114e62';
|
||||
|
||||
/// See also [storeRepository].
|
||||
@ProviderFor(storeRepository)
|
||||
final storeRepositoryProvider = Provider<IsarStoreRepository>.internal(
|
||||
final storeRepositoryProvider = Provider<IStoreRepository>.internal(
|
||||
storeRepository,
|
||||
name: r'storeRepositoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
@@ -22,7 +22,7 @@ final storeRepositoryProvider = Provider<IsarStoreRepository>.internal(
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef StoreRepositoryRef = ProviderRef<IsarStoreRepository>;
|
||||
typedef StoreRepositoryRef = ProviderRef<IStoreRepository>;
|
||||
String _$storeServiceHash() => r'250e10497c42df360e9e1f9a618d0b19c1b5b0a0';
|
||||
|
||||
/// See also [storeService].
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user