Compare commits

..

65 Commits

Author SHA1 Message Date
Yaros
a151ebc26d chore: remove create from migration
Co-authored-by: Copilot <copilot@github.com>
2026-04-27 18:52:05 +02:00
Yaros
cb6f18b3a4 test: visibility change
Co-authored-by: Copilot <copilot@github.com>
2026-04-27 18:45:09 +02:00
Yaros
63be7254a8 chore: make build mobile 2026-04-22 19:29:08 +02:00
Yaros
882d315fb0 chore: rename text column 2026-04-22 19:06:44 +02:00
Yaros
6b908b28b6 chore: zod use double
Co-authored-by: Copilot <copilot@github.com>
2026-04-22 18:35:27 +02:00
Yaros
076c355511 chore: regenerate openapi on linux 2026-04-22 18:27:22 +02:00
Yaros
d2f4ddf131 chore: openapi generate & drift migrate 2026-04-22 18:20:21 +02:00
Yaros
aa4d7055ab Merge branch 'main' into feat/mobile-ocr 2026-04-22 18:08:10 +02:00
Alex
03b70cf029 fix: jump to timeline on new auto_router update (#28022) 2026-04-22 10:21:48 -05:00
Daniel Dietzler
4bfb8b36c2 chore!: migrate album owner to album_user (#27467)
Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-22 16:52:23 +02:00
renovate[bot]
dfacde5af8 fix(deps): update typescript-projects (#28025)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-22 16:49:28 +02:00
Junghwan
317afe9e3b fix(web): normalize underscore locale codes in dynamic language selection (#27900)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-22 13:28:33 +00:00
Jason Rasmussen
1fb5f13237 fix: oauth prompt (#28021) 2026-04-22 09:19:28 -04:00
Luis Nachtigall
793a7054fb fix(mobile): thumbnail transition to asset viewer (#27850) 2026-04-21 15:54:40 -05:00
Luis Nachtigall
3a874dd441 fix(mobile): enable autoplay for motion photos in video viewer (#27961) 2026-04-21 15:53:21 -05:00
Luis Nachtigall
3dc7dc93d8 fix(mobile): clear local data on forced logout (#27957) 2026-04-21 15:52:00 -05:00
Yaros
70397dc5a6 fix(mobile): zero exposure (#28017) 2026-04-21 15:47:27 -05:00
Jason Rasmussen
a16d233a0c chore(web): sort imports (#27922)
* feat: sort imports

* fix: something?
2026-04-21 14:51:38 -04:00
Daniel Dietzler
bb0872afef chore: upgrade sql-tools (#27885) 2026-04-21 17:42:27 +00:00
Freddie Floydd
b9ca68f6e4 chore(web): rename components to PascalCase (#28013)
chore: rename components to PascalCase
2026-04-21 12:29:42 -04:00
Daniel Dietzler
837305da7e chore: un-skip tests (#28012) 2026-04-21 12:08:23 -04:00
Daniel Dietzler
e20fb44142 fix: web navigation/animation regression (#28011) 2026-04-21 14:51:37 +00:00
renovate[bot]
c2786978cd fix(deps): update typescript-projects (#28008)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-21 15:29:34 +02:00
renovate[bot]
312bb91a4f chore(deps): update github-actions (#28005)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 13:35:55 +02:00
renovate[bot]
c1934b904c chore(deps): update dependency opentofu to v1.11.6 (#27999)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 13:31:16 +02:00
renovate[bot]
47752d158a fix(deps): update dependency @mapbox/mapbox-gl-rtl-text to v0.4.0 (#28007)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 13:25:30 +02:00
renovate[bot]
6267322b9c chore(deps): update dependency exiftool-vendored to v35.17.0 (#28004)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 13:08:12 +02:00
renovate[bot]
93c3cd49f3 chore(deps): update node.js to v24.15.0 (#28006)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 09:47:32 +00:00
renovate[bot]
f52825ab08 chore(deps): update prom/prometheus docker digest to 5550dc6 (#27998)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 11:23:24 +02:00
renovate[bot]
d74dc74f92 chore(deps): update dependency terragrunt to v1.0.1 (#28002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 11:23:07 +02:00
Luis Nachtigall
539a39ae49 refactor(mobile): Migrate durationInSeconds to durationMs (#26615) 2026-04-20 23:28:11 -04:00
Daniel Dietzler
f68cd424a7 chore: tags styling (#27984) 2026-04-20 22:06:43 -05:00
Alex
20c0cc7e73 fix: show neon light (#27994) 2026-04-20 20:12:54 -04:00
Aki Hakune
be1b9a5f67 feat(server): add MPO file type support (#27963)
* feat(server): add MPO file type support

* fix(server): document description error
2026-04-20 17:45:53 -04:00
shenlong
d9011c0829 refactor: test organisation and service test (#27991)
* refactor: test organisation

# Conflicts:
#	mobile/test/unit/utils/editor_test.dart

* regroup hash_service_test

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-20 17:45:20 -04:00
shenlong
f909648bce chore: pump flutter to 3.41.7 (#27990)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-04-20 14:51:27 +00:00
Min Idzelis
c78b1d8ab4 fix(web): prevent interaction with detail panel behind person side panel (#27309) 2026-04-20 15:26:06 +02:00
Jason Rasmussen
94a34436a3 chore: remove unused packages & code (#27925) 2026-04-20 08:39:46 -04:00
Yaros
a659cf0751 Merge branch 'main' into feat/mobile-ocr 2026-04-13 18:07:43 +02:00
Yaros
0c985ec1e8 chore: remove drift prefix naming 2026-04-13 17:28:05 +02:00
Yaros
4de5837ff9 chore: toggleOcr function
Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com>
2026-04-13 17:22:28 +02:00
Yaros
9df7efcea5 chore: update version check to v3 2026-04-11 11:22:54 +02:00
Yaros
6af125b3f8 refactor(mobile): remove toDouble 2026-03-24 15:12:54 +01:00
Yaros
d1f58e6f46 refactor(server): use double 2026-03-24 15:08:46 +01:00
Yaros
a71325a978 chore: add version check on sync type 2026-03-24 14:03:43 +01:00
Yaros
4aa45bfae9 test: fix asset.service test 2026-03-24 13:44:51 +01:00
Yaros
9a770cf82c test: fix ocr service medium test 2026-03-24 13:41:47 +01:00
Yaros
68c2dc3df3 Merge branch 'main' into feat/mobile-ocr 2026-03-24 13:29:07 +01:00
Yaros
630ae1cbe2 feat(mobile): support zoom 2026-03-24 13:20:49 +01:00
Yaros
5348a44be9 chore: minor ui tweaks 2026-03-16 16:00:13 +01:00
Yaros
fc515af284 chore(server): generate sql 2026-03-16 15:40:14 +01:00
Yaros
928e667934 fix: added missing extramodel 2026-03-16 15:38:04 +01:00
Yaros
49f9c01003 fix: imports 2026-03-16 13:47:34 +01:00
Yaros
e6edd868a5 fix: drift migration 2026-03-16 13:06:02 +01:00
Yaros
a50679436c Merge branch 'main' into feat/mobile-ocr 2026-03-16 13:00:41 +01:00
Yaros
ef96fa62c1 Merge branch 'main' into feat/mobile-ocr 2026-02-26 13:21:02 +01:00
Yaros
884ebbc965 Revert "Merge branch 'main' into feat/mobile-ocr"
This reverts commit 93cd80ad12.
2026-02-26 13:08:04 +01:00
Yaros
93cd80ad12 Merge branch 'main' into feat/mobile-ocr 2026-02-26 13:06:51 +01:00
Yaros
6052f84022 feat(mobile): ocr ui 2026-02-25 21:20:28 +01:00
Yaros
207d8ace07 test(server): medium tests 2026-02-25 21:20:09 +01:00
Yaros
82cfadb599 fix(mobile): list of ocrs 2026-02-25 19:12:46 +01:00
Yaros
8ab8a9156f chore(mobile): db migration & sync implementation 2026-02-25 15:57:18 +01:00
Yaros
d1466731d8 fix(server): add ocr audit table to migration & fix queries 2026-02-25 14:57:15 +01:00
Yaros
f706738f93 feat(server): ocr audit table 2026-02-25 12:45:56 +01:00
Yaros
811d3e1c33 feat(server): ocr sync 2026-02-25 11:31:44 +01:00
665 changed files with 51807 additions and 6765 deletions

2
.github/.nvmrc vendored
View File

@@ -1 +1 @@
24.14.1
24.15.0

View File

@@ -103,7 +103,7 @@ jobs:
- name: Restore Gradle Cache
id: cache-gradle-restore
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.gradle/caches
@@ -160,7 +160,7 @@ jobs:
- name: Save Gradle Cache
id: cache-gradle-save
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
if: github.ref == 'refs/heads/main'
with:
path: |
@@ -210,7 +210,7 @@ jobs:
working-directory: ./mobile
- name: Setup Ruby
uses: ruby/setup-ruby@e65c17d16e57e481586a6a5a0282698790062f92 # v1.300.0
uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0
with:
ruby-version: '3.3'
bundler-cache: true

View File

@@ -24,7 +24,7 @@ jobs:
persist-credentials: false
- name: Check for breaking API changes
uses: oasdiff/oasdiff-action/breaking@e6faebce24cf20ac38653d0d2c7f4aa80aaafc79 # v0.0.38
uses: oasdiff/oasdiff-action/breaking@f8cb9308b42121e793f835bd14c0b8090420430c # v0.0.39
with:
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
revision: open-api/immich-openapi-specs.json

View File

@@ -57,7 +57,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -70,7 +70,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -83,6 +83,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
category: '/language:${{matrix.language}}'

View File

@@ -211,7 +211,7 @@ jobs:
run: 'mise run //deployment:tf apply'
- name: Comment
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0
if: ${{ steps.parameters.outputs.event == 'pr' }}
with:
token: ${{ steps.token.outputs.token }}

View File

@@ -42,7 +42,7 @@ jobs:
run: 'mise run //deployment:tf destroy -- -refresh=false'
- name: Comment
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0
with:
token: ${{ steps.token.outputs.token }}
number: ${{ github.event.number }}

View File

@@ -142,7 +142,7 @@ jobs:
github-token: ${{ steps.generate-token.outputs.token }}
- name: Create draft release
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
with:
draft: true
tag_name: ${{ needs.bump_version.outputs.version }}

12
.vscode/settings.json vendored
View File

@@ -13,10 +13,6 @@
"editor.wordBasedSuggestions": "off"
},
"[javascript]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
@@ -29,18 +25,10 @@
"editor.formatOnSave": true
},
"[svelte]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "svelte.svelte-vscode",
"editor.formatOnSave": true
},
"[typescript]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},

View File

@@ -1 +1 @@
24.14.1
24.15.0

View File

@@ -68,6 +68,6 @@
"micromatch": "^4.0.8"
},
"volta": {
"node": "24.14.1"
"node": "24.15.0"
}
}

View File

@@ -1,6 +1,6 @@
[tools]
terragrunt = "1.0.0"
opentofu = "1.11.5"
terragrunt = "1.0.1"
opentofu = "1.11.6"
[tasks."tg:fmt"]
run = "terragrunt hclfmt"

View File

@@ -85,7 +85,7 @@ services:
container_name: immich_prometheus
ports:
- 9090:9090
image: prom/prometheus@sha256:dda13e28bf95a5e5ca5b8ed56852006094c1c8e8871d9c9dbeed30aa6e55271f
image: prom/prometheus@sha256:5550dc63da361dc30f6fe02ac0e4dfc736ededfef3c8d12a634db04a67824d78
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus

View File

@@ -1 +1 @@
24.14.1
24.15.0

View File

@@ -18,6 +18,7 @@ For the full list, refer to the [Immich source code](https://github.com/immich-a
| `JPEG 2000` | `.jp2` | :white_check_mark: | |
| `JPEG` | `.jpeg` `.jpg` `.jpe` `.insp` | :white_check_mark: | |
| `JPEG XL` | `.jxl` | :white_check_mark: | |
| `MPO` | `.mpo` | :white_check_mark: | Multi-Picture |
| `PNG` | `.png` | :white_check_mark: | |
| `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop |
| `RAW` | `.raw` | :white_check_mark: | |

View File

@@ -17,10 +17,10 @@
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "~3.9.0",
"@docusaurus/preset-classic": "~3.9.0",
"@docusaurus/theme-common": "~3.9.0",
"@docusaurus/theme-mermaid": "~3.9.0",
"@docusaurus/core": "~3.10.0",
"@docusaurus/preset-classic": "~3.10.0",
"@docusaurus/theme-common": "~3.10.0",
"@docusaurus/theme-mermaid": "~3.10.0",
"@mdi/js": "^7.3.67",
"@mdi/react": "^1.6.1",
"@mdx-js/react": "^3.0.0",
@@ -36,9 +36,9 @@
"url": "^0.11.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "~3.9.0",
"@docusaurus/module-type-aliases": "~3.10.0",
"@docusaurus/tsconfig": "^3.10.0",
"@docusaurus/types": "^3.7.0",
"@docusaurus/types": "^3.10.0",
"prettier": "^3.7.4",
"typescript": "^6.0.0"
},
@@ -58,6 +58,6 @@
"node": ">=20"
},
"volta": {
"node": "24.14.1"
"node": "24.15.0"
}
}

View File

@@ -1 +1 @@
24.14.1
24.15.0

View File

@@ -58,6 +58,6 @@
"vitest": "^4.0.0"
},
"volta": {
"node": "24.14.1"
"node": "24.15.0"
}
}

View File

@@ -154,23 +154,31 @@ describe('/albums', () => {
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedLink,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true,
}),
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedEditorUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true,
}),
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedViewerUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true,
}),
expect.objectContaining({
ownerId: user2.userId,
albumName: user2SharedUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) },
]),
shared: true,
}),
]),
@@ -184,23 +192,31 @@ describe('/albums', () => {
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedEditorUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true,
}),
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedViewerUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true,
}),
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedLink,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true,
}),
expect.objectContaining({
ownerId: user1.userId,
albumName: user1NotShared,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: false,
}),
]),
@@ -216,23 +232,31 @@ describe('/albums', () => {
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedEditorUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true,
}),
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedViewerUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true,
}),
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedLink,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true,
}),
expect.objectContaining({
ownerId: user2.userId,
albumName: user2SharedUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) },
]),
shared: true,
}),
]),
@@ -248,8 +272,10 @@ describe('/albums', () => {
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({
ownerId: user1.userId,
albumName: user1NotShared,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: false,
}),
]),
@@ -286,13 +312,17 @@ describe('/albums', () => {
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({
ownerId: user4.userId,
albumName: user4DeletedAsset,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user4.userId }) },
]),
shared: false,
}),
expect.objectContaining({
ownerId: user4.userId,
albumName: user4Empty,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user4.userId }) },
]),
shared: false,
}),
]),
@@ -362,16 +392,17 @@ describe('/albums', () => {
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
...user2Albums[0],
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
assetCount: 1,
lastModifiedAssetTimestamp: expect.any(String),
endDate: expect.any(String),
startDate: expect.any(String),
albumUsers: expect.any(Array),
shared: true,
});
expect(body).toEqual(
expect.objectContaining({
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
assetCount: 1,
lastModifiedAssetTimestamp: expect.any(String),
endDate: expect.any(String),
startDate: expect.any(String),
albumUsers: expect.any(Array),
shared: true,
}),
);
});
});
@@ -397,15 +428,13 @@ describe('/albums', () => {
id: expect.any(String),
createdAt: expect.any(String),
updatedAt: expect.any(String),
ownerId: user1.userId,
albumName: 'New album',
description: '',
albumThumbnailAssetId: null,
shared: false,
albumUsers: [],
albumUsers: [{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }],
hasSharedLink: false,
assetCount: 0,
owner: expect.objectContaining({ email: user1.userEmail }),
isActivityEnabled: true,
order: AssetOrder.Desc,
});
@@ -621,11 +650,11 @@ describe('/albums', () => {
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
albumUsers: [
albumUsers: expect.arrayContaining([
expect.objectContaining({
user: expect.objectContaining({ id: user2.userId }),
}),
],
]),
}),
);
});
@@ -637,7 +666,7 @@ describe('/albums', () => {
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Cannot be shared with owner'));
expect(body).toEqual(errorDto.badRequest('User already added'));
});
it('should not be able to add existing user to shared album', async () => {
@@ -663,7 +692,7 @@ describe('/albums', () => {
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
});
expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer);
expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Viewer);
const { status } = await request(app)
.put(`/albums/${album.id}/user/${user2.userId}`)
@@ -678,7 +707,10 @@ describe('/albums', () => {
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(body).toEqual(
expect.objectContaining({
albumUsers: [expect.objectContaining({ role: AlbumUserRole.Editor })],
albumUsers: [
expect.objectContaining({ role: AlbumUserRole.Owner }),
expect.objectContaining({ role: AlbumUserRole.Editor }),
],
}),
);
});
@@ -689,7 +721,7 @@ describe('/albums', () => {
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
});
expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer);
expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Viewer);
const { status, body } = await request(app)
.put(`/albums/${album.id}/user/${user2.userId}`)

View File

@@ -3,6 +3,7 @@
*/
import {
AlbumUserRole,
AssetTypeEnum,
AssetVisibility,
UserAvatarColor,
@@ -420,9 +421,7 @@ export function getAlbum(
albumThumbnailAssetId: album.thumbnailAssetId,
createdAt: album.createdAt,
updatedAt: album.updatedAt,
ownerId: albumOwner.id,
owner: albumOwner,
albumUsers: [], // Empty array for non-shared album
albumUsers: [{ user: albumOwner, role: AlbumUserRole.Owner }],
shared: false,
hasSharedLink: false,
isActivityEnabled: true,

View File

@@ -223,6 +223,7 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI
'.jp2',
'.jpe',
'.jxl',
'.mpo',
'.svg',
'.tif',
'.tiff',

View File

@@ -349,7 +349,7 @@ test.describe('Timeline', () => {
expect(visibleMockAssetsYearMonths).toContain(month);
}
});
test('Deep link to last photo, scroll up', async ({ page }) => {
test.skip('Deep link to last photo, scroll up', async ({ page }) => {
const lastAsset = assets.at(-1)!;
await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
@@ -361,7 +361,7 @@ test.describe('Timeline', () => {
await thumbnailUtils.expectInViewport(page, '14e5901f-fd7f-40c0-b186-4d7e7fc67968');
});
test('Deep link to first bucket, scroll down', async ({ page }) => {
test.skip('Deep link to first bucket, scroll down', async ({ page }) => {
const lastAsset = assets.at(0)!;
await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
await timelineUtils.locator(page).hover();
@@ -440,7 +440,7 @@ test.describe('Timeline', () => {
await thumbnailUtils.expectInViewport(page, asset.id);
await thumbnailUtils.expectSelectedDisabled(page, asset.id);
});
test('Add photos to album', async ({ page }) => {
test.skip('Add photos to album', async ({ page }) => {
const album = timelineRestData.album;
await pageUtils.openAlbumPage(page, album.id);
await page.locator('nav button[aria-label="Add photos"]').click();
@@ -752,7 +752,7 @@ test.describe('Timeline', () => {
await page.getByText('Photos', { exact: true }).click();
await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
});
test('open /favorites, archive photo, unarchive photo', async ({ page }) => {
test.skip('open /favorites, archive photo, unarchive photo', async ({ page }) => {
await pageUtils.openFavorites(page);
const assetToArchive = getAsset(timelineRestData, 'ad31e29f-2069-4574-b9a9-ad86523c92cb')!;
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();

View File

@@ -14,11 +14,11 @@ config_roots = [
]
[tools]
node = "24.14.1"
node = "24.15.0"
flutter = "3.41.6"
pnpm = "10.33.0"
terragrunt = "1.0.0"
opentofu = "1.11.5"
terragrunt = "1.0.1"
opentofu = "1.11.6"
java = "21.0.2"
[tools."github:CQLabs/homebrew-dcm"]

View File

@@ -1,5 +1,5 @@
{
"dart.flutterSdkPath": ".fvm/versions/3.41.6",
"dart.flutterSdkPath": ".fvm/versions/3.41.7",
"dart.lineLength": 120,
"[dart]": {
"editor.rulers": [

View File

@@ -216,7 +216,7 @@ data class PlatformAsset (
val updatedAt: Long? = null,
val width: Long? = null,
val height: Long? = null,
val durationInSeconds: Long,
val durationMs: Long,
val orientation: Long,
val isFavorite: Boolean,
val adjustmentTime: Long? = null,
@@ -234,14 +234,14 @@ data class PlatformAsset (
val updatedAt = pigeonVar_list[4] as Long?
val width = pigeonVar_list[5] as Long?
val height = pigeonVar_list[6] as Long?
val durationInSeconds = pigeonVar_list[7] as Long
val durationMs = pigeonVar_list[7] as Long
val orientation = pigeonVar_list[8] as Long
val isFavorite = pigeonVar_list[9] as Boolean
val adjustmentTime = pigeonVar_list[10] as Long?
val latitude = pigeonVar_list[11] as Double?
val longitude = pigeonVar_list[12] as Double?
val playbackStyle = pigeonVar_list[13] as PlatformAssetPlaybackStyle
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, adjustmentTime, latitude, longitude, playbackStyle)
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationMs, orientation, isFavorite, adjustmentTime, latitude, longitude, playbackStyle)
}
}
fun toList(): List<Any?> {
@@ -253,7 +253,7 @@ data class PlatformAsset (
updatedAt,
width,
height,
durationInSeconds,
durationMs,
orientation,
isFavorite,
adjustmentTime,
@@ -270,7 +270,7 @@ data class PlatformAsset (
return true
}
val other = other as PlatformAsset
return MessagesPigeonUtils.deepEquals(this.id, other.id) && MessagesPigeonUtils.deepEquals(this.name, other.name) && MessagesPigeonUtils.deepEquals(this.type, other.type) && MessagesPigeonUtils.deepEquals(this.createdAt, other.createdAt) && MessagesPigeonUtils.deepEquals(this.updatedAt, other.updatedAt) && MessagesPigeonUtils.deepEquals(this.width, other.width) && MessagesPigeonUtils.deepEquals(this.height, other.height) && MessagesPigeonUtils.deepEquals(this.durationInSeconds, other.durationInSeconds) && MessagesPigeonUtils.deepEquals(this.orientation, other.orientation) && MessagesPigeonUtils.deepEquals(this.isFavorite, other.isFavorite) && MessagesPigeonUtils.deepEquals(this.adjustmentTime, other.adjustmentTime) && MessagesPigeonUtils.deepEquals(this.latitude, other.latitude) && MessagesPigeonUtils.deepEquals(this.longitude, other.longitude) && MessagesPigeonUtils.deepEquals(this.playbackStyle, other.playbackStyle)
return MessagesPigeonUtils.deepEquals(this.id, other.id) && MessagesPigeonUtils.deepEquals(this.name, other.name) && MessagesPigeonUtils.deepEquals(this.type, other.type) && MessagesPigeonUtils.deepEquals(this.createdAt, other.createdAt) && MessagesPigeonUtils.deepEquals(this.updatedAt, other.updatedAt) && MessagesPigeonUtils.deepEquals(this.width, other.width) && MessagesPigeonUtils.deepEquals(this.height, other.height) && MessagesPigeonUtils.deepEquals(this.durationMs, other.durationMs) && MessagesPigeonUtils.deepEquals(this.orientation, other.orientation) && MessagesPigeonUtils.deepEquals(this.isFavorite, other.isFavorite) && MessagesPigeonUtils.deepEquals(this.adjustmentTime, other.adjustmentTime) && MessagesPigeonUtils.deepEquals(this.latitude, other.latitude) && MessagesPigeonUtils.deepEquals(this.longitude, other.longitude) && MessagesPigeonUtils.deepEquals(this.playbackStyle, other.playbackStyle)
}
override fun hashCode(): Int {
@@ -282,7 +282,7 @@ data class PlatformAsset (
result = 31 * result + MessagesPigeonUtils.deepHash(this.updatedAt)
result = 31 * result + MessagesPigeonUtils.deepHash(this.width)
result = 31 * result + MessagesPigeonUtils.deepHash(this.height)
result = 31 * result + MessagesPigeonUtils.deepHash(this.durationInSeconds)
result = 31 * result + MessagesPigeonUtils.deepHash(this.durationMs)
result = 31 * result + MessagesPigeonUtils.deepHash(this.orientation)
result = 31 * result + MessagesPigeonUtils.deepHash(this.isFavorite)
result = 31 * result + MessagesPigeonUtils.deepHash(this.adjustmentTime)

View File

@@ -178,7 +178,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
val height = c.getInt(heightColumn).toLong()
// Duration is milliseconds
val duration = if (rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0L
else c.getLong(durationColumn) / 1000
else c.getLong(durationColumn)
val orientation = c.getInt(orientationColumn)
val isFavorite = if (favoriteColumn == -1) false else c.getInt(favoriteColumn) != 0

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */

View File

@@ -192,7 +192,7 @@ struct PlatformAsset: Hashable {
var updatedAt: Int64? = nil
var width: Int64? = nil
var height: Int64? = nil
var durationInSeconds: Int64
var durationMs: Int64
var orientation: Int64
var isFavorite: Bool
var adjustmentTime: Int64? = nil
@@ -210,7 +210,7 @@ struct PlatformAsset: Hashable {
let updatedAt: Int64? = nilOrValue(pigeonVar_list[4])
let width: Int64? = nilOrValue(pigeonVar_list[5])
let height: Int64? = nilOrValue(pigeonVar_list[6])
let durationInSeconds = pigeonVar_list[7] as! Int64
let durationMs = pigeonVar_list[7] as! Int64
let orientation = pigeonVar_list[8] as! Int64
let isFavorite = pigeonVar_list[9] as! Bool
let adjustmentTime: Int64? = nilOrValue(pigeonVar_list[10])
@@ -226,7 +226,7 @@ struct PlatformAsset: Hashable {
updatedAt: updatedAt,
width: width,
height: height,
durationInSeconds: durationInSeconds,
durationMs: durationMs,
orientation: orientation,
isFavorite: isFavorite,
adjustmentTime: adjustmentTime,
@@ -244,7 +244,7 @@ struct PlatformAsset: Hashable {
updatedAt,
width,
height,
durationInSeconds,
durationMs,
orientation,
isFavorite,
adjustmentTime,
@@ -257,7 +257,7 @@ struct PlatformAsset: Hashable {
if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false
}
return deepEqualsMessages(lhs.id, rhs.id) && deepEqualsMessages(lhs.name, rhs.name) && deepEqualsMessages(lhs.type, rhs.type) && deepEqualsMessages(lhs.createdAt, rhs.createdAt) && deepEqualsMessages(lhs.updatedAt, rhs.updatedAt) && deepEqualsMessages(lhs.width, rhs.width) && deepEqualsMessages(lhs.height, rhs.height) && deepEqualsMessages(lhs.durationInSeconds, rhs.durationInSeconds) && deepEqualsMessages(lhs.orientation, rhs.orientation) && deepEqualsMessages(lhs.isFavorite, rhs.isFavorite) && deepEqualsMessages(lhs.adjustmentTime, rhs.adjustmentTime) && deepEqualsMessages(lhs.latitude, rhs.latitude) && deepEqualsMessages(lhs.longitude, rhs.longitude) && deepEqualsMessages(lhs.playbackStyle, rhs.playbackStyle)
return deepEqualsMessages(lhs.id, rhs.id) && deepEqualsMessages(lhs.name, rhs.name) && deepEqualsMessages(lhs.type, rhs.type) && deepEqualsMessages(lhs.createdAt, rhs.createdAt) && deepEqualsMessages(lhs.updatedAt, rhs.updatedAt) && deepEqualsMessages(lhs.width, rhs.width) && deepEqualsMessages(lhs.height, rhs.height) && deepEqualsMessages(lhs.durationMs, rhs.durationMs) && deepEqualsMessages(lhs.orientation, rhs.orientation) && deepEqualsMessages(lhs.isFavorite, rhs.isFavorite) && deepEqualsMessages(lhs.adjustmentTime, rhs.adjustmentTime) && deepEqualsMessages(lhs.latitude, rhs.latitude) && deepEqualsMessages(lhs.longitude, rhs.longitude) && deepEqualsMessages(lhs.playbackStyle, rhs.playbackStyle)
}
func hash(into hasher: inout Hasher) {
@@ -269,7 +269,7 @@ struct PlatformAsset: Hashable {
deepHashMessages(value: updatedAt, hasher: &hasher)
deepHashMessages(value: width, hasher: &hasher)
deepHashMessages(value: height, hasher: &hasher)
deepHashMessages(value: durationInSeconds, hasher: &hasher)
deepHashMessages(value: durationMs, hasher: &hasher)
deepHashMessages(value: orientation, hasher: &hasher)
deepHashMessages(value: isFavorite, hasher: &hasher)
deepHashMessages(value: adjustmentTime, hasher: &hasher)

View File

@@ -171,7 +171,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
id: asset.localIdentifier,
name: "",
type: 0,
durationInSeconds: 0,
durationMs: 0,
orientation: 0,
isFavorite: false,
playbackStyle: .unknown

View File

@@ -21,7 +21,7 @@ extension PHAsset {
updatedAt: modificationDate.map { Int64($0.timeIntervalSince1970) },
width: Int64(pixelWidth),
height: Int64(pixelHeight),
durationInSeconds: Int64(duration),
durationMs: Int64(duration * 1000),
orientation: 0,
isFavorite: isFavorite,
adjustmentTime: adjustmentTimestamp,

View File

@@ -8,6 +8,7 @@ enum AlbumUserRole {
// do not change this order!
editor,
viewer,
owner,
}
// Model for an album stored in the server

View File

@@ -25,7 +25,7 @@ sealed class BaseAsset {
final DateTime updatedAt;
final int? width;
final int? height;
final int? durationInSeconds;
final int? durationMs;
final bool isFavorite;
final String? livePhotoVideoId;
final bool isEdited;
@@ -38,7 +38,7 @@ sealed class BaseAsset {
required this.updatedAt,
this.width,
this.height,
this.durationInSeconds,
this.durationMs,
this.isFavorite = false,
this.livePhotoVideoId,
required this.isEdited,
@@ -53,15 +53,17 @@ sealed class BaseAsset {
AssetPlaybackStyle get playbackStyle {
if (isVideo) return AssetPlaybackStyle.video;
if (isMotionPhoto) return AssetPlaybackStyle.livePhoto;
if (isImage && durationInSeconds != null && durationInSeconds! > 0) return AssetPlaybackStyle.imageAnimated;
if (isImage && durationMs != null && durationMs! > 0) {
return AssetPlaybackStyle.imageAnimated;
}
if (isImage) return AssetPlaybackStyle.image;
return AssetPlaybackStyle.unknown;
}
Duration get duration {
final durationInSeconds = this.durationInSeconds;
if (durationInSeconds != null) {
return Duration(seconds: durationInSeconds);
final durationMs = this.durationMs;
if (durationMs != null) {
return Duration(milliseconds: durationMs);
}
return const Duration();
}
@@ -88,7 +90,7 @@ sealed class BaseAsset {
updatedAt: $updatedAt,
width: ${width ?? "<NA>"},
height: ${height ?? "<NA>"},
durationInSeconds: ${durationInSeconds ?? "<NA>"},
durationMs: ${durationMs ?? "<NA>"},
isFavorite: $isFavorite,
isEdited: $isEdited,
}''';
@@ -104,7 +106,7 @@ sealed class BaseAsset {
updatedAt == other.updatedAt &&
width == other.width &&
height == other.height &&
durationInSeconds == other.durationInSeconds &&
durationMs == other.durationMs &&
isFavorite == other.isFavorite &&
isEdited == other.isEdited;
}
@@ -119,7 +121,7 @@ sealed class BaseAsset {
updatedAt.hashCode ^
width.hashCode ^
height.hashCode ^
durationInSeconds.hashCode ^
durationMs.hashCode ^
isFavorite.hashCode ^
isEdited.hashCode;
}

View File

@@ -23,7 +23,7 @@ class LocalAsset extends BaseAsset {
required super.updatedAt,
super.width,
super.height,
super.durationInSeconds,
super.durationMs,
super.isFavorite = false,
super.livePhotoVideoId,
this.orientation = 0,
@@ -58,7 +58,7 @@ class LocalAsset extends BaseAsset {
updatedAt: $updatedAt,
width: ${width ?? "<NA>"},
height: ${height ?? "<NA>"},
durationInSeconds: ${durationInSeconds ?? "<NA>"},
durationMs: ${durationMs ?? "<NA>"},
playbackStyle: $playbackStyle,
remoteId: ${remoteId ?? "<NA>"},
cloudId: ${cloudId ?? "<NA>"},
@@ -108,7 +108,7 @@ class LocalAsset extends BaseAsset {
DateTime? updatedAt,
int? width,
int? height,
int? durationInSeconds,
int? durationMs,
bool? isFavorite,
int? orientation,
AssetPlaybackStyle? playbackStyle,
@@ -128,7 +128,7 @@ class LocalAsset extends BaseAsset {
updatedAt: updatedAt ?? this.updatedAt,
width: width ?? this.width,
height: height ?? this.height,
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
durationMs: durationMs ?? this.durationMs,
isFavorite: isFavorite ?? this.isFavorite,
orientation: orientation ?? this.orientation,
playbackStyle: playbackStyle ?? this.playbackStyle,

View File

@@ -22,7 +22,7 @@ class RemoteAsset extends BaseAsset {
required super.updatedAt,
super.width,
super.height,
super.durationInSeconds,
super.durationMs,
super.isFavorite = false,
this.thumbHash,
this.visibility = AssetVisibility.timeline,
@@ -57,7 +57,7 @@ class RemoteAsset extends BaseAsset {
updatedAt: $updatedAt,
width: ${width ?? "<NA>"},
height: ${height ?? "<NA>"},
durationInSeconds: ${durationInSeconds ?? "<NA>"},
durationMs: ${durationMs ?? "<NA>"},
localId: ${localId ?? "<NA>"},
isFavorite: $isFavorite,
thumbHash: ${thumbHash ?? "<NA>"},
@@ -102,7 +102,7 @@ class RemoteAsset extends BaseAsset {
DateTime? updatedAt,
int? width,
int? height,
int? durationInSeconds,
int? durationMs,
bool? isFavorite,
String? thumbHash,
AssetVisibility? visibility,
@@ -121,7 +121,7 @@ class RemoteAsset extends BaseAsset {
updatedAt: updatedAt ?? this.updatedAt,
width: width ?? this.width,
height: height ?? this.height,
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
durationMs: durationMs ?? this.durationMs,
isFavorite: isFavorite ?? this.isFavorite,
thumbHash: thumbHash ?? this.thumbHash,
visibility: visibility ?? this.visibility,
@@ -146,7 +146,7 @@ class RemoteAssetExif extends RemoteAsset {
required super.updatedAt,
super.width,
super.height,
super.durationInSeconds,
super.durationMs,
super.isFavorite = false,
super.thumbHash,
super.visibility = AssetVisibility.timeline,
@@ -178,7 +178,7 @@ class RemoteAssetExif extends RemoteAsset {
DateTime? updatedAt,
int? width,
int? height,
int? durationInSeconds,
int? durationMs,
bool? isFavorite,
String? thumbHash,
AssetVisibility? visibility,
@@ -198,7 +198,7 @@ class RemoteAssetExif extends RemoteAsset {
updatedAt: updatedAt ?? this.updatedAt,
width: width ?? this.width,
height: height ?? this.height,
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
durationMs: durationMs ?? this.durationMs,
isFavorite: isFavorite ?? this.isFavorite,
thumbHash: thumbHash ?? this.thumbHash,
visibility: visibility ?? this.visibility,

View File

@@ -29,7 +29,7 @@ class ExifInfo {
bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0;
String get exposureTime {
if (exposureSeconds == null) {
if (exposureSeconds == null || exposureSeconds! <= 0 || exposureSeconds!.isNaN) {
return "";
}
if (exposureSeconds! < 1) {

View File

@@ -0,0 +1,126 @@
class Ocr {
final String id;
final String assetId;
final double x1;
final double y1;
final double x2;
final double y2;
final double x3;
final double y3;
final double x4;
final double y4;
final double boxScore;
final double textScore;
final String text;
final bool isVisible;
const Ocr({
required this.id,
required this.assetId,
required this.x1,
required this.y1,
required this.x2,
required this.y2,
required this.x3,
required this.y3,
required this.x4,
required this.y4,
required this.boxScore,
required this.textScore,
required this.text,
required this.isVisible,
});
Ocr copyWith({
String? id,
String? assetId,
double? x1,
double? y1,
double? x2,
double? y2,
double? x3,
double? y3,
double? x4,
double? y4,
double? boxScore,
double? textScore,
String? text,
bool? isVisible,
}) {
return Ocr(
id: id ?? this.id,
assetId: assetId ?? this.assetId,
x1: x1 ?? this.x1,
y1: y1 ?? this.y1,
x2: x2 ?? this.x2,
y2: y2 ?? this.y2,
x3: x3 ?? this.x3,
y3: y3 ?? this.y3,
x4: x4 ?? this.x4,
y4: y4 ?? this.y4,
boxScore: boxScore ?? this.boxScore,
textScore: textScore ?? this.textScore,
text: text ?? this.text,
isVisible: isVisible ?? this.isVisible,
);
}
@override
String toString() {
return '''Ocr {
id: $id,
assetId: $assetId,
x1: $x1,
y1: $y1,
x2: $x2,
y2: $y2,
x3: $x3,
y3: $y3,
x4: $x4,
y4: $y4,
boxScore: $boxScore,
textScore: $textScore,
text: $text,
isVisible: $isVisible
}''';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Ocr &&
other.id == id &&
other.assetId == assetId &&
other.x1 == x1 &&
other.y1 == y1 &&
other.x2 == x2 &&
other.y2 == y2 &&
other.x3 == x3 &&
other.y3 == y3 &&
other.x4 == x4 &&
other.y4 == y4 &&
other.boxScore == boxScore &&
other.textScore == textScore &&
other.text == text &&
other.isVisible == isVisible;
}
@override
int get hashCode {
return id.hashCode ^
assetId.hashCode ^
x1.hashCode ^
y1.hashCode ^
x2.hashCode ^
y2.hashCode ^
x3.hashCode ^
y3.hashCode ^
x4.hashCode ^
y4.hashCode ^
boxScore.hashCode ^
textScore.hashCode ^
text.hashCode ^
isVisible.hashCode;
}
}

View File

@@ -337,7 +337,7 @@ class LocalSyncService {
a.createdAt.isAtSameMomentAs(b.createdAt) &&
a.width == b.width &&
a.height == b.height &&
a.durationInSeconds == b.durationInSeconds;
a.durationMs == b.durationMs;
}
final firstAdjustment = a.adjustmentTime?.millisecondsSinceEpoch ?? 0;
@@ -346,7 +346,7 @@ class LocalSyncService {
a.createdAt.isAtSameMomentAs(b.createdAt) &&
a.width == b.width &&
a.height == b.height &&
a.durationInSeconds == b.durationInSeconds &&
a.durationMs == b.durationMs &&
a.latitude == b.latitude &&
a.longitude == b.longitude;
}
@@ -432,7 +432,7 @@ extension PlatformToLocalAsset on PlatformAsset {
updatedAt: tryFromSecondsSinceEpoch(updatedAt, isUtc: true) ?? DateTime.timestamp(),
width: width,
height: height,
durationInSeconds: durationInSeconds,
durationMs: durationMs,
isFavorite: isFavorite,
orientation: orientation,
playbackStyle: _toPlaybackStyle(playbackStyle),

View File

@@ -0,0 +1,12 @@
import 'package:immich_mobile/domain/models/ocr.model.dart';
import 'package:immich_mobile/infrastructure/repositories/ocr.repository.dart';
class OcrService {
final OcrRepository _repository;
const OcrService(this._repository);
Future<List<Ocr>?> get(String assetId) {
return _repository.get(assetId);
}
}

View File

@@ -7,8 +7,8 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
import 'package:immich_mobile/models/albums/album_search.model.dart';
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
class RemoteAlbumService {
final DriftRemoteAlbumRepository _repository;
@@ -28,10 +28,6 @@ class RemoteAlbumService {
return _repository.get(albumId);
}
Future<RemoteAlbum?> getByName(String albumName, String ownerId) {
return _repository.getByName(albumName, ownerId);
}
Future<List<RemoteAlbum>> sortAlbums(
List<RemoteAlbum> albums,
AlbumSortMode sortMode, {
@@ -86,8 +82,18 @@ class RemoteAlbumService {
return filtered;
}
Future<RemoteAlbum> createAlbum({required String title, required List<String> assetIds, String? description}) async {
final album = await _albumApiRepository.createDriftAlbum(title, description: description, assetIds: assetIds);
Future<RemoteAlbum> createAlbum({
required String title,
required UserDto owner,
required List<String> assetIds,
String? description,
}) async {
final album = await _albumApiRepository.createDriftAlbum(
title,
owner,
description: description,
assetIds: assetIds,
);
await _repository.create(album, assetIds);
return album;
@@ -101,8 +107,10 @@ class RemoteAlbumService {
bool? isActivityEnabled,
AlbumAssetOrder? order,
}) async {
final owner = await _repository.getOwner(albumId);
final updatedAlbum = await _albumApiRepository.updateAlbum(
albumId,
owner,
name: name,
description: description,
thumbnailAssetId: thumbnailAssetId,

View File

@@ -1,8 +1,11 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/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/infrastructure/repositories/remote_album.repository.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
import 'package:immich_mobile/utils/debug_print.dart';
import 'package:logging/logging.dart';
@@ -12,6 +15,7 @@ final syncLinkedAlbumServiceProvider = Provider(
ref.watch(localAlbumRepository),
ref.watch(remoteAlbumRepository),
ref.watch(driftAlbumApiRepositoryProvider),
ref.watch(storeServiceProvider),
),
);
@@ -19,8 +23,14 @@ class SyncLinkedAlbumService {
final DriftLocalAlbumRepository _localAlbumRepository;
final DriftRemoteAlbumRepository _remoteAlbumRepository;
final DriftAlbumApiRepository _albumApiRepository;
final StoreService _storeService;
SyncLinkedAlbumService(this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository);
SyncLinkedAlbumService(
this._localAlbumRepository,
this._remoteAlbumRepository,
this._albumApiRepository,
this._storeService,
);
final _log = Logger("SyncLinkedAlbumService");
@@ -103,7 +113,11 @@ class SyncLinkedAlbumService {
/// Creates a new remote album and links it to the local album
Future<void> _createAndLinkNewRemoteAlbum(LocalAlbum localAlbum) async {
dPrint(() => "Creating new remote album for local album: ${localAlbum.name}");
final newRemoteAlbum = await _albumApiRepository.createDriftAlbum(localAlbum.name, assetIds: []);
final newRemoteAlbum = await _albumApiRepository.createDriftAlbum(
localAlbum.name,
_storeService.get(StoreKey.currentUser),
assetIds: [],
);
await _remoteAlbumRepository.create(newRemoteAlbum, []);
return _localAlbumRepository.linkRemoteAlbum(localAlbum.id, newRemoteAlbum.id);
}

View File

@@ -225,6 +225,8 @@ class SyncStreamService {
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'partner backfill');
case SyncEntityType.albumV1:
return _syncStreamRepository.updateAlbumsV1(data.cast());
case SyncEntityType.albumV2:
return _syncStreamRepository.updateAlbumsV2(data.cast());
case SyncEntityType.albumDeleteV1:
return _syncStreamRepository.deleteAlbumsV1(data.cast());
case SyncEntityType.albumUserV1:
@@ -294,6 +296,10 @@ class SyncStreamService {
return _syncStreamRepository.updateAssetFacesV2(data.cast());
case SyncEntityType.assetFaceDeleteV1:
return _syncStreamRepository.deleteAssetFacesV1(data.cast());
case SyncEntityType.assetOcrV1:
return _syncStreamRepository.updateAssetOcrV1(data.cast());
case SyncEntityType.assetOcrDeleteV1:
return _syncStreamRepository.deleteAssetOcrV1(data.cast());
default:
_logger.warning("Unknown sync data type: $type");
}

View File

@@ -14,7 +14,7 @@ extension DTOToAsset on api.AssetResponseDto {
updatedAt: updatedAt,
ownerId: ownerId,
visibility: visibility.toAssetVisibility(),
durationInSeconds: duration?.toDuration()?.inSeconds ?? 0,
durationMs: duration?.toDuration()?.inMilliseconds ?? 0,
height: height?.toInt(),
width: width?.toInt(),
isFavorite: isFavorite,
@@ -36,7 +36,7 @@ extension DTOToAsset on api.AssetResponseDto {
updatedAt: updatedAt,
ownerId: ownerId,
visibility: visibility.toAssetVisibility(),
durationInSeconds: duration?.toDuration()?.inSeconds ?? 0,
durationMs: duration?.toDuration()?.inMilliseconds ?? 0,
height: height?.toInt(),
width: width?.toInt(),
isFavorite: isFavorite,

View File

@@ -7,11 +7,16 @@ extension StringExtension on String {
}
extension DurationExtension on String {
/// Parses and returns the string of format HH:MM:SS as a duration object else null
/// Parses and returns the string of format HH:MM:SS.ffffff as a duration object else null
Duration? toDuration() {
try {
final parts = split(':').map((e) => double.parse(e).toInt()).toList(growable: false);
return Duration(hours: parts[0], minutes: parts[1], seconds: parts[2]);
final parts = split(':');
final hours = double.parse(parts[0]).toInt();
final minutes = double.parse(parts[1]).toInt();
final secondsParts = parts[2].split('.');
final seconds = int.parse(secondsParts[0]);
final milliseconds = secondsParts.length > 1 ? (double.parse('0.${secondsParts[1]}') * 1000).round() : 0;
return Duration(hours: hours, minutes: minutes, seconds: seconds, milliseconds: milliseconds);
} catch (e) {
return null;
}

View File

@@ -0,0 +1,33 @@
import 'package:drift/drift.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
class AssetOcrEntity extends Table with DriftDefaultsMixin {
const AssetOcrEntity();
TextColumn get id => text()();
TextColumn get assetId => text().references(RemoteAssetEntity, #id, onDelete: KeyAction.cascade)();
RealColumn get x1 => real()();
RealColumn get y1 => real()();
RealColumn get x2 => real()();
RealColumn get y2 => real()();
RealColumn get x3 => real()();
RealColumn get y3 => real()();
RealColumn get x4 => real()();
RealColumn get y4 => real()();
RealColumn get boxScore => real()();
RealColumn get textScore => real()();
TextColumn get recognizedText => text()();
BoolColumn get isVisible => boolean().withDefault(const Constant(true))();
@override
Set<Column> get primaryKey => {id};
}

File diff suppressed because it is too large Load Diff

View File

@@ -39,7 +39,7 @@ extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData {
type: type,
createdAt: createdAt,
updatedAt: updatedAt,
durationInSeconds: durationInSeconds,
durationMs: durationMs,
isFavorite: isFavorite,
height: height,
width: width,

View File

@@ -16,7 +16,7 @@ typedef $$LocalAssetEntityTableCreateCompanionBuilder =
i0.Value<DateTime> updatedAt,
i0.Value<int?> width,
i0.Value<int?> height,
i0.Value<int?> durationInSeconds,
i0.Value<int?> durationMs,
required String id,
i0.Value<String?> checksum,
i0.Value<bool> isFavorite,
@@ -35,7 +35,7 @@ typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
i0.Value<DateTime> updatedAt,
i0.Value<int?> width,
i0.Value<int?> height,
i0.Value<int?> durationInSeconds,
i0.Value<int?> durationMs,
i0.Value<String> id,
i0.Value<String?> checksum,
i0.Value<bool> isFavorite,
@@ -87,8 +87,8 @@ class $$LocalAssetEntityTableFilterComposer
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<int> get durationInSeconds => $composableBuilder(
column: $table.durationInSeconds,
i0.ColumnFilters<int> get durationMs => $composableBuilder(
column: $table.durationMs,
builder: (column) => i0.ColumnFilters(column),
);
@@ -182,8 +182,8 @@ class $$LocalAssetEntityTableOrderingComposer
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<int> get durationInSeconds => $composableBuilder(
column: $table.durationInSeconds,
i0.ColumnOrderings<int> get durationMs => $composableBuilder(
column: $table.durationMs,
builder: (column) => i0.ColumnOrderings(column),
);
@@ -260,8 +260,8 @@ class $$LocalAssetEntityTableAnnotationComposer
i0.GeneratedColumn<int> get height =>
$composableBuilder(column: $table.height, builder: (column) => column);
i0.GeneratedColumn<int> get durationInSeconds => $composableBuilder(
column: $table.durationInSeconds,
i0.GeneratedColumn<int> get durationMs => $composableBuilder(
column: $table.durationMs,
builder: (column) => column,
);
@@ -348,7 +348,7 @@ class $$LocalAssetEntityTableTableManager
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(),
i0.Value<String> id = const i0.Value.absent(),
i0.Value<String?> checksum = const i0.Value.absent(),
i0.Value<bool> isFavorite = const i0.Value.absent(),
@@ -366,7 +366,7 @@ class $$LocalAssetEntityTableTableManager
updatedAt: updatedAt,
width: width,
height: height,
durationInSeconds: durationInSeconds,
durationMs: durationMs,
id: id,
checksum: checksum,
isFavorite: isFavorite,
@@ -385,7 +385,7 @@ class $$LocalAssetEntityTableTableManager
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(),
required String id,
i0.Value<String?> checksum = const i0.Value.absent(),
i0.Value<bool> isFavorite = const i0.Value.absent(),
@@ -403,7 +403,7 @@ class $$LocalAssetEntityTableTableManager
updatedAt: updatedAt,
width: width,
height: height,
durationInSeconds: durationInSeconds,
durationMs: durationMs,
id: id,
checksum: checksum,
isFavorite: isFavorite,
@@ -522,17 +522,17 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
type: i0.DriftSqlType.int,
requiredDuringInsert: false,
);
static const i0.VerificationMeta _durationInSecondsMeta =
const i0.VerificationMeta('durationInSeconds');
static const i0.VerificationMeta _durationMsMeta = const i0.VerificationMeta(
'durationMs',
);
@override
late final i0.GeneratedColumn<int> durationInSeconds =
i0.GeneratedColumn<int>(
'duration_in_seconds',
aliasedName,
true,
type: i0.DriftSqlType.int,
requiredDuringInsert: false,
);
late final i0.GeneratedColumn<int> durationMs = i0.GeneratedColumn<int>(
'duration_ms',
aliasedName,
true,
type: i0.DriftSqlType.int,
requiredDuringInsert: false,
);
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
@override
late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>(
@@ -645,7 +645,7 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
updatedAt,
width,
height,
durationInSeconds,
durationMs,
id,
checksum,
isFavorite,
@@ -700,13 +700,10 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
height.isAcceptableOrUnknown(data['height']!, _heightMeta),
);
}
if (data.containsKey('duration_in_seconds')) {
if (data.containsKey('duration_ms')) {
context.handle(
_durationInSecondsMeta,
durationInSeconds.isAcceptableOrUnknown(
data['duration_in_seconds']!,
_durationInSecondsMeta,
),
_durationMsMeta,
durationMs.isAcceptableOrUnknown(data['duration_ms']!, _durationMsMeta),
);
}
if (data.containsKey('id')) {
@@ -800,9 +797,9 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
i0.DriftSqlType.int,
data['${effectivePrefix}height'],
),
durationInSeconds: attachedDatabase.typeMapping.read(
durationMs: attachedDatabase.typeMapping.read(
i0.DriftSqlType.int,
data['${effectivePrefix}duration_in_seconds'],
data['${effectivePrefix}duration_ms'],
),
id: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
@@ -870,7 +867,7 @@ class LocalAssetEntityData extends i0.DataClass
final DateTime updatedAt;
final int? width;
final int? height;
final int? durationInSeconds;
final int? durationMs;
final String id;
final String? checksum;
final bool isFavorite;
@@ -887,7 +884,7 @@ class LocalAssetEntityData extends i0.DataClass
required this.updatedAt,
this.width,
this.height,
this.durationInSeconds,
this.durationMs,
required this.id,
this.checksum,
required this.isFavorite,
@@ -915,8 +912,8 @@ class LocalAssetEntityData extends i0.DataClass
if (!nullToAbsent || height != null) {
map['height'] = i0.Variable<int>(height);
}
if (!nullToAbsent || durationInSeconds != null) {
map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds);
if (!nullToAbsent || durationMs != null) {
map['duration_ms'] = i0.Variable<int>(durationMs);
}
map['id'] = i0.Variable<String>(id);
if (!nullToAbsent || checksum != null) {
@@ -958,7 +955,7 @@ class LocalAssetEntityData extends i0.DataClass
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
width: serializer.fromJson<int?>(json['width']),
height: serializer.fromJson<int?>(json['height']),
durationInSeconds: serializer.fromJson<int?>(json['durationInSeconds']),
durationMs: serializer.fromJson<int?>(json['durationMs']),
id: serializer.fromJson<String>(json['id']),
checksum: serializer.fromJson<String?>(json['checksum']),
isFavorite: serializer.fromJson<bool>(json['isFavorite']),
@@ -984,7 +981,7 @@ class LocalAssetEntityData extends i0.DataClass
'updatedAt': serializer.toJson<DateTime>(updatedAt),
'width': serializer.toJson<int?>(width),
'height': serializer.toJson<int?>(height),
'durationInSeconds': serializer.toJson<int?>(durationInSeconds),
'durationMs': serializer.toJson<int?>(durationMs),
'id': serializer.toJson<String>(id),
'checksum': serializer.toJson<String?>(checksum),
'isFavorite': serializer.toJson<bool>(isFavorite),
@@ -1006,7 +1003,7 @@ class LocalAssetEntityData extends i0.DataClass
DateTime? updatedAt,
i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(),
String? id,
i0.Value<String?> checksum = const i0.Value.absent(),
bool? isFavorite,
@@ -1023,9 +1020,7 @@ class LocalAssetEntityData extends i0.DataClass
updatedAt: updatedAt ?? this.updatedAt,
width: width.present ? width.value : this.width,
height: height.present ? height.value : this.height,
durationInSeconds: durationInSeconds.present
? durationInSeconds.value
: this.durationInSeconds,
durationMs: durationMs.present ? durationMs.value : this.durationMs,
id: id ?? this.id,
checksum: checksum.present ? checksum.value : this.checksum,
isFavorite: isFavorite ?? this.isFavorite,
@@ -1046,9 +1041,9 @@ class LocalAssetEntityData extends i0.DataClass
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
width: data.width.present ? data.width.value : this.width,
height: data.height.present ? data.height.value : this.height,
durationInSeconds: data.durationInSeconds.present
? data.durationInSeconds.value
: this.durationInSeconds,
durationMs: data.durationMs.present
? data.durationMs.value
: this.durationMs,
id: data.id.present ? data.id.value : this.id,
checksum: data.checksum.present ? data.checksum.value : this.checksum,
isFavorite: data.isFavorite.present
@@ -1078,7 +1073,7 @@ class LocalAssetEntityData extends i0.DataClass
..write('updatedAt: $updatedAt, ')
..write('width: $width, ')
..write('height: $height, ')
..write('durationInSeconds: $durationInSeconds, ')
..write('durationMs: $durationMs, ')
..write('id: $id, ')
..write('checksum: $checksum, ')
..write('isFavorite: $isFavorite, ')
@@ -1100,7 +1095,7 @@ class LocalAssetEntityData extends i0.DataClass
updatedAt,
width,
height,
durationInSeconds,
durationMs,
id,
checksum,
isFavorite,
@@ -1121,7 +1116,7 @@ class LocalAssetEntityData extends i0.DataClass
other.updatedAt == this.updatedAt &&
other.width == this.width &&
other.height == this.height &&
other.durationInSeconds == this.durationInSeconds &&
other.durationMs == this.durationMs &&
other.id == this.id &&
other.checksum == this.checksum &&
other.isFavorite == this.isFavorite &&
@@ -1141,7 +1136,7 @@ class LocalAssetEntityCompanion
final i0.Value<DateTime> updatedAt;
final i0.Value<int?> width;
final i0.Value<int?> height;
final i0.Value<int?> durationInSeconds;
final i0.Value<int?> durationMs;
final i0.Value<String> id;
final i0.Value<String?> checksum;
final i0.Value<bool> isFavorite;
@@ -1158,7 +1153,7 @@ class LocalAssetEntityCompanion
this.updatedAt = const i0.Value.absent(),
this.width = const i0.Value.absent(),
this.height = const i0.Value.absent(),
this.durationInSeconds = const i0.Value.absent(),
this.durationMs = const i0.Value.absent(),
this.id = const i0.Value.absent(),
this.checksum = const i0.Value.absent(),
this.isFavorite = const i0.Value.absent(),
@@ -1176,7 +1171,7 @@ class LocalAssetEntityCompanion
this.updatedAt = const i0.Value.absent(),
this.width = const i0.Value.absent(),
this.height = const i0.Value.absent(),
this.durationInSeconds = const i0.Value.absent(),
this.durationMs = const i0.Value.absent(),
required String id,
this.checksum = const i0.Value.absent(),
this.isFavorite = const i0.Value.absent(),
@@ -1196,7 +1191,7 @@ class LocalAssetEntityCompanion
i0.Expression<DateTime>? updatedAt,
i0.Expression<int>? width,
i0.Expression<int>? height,
i0.Expression<int>? durationInSeconds,
i0.Expression<int>? durationMs,
i0.Expression<String>? id,
i0.Expression<String>? checksum,
i0.Expression<bool>? isFavorite,
@@ -1214,7 +1209,7 @@ class LocalAssetEntityCompanion
if (updatedAt != null) 'updated_at': updatedAt,
if (width != null) 'width': width,
if (height != null) 'height': height,
if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds,
if (durationMs != null) 'duration_ms': durationMs,
if (id != null) 'id': id,
if (checksum != null) 'checksum': checksum,
if (isFavorite != null) 'is_favorite': isFavorite,
@@ -1234,7 +1229,7 @@ class LocalAssetEntityCompanion
i0.Value<DateTime>? updatedAt,
i0.Value<int?>? width,
i0.Value<int?>? height,
i0.Value<int?>? durationInSeconds,
i0.Value<int?>? durationMs,
i0.Value<String>? id,
i0.Value<String?>? checksum,
i0.Value<bool>? isFavorite,
@@ -1252,7 +1247,7 @@ class LocalAssetEntityCompanion
updatedAt: updatedAt ?? this.updatedAt,
width: width ?? this.width,
height: height ?? this.height,
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
durationMs: durationMs ?? this.durationMs,
id: id ?? this.id,
checksum: checksum ?? this.checksum,
isFavorite: isFavorite ?? this.isFavorite,
@@ -1288,8 +1283,8 @@ class LocalAssetEntityCompanion
if (height.present) {
map['height'] = i0.Variable<int>(height.value);
}
if (durationInSeconds.present) {
map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds.value);
if (durationMs.present) {
map['duration_ms'] = i0.Variable<int>(durationMs.value);
}
if (id.present) {
map['id'] = i0.Variable<String>(id.value);
@@ -1334,7 +1329,7 @@ class LocalAssetEntityCompanion
..write('updatedAt: $updatedAt, ')
..write('width: $width, ')
..write('height: $height, ')
..write('durationInSeconds: $durationInSeconds, ')
..write('durationMs: $durationMs, ')
..write('id: $id, ')
..write('checksum: $checksum, ')
..write('isFavorite: $isFavorite, ')

View File

@@ -14,7 +14,7 @@ SELECT
rae.updated_at,
rae.width,
rae.height,
rae.duration_in_seconds,
rae.duration_ms,
rae.is_favorite,
rae.thumb_hash,
rae.checksum,
@@ -52,7 +52,7 @@ SELECT
lae.updated_at,
lae.width,
lae.height,
lae.duration_in_seconds,
lae.duration_ms,
lae.is_favorite,
NULL as thumb_hash,
lae.checksum,

View File

@@ -29,7 +29,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
);
$arrayStartIndex += generatedlimit.amountOfVariables;
return customSelect(
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited, 0 AS playback_style FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited, lae.playback_style FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}',
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_ms, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited, 0 AS playback_style FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_ms, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited, lae.playback_style FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}',
variables: [
for (var $ in userIds) i0.Variable<String>($),
...generatedlimit.introducedVariables,
@@ -54,7 +54,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
updatedAt: row.read<DateTime>('updated_at'),
width: row.readNullable<int>('width'),
height: row.readNullable<int>('height'),
durationInSeconds: row.readNullable<int>('duration_in_seconds'),
durationMs: row.readNullable<int>('duration_ms'),
isFavorite: row.read<bool>('is_favorite'),
thumbHash: row.readNullable<String>('thumb_hash'),
checksum: row.readNullable<String>('checksum'),
@@ -127,7 +127,7 @@ class MergedAssetResult {
final DateTime updatedAt;
final int? width;
final int? height;
final int? durationInSeconds;
final int? durationMs;
final bool isFavorite;
final String? thumbHash;
final String? checksum;
@@ -150,7 +150,7 @@ class MergedAssetResult {
required this.updatedAt,
this.width,
this.height,
this.durationInSeconds,
this.durationMs,
required this.isFavorite,
this.thumbHash,
this.checksum,

View File

@@ -1,10 +1,8 @@
import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)')
class RemoteAlbumEntity extends Table with DriftDefaultsMixin {
const RemoteAlbumEntity();
@@ -18,8 +16,6 @@ class RemoteAlbumEntity extends Table with DriftDefaultsMixin {
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
TextColumn get ownerId => text().references(UserEntity, #id, onDelete: KeyAction.cascade)();
TextColumn get thumbnailAssetId =>
text().references(RemoteAssetEntity, #id, onDelete: KeyAction.setNull).nullable()();

View File

@@ -7,11 +7,9 @@ import 'package:immich_mobile/domain/models/album/album.model.dart' as i2;
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart'
as i3;
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
as i5;
import 'package:drift/internal/modular.dart' as i6;
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
as i7;
typedef $$RemoteAlbumEntityTableCreateCompanionBuilder =
i1.RemoteAlbumEntityCompanion Function({
@@ -20,7 +18,6 @@ typedef $$RemoteAlbumEntityTableCreateCompanionBuilder =
i0.Value<String> description,
i0.Value<DateTime> createdAt,
i0.Value<DateTime> updatedAt,
required String ownerId,
i0.Value<String?> thumbnailAssetId,
i0.Value<bool> isActivityEnabled,
required i2.AlbumAssetOrder order,
@@ -32,7 +29,6 @@ typedef $$RemoteAlbumEntityTableUpdateCompanionBuilder =
i0.Value<String> description,
i0.Value<DateTime> createdAt,
i0.Value<DateTime> updatedAt,
i0.Value<String> ownerId,
i0.Value<String?> thumbnailAssetId,
i0.Value<bool> isActivityEnabled,
i0.Value<i2.AlbumAssetOrder> order,
@@ -51,42 +47,10 @@ final class $$RemoteAlbumEntityTableReferences
super.$_typedResult,
);
static i5.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) =>
i6.ReadDatabaseContainer(db)
.resultSet<i5.$UserEntityTable>('user_entity')
.createAlias(
i0.$_aliasNameGenerator(
i6.ReadDatabaseContainer(db)
.resultSet<i1.$RemoteAlbumEntityTable>('remote_album_entity')
.ownerId,
i6.ReadDatabaseContainer(
db,
).resultSet<i5.$UserEntityTable>('user_entity').id,
),
);
i5.$$UserEntityTableProcessedTableManager get ownerId {
final $_column = $_itemColumn<String>('owner_id')!;
final manager = i5
.$$UserEntityTableTableManager(
$_db,
i6.ReadDatabaseContainer(
$_db,
).resultSet<i5.$UserEntityTable>('user_entity'),
)
.filter((f) => f.id.sqlEquals($_column));
final item = $_typedResult.readTableOrNull(_ownerIdTable($_db));
if (item == null) return manager;
return i0.ProcessedTableManager(
manager.$state.copyWith(prefetchedData: [item]),
);
}
static i7.$RemoteAssetEntityTable _thumbnailAssetIdTable(
static i5.$RemoteAssetEntityTable _thumbnailAssetIdTable(
i0.GeneratedDatabase db,
) => i6.ReadDatabaseContainer(db)
.resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity')
.resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity')
.createAlias(
i0.$_aliasNameGenerator(
i6.ReadDatabaseContainer(db)
@@ -94,19 +58,19 @@ final class $$RemoteAlbumEntityTableReferences
.thumbnailAssetId,
i6.ReadDatabaseContainer(
db,
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity').id,
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity').id,
),
);
i7.$$RemoteAssetEntityTableProcessedTableManager? get thumbnailAssetId {
i5.$$RemoteAssetEntityTableProcessedTableManager? get thumbnailAssetId {
final $_column = $_itemColumn<String>('thumbnail_asset_id');
if ($_column == null) return null;
final manager = i7
final manager = i5
.$$RemoteAssetEntityTableTableManager(
$_db,
i6.ReadDatabaseContainer(
$_db,
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
)
.filter((f) => f.id.sqlEquals($_column));
final item = $_typedResult.readTableOrNull(_thumbnailAssetIdTable($_db));
@@ -162,51 +126,24 @@ class $$RemoteAlbumEntityTableFilterComposer
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
);
i5.$$UserEntityTableFilterComposer get ownerId {
final i5.$$UserEntityTableFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.ownerId,
referencedTable: i6.ReadDatabaseContainer(
$db,
).resultSet<i5.$UserEntityTable>('user_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i5.$$UserEntityTableFilterComposer(
$db: $db,
$table: i6.ReadDatabaseContainer(
$db,
).resultSet<i5.$UserEntityTable>('user_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
),
);
return composer;
}
i7.$$RemoteAssetEntityTableFilterComposer get thumbnailAssetId {
final i7.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
i5.$$RemoteAssetEntityTableFilterComposer get thumbnailAssetId {
final i5.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.thumbnailAssetId,
referencedTable: i6.ReadDatabaseContainer(
$db,
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i7.$$RemoteAssetEntityTableFilterComposer(
}) => i5.$$RemoteAssetEntityTableFilterComposer(
$db: $db,
$table: i6.ReadDatabaseContainer(
$db,
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
@@ -261,52 +198,25 @@ class $$RemoteAlbumEntityTableOrderingComposer
builder: (column) => i0.ColumnOrderings(column),
);
i5.$$UserEntityTableOrderingComposer get ownerId {
final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.ownerId,
referencedTable: i6.ReadDatabaseContainer(
$db,
).resultSet<i5.$UserEntityTable>('user_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i5.$$UserEntityTableOrderingComposer(
$db: $db,
$table: i6.ReadDatabaseContainer(
$db,
).resultSet<i5.$UserEntityTable>('user_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
),
);
return composer;
}
i7.$$RemoteAssetEntityTableOrderingComposer get thumbnailAssetId {
final i7.$$RemoteAssetEntityTableOrderingComposer composer =
i5.$$RemoteAssetEntityTableOrderingComposer get thumbnailAssetId {
final i5.$$RemoteAssetEntityTableOrderingComposer composer =
$composerBuilder(
composer: this,
getCurrentColumn: (t) => t.thumbnailAssetId,
referencedTable: i6.ReadDatabaseContainer(
$db,
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i7.$$RemoteAssetEntityTableOrderingComposer(
}) => i5.$$RemoteAssetEntityTableOrderingComposer(
$db: $db,
$table: i6.ReadDatabaseContainer(
$db,
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
@@ -351,52 +261,25 @@ class $$RemoteAlbumEntityTableAnnotationComposer
i0.GeneratedColumnWithTypeConverter<i2.AlbumAssetOrder, int> get order =>
$composableBuilder(column: $table.order, builder: (column) => column);
i5.$$UserEntityTableAnnotationComposer get ownerId {
final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.ownerId,
referencedTable: i6.ReadDatabaseContainer(
$db,
).resultSet<i5.$UserEntityTable>('user_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i5.$$UserEntityTableAnnotationComposer(
$db: $db,
$table: i6.ReadDatabaseContainer(
$db,
).resultSet<i5.$UserEntityTable>('user_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
),
);
return composer;
}
i7.$$RemoteAssetEntityTableAnnotationComposer get thumbnailAssetId {
final i7.$$RemoteAssetEntityTableAnnotationComposer composer =
i5.$$RemoteAssetEntityTableAnnotationComposer get thumbnailAssetId {
final i5.$$RemoteAssetEntityTableAnnotationComposer composer =
$composerBuilder(
composer: this,
getCurrentColumn: (t) => t.thumbnailAssetId,
referencedTable: i6.ReadDatabaseContainer(
$db,
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i7.$$RemoteAssetEntityTableAnnotationComposer(
}) => i5.$$RemoteAssetEntityTableAnnotationComposer(
$db: $db,
$table: i6.ReadDatabaseContainer(
$db,
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
@@ -420,7 +303,7 @@ class $$RemoteAlbumEntityTableTableManager
$$RemoteAlbumEntityTableUpdateCompanionBuilder,
(i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences),
i1.RemoteAlbumEntityData,
i0.PrefetchHooks Function({bool ownerId, bool thumbnailAssetId})
i0.PrefetchHooks Function({bool thumbnailAssetId})
> {
$$RemoteAlbumEntityTableTableManager(
i0.GeneratedDatabase db,
@@ -445,7 +328,6 @@ class $$RemoteAlbumEntityTableTableManager
i0.Value<String> description = const i0.Value.absent(),
i0.Value<DateTime> createdAt = const i0.Value.absent(),
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<String> ownerId = const i0.Value.absent(),
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
i0.Value<bool> isActivityEnabled = const i0.Value.absent(),
i0.Value<i2.AlbumAssetOrder> order = const i0.Value.absent(),
@@ -455,7 +337,6 @@ class $$RemoteAlbumEntityTableTableManager
description: description,
createdAt: createdAt,
updatedAt: updatedAt,
ownerId: ownerId,
thumbnailAssetId: thumbnailAssetId,
isActivityEnabled: isActivityEnabled,
order: order,
@@ -467,7 +348,6 @@ class $$RemoteAlbumEntityTableTableManager
i0.Value<String> description = const i0.Value.absent(),
i0.Value<DateTime> createdAt = const i0.Value.absent(),
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
required String ownerId,
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
i0.Value<bool> isActivityEnabled = const i0.Value.absent(),
required i2.AlbumAssetOrder order,
@@ -477,7 +357,6 @@ class $$RemoteAlbumEntityTableTableManager
description: description,
createdAt: createdAt,
updatedAt: updatedAt,
ownerId: ownerId,
thumbnailAssetId: thumbnailAssetId,
isActivityEnabled: isActivityEnabled,
order: order,
@@ -490,7 +369,7 @@ class $$RemoteAlbumEntityTableTableManager
),
)
.toList(),
prefetchHooksCallback: ({ownerId = false, thumbnailAssetId = false}) {
prefetchHooksCallback: ({thumbnailAssetId = false}) {
return i0.PrefetchHooks(
db: db,
explicitlyWatchedTables: [],
@@ -510,21 +389,6 @@ class $$RemoteAlbumEntityTableTableManager
dynamic
>
>(state) {
if (ownerId) {
state =
state.withJoin(
currentTable: table,
currentColumn: table.ownerId,
referencedTable: i1
.$$RemoteAlbumEntityTableReferences
._ownerIdTable(db),
referencedColumn: i1
.$$RemoteAlbumEntityTableReferences
._ownerIdTable(db)
.id,
)
as T;
}
if (thumbnailAssetId) {
state =
state.withJoin(
@@ -564,12 +428,8 @@ typedef $$RemoteAlbumEntityTableProcessedTableManager =
$$RemoteAlbumEntityTableUpdateCompanionBuilder,
(i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences),
i1.RemoteAlbumEntityData,
i0.PrefetchHooks Function({bool ownerId, bool thumbnailAssetId})
i0.PrefetchHooks Function({bool thumbnailAssetId})
>;
i0.Index get idxRemoteAlbumOwnerId => i0.Index(
'idx_remote_album_owner_id',
'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)',
);
class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
with i0.TableInfo<$RemoteAlbumEntityTable, i1.RemoteAlbumEntityData> {
@@ -636,20 +496,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
requiredDuringInsert: false,
defaultValue: i4.currentDateAndTime,
);
static const i0.VerificationMeta _ownerIdMeta = const i0.VerificationMeta(
'ownerId',
);
@override
late final i0.GeneratedColumn<String> ownerId = i0.GeneratedColumn<String>(
'owner_id',
aliasedName,
false,
type: i0.DriftSqlType.string,
requiredDuringInsert: true,
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
'REFERENCES user_entity (id) ON DELETE CASCADE',
),
);
static const i0.VerificationMeta _thumbnailAssetIdMeta =
const i0.VerificationMeta('thumbnailAssetId');
@override
@@ -698,7 +544,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
description,
createdAt,
updatedAt,
ownerId,
thumbnailAssetId,
isActivityEnabled,
order,
@@ -749,14 +594,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta),
);
}
if (data.containsKey('owner_id')) {
context.handle(
_ownerIdMeta,
ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta),
);
} else if (isInserting) {
context.missing(_ownerIdMeta);
}
if (data.containsKey('thumbnail_asset_id')) {
context.handle(
_thumbnailAssetIdMeta,
@@ -807,10 +644,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
i0.DriftSqlType.dateTime,
data['${effectivePrefix}updated_at'],
)!,
ownerId: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
data['${effectivePrefix}owner_id'],
)!,
thumbnailAssetId: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
data['${effectivePrefix}thumbnail_asset_id'],
@@ -850,7 +683,6 @@ class RemoteAlbumEntityData extends i0.DataClass
final String description;
final DateTime createdAt;
final DateTime updatedAt;
final String ownerId;
final String? thumbnailAssetId;
final bool isActivityEnabled;
final i2.AlbumAssetOrder order;
@@ -860,7 +692,6 @@ class RemoteAlbumEntityData extends i0.DataClass
required this.description,
required this.createdAt,
required this.updatedAt,
required this.ownerId,
this.thumbnailAssetId,
required this.isActivityEnabled,
required this.order,
@@ -873,7 +704,6 @@ class RemoteAlbumEntityData extends i0.DataClass
map['description'] = i0.Variable<String>(description);
map['created_at'] = i0.Variable<DateTime>(createdAt);
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
map['owner_id'] = i0.Variable<String>(ownerId);
if (!nullToAbsent || thumbnailAssetId != null) {
map['thumbnail_asset_id'] = i0.Variable<String>(thumbnailAssetId);
}
@@ -897,7 +727,6 @@ class RemoteAlbumEntityData extends i0.DataClass
description: serializer.fromJson<String>(json['description']),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
ownerId: serializer.fromJson<String>(json['ownerId']),
thumbnailAssetId: serializer.fromJson<String?>(json['thumbnailAssetId']),
isActivityEnabled: serializer.fromJson<bool>(json['isActivityEnabled']),
order: i1.$RemoteAlbumEntityTable.$converterorder.fromJson(
@@ -914,7 +743,6 @@ class RemoteAlbumEntityData extends i0.DataClass
'description': serializer.toJson<String>(description),
'createdAt': serializer.toJson<DateTime>(createdAt),
'updatedAt': serializer.toJson<DateTime>(updatedAt),
'ownerId': serializer.toJson<String>(ownerId),
'thumbnailAssetId': serializer.toJson<String?>(thumbnailAssetId),
'isActivityEnabled': serializer.toJson<bool>(isActivityEnabled),
'order': serializer.toJson<int>(
@@ -929,7 +757,6 @@ class RemoteAlbumEntityData extends i0.DataClass
String? description,
DateTime? createdAt,
DateTime? updatedAt,
String? ownerId,
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
bool? isActivityEnabled,
i2.AlbumAssetOrder? order,
@@ -939,7 +766,6 @@ class RemoteAlbumEntityData extends i0.DataClass
description: description ?? this.description,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
ownerId: ownerId ?? this.ownerId,
thumbnailAssetId: thumbnailAssetId.present
? thumbnailAssetId.value
: this.thumbnailAssetId,
@@ -955,7 +781,6 @@ class RemoteAlbumEntityData extends i0.DataClass
: this.description,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId,
thumbnailAssetId: data.thumbnailAssetId.present
? data.thumbnailAssetId.value
: this.thumbnailAssetId,
@@ -974,7 +799,6 @@ class RemoteAlbumEntityData extends i0.DataClass
..write('description: $description, ')
..write('createdAt: $createdAt, ')
..write('updatedAt: $updatedAt, ')
..write('ownerId: $ownerId, ')
..write('thumbnailAssetId: $thumbnailAssetId, ')
..write('isActivityEnabled: $isActivityEnabled, ')
..write('order: $order')
@@ -989,7 +813,6 @@ class RemoteAlbumEntityData extends i0.DataClass
description,
createdAt,
updatedAt,
ownerId,
thumbnailAssetId,
isActivityEnabled,
order,
@@ -1003,7 +826,6 @@ class RemoteAlbumEntityData extends i0.DataClass
other.description == this.description &&
other.createdAt == this.createdAt &&
other.updatedAt == this.updatedAt &&
other.ownerId == this.ownerId &&
other.thumbnailAssetId == this.thumbnailAssetId &&
other.isActivityEnabled == this.isActivityEnabled &&
other.order == this.order);
@@ -1016,7 +838,6 @@ class RemoteAlbumEntityCompanion
final i0.Value<String> description;
final i0.Value<DateTime> createdAt;
final i0.Value<DateTime> updatedAt;
final i0.Value<String> ownerId;
final i0.Value<String?> thumbnailAssetId;
final i0.Value<bool> isActivityEnabled;
final i0.Value<i2.AlbumAssetOrder> order;
@@ -1026,7 +847,6 @@ class RemoteAlbumEntityCompanion
this.description = const i0.Value.absent(),
this.createdAt = const i0.Value.absent(),
this.updatedAt = const i0.Value.absent(),
this.ownerId = const i0.Value.absent(),
this.thumbnailAssetId = const i0.Value.absent(),
this.isActivityEnabled = const i0.Value.absent(),
this.order = const i0.Value.absent(),
@@ -1037,13 +857,11 @@ class RemoteAlbumEntityCompanion
this.description = const i0.Value.absent(),
this.createdAt = const i0.Value.absent(),
this.updatedAt = const i0.Value.absent(),
required String ownerId,
this.thumbnailAssetId = const i0.Value.absent(),
this.isActivityEnabled = const i0.Value.absent(),
required i2.AlbumAssetOrder order,
}) : id = i0.Value(id),
name = i0.Value(name),
ownerId = i0.Value(ownerId),
order = i0.Value(order);
static i0.Insertable<i1.RemoteAlbumEntityData> custom({
i0.Expression<String>? id,
@@ -1051,7 +869,6 @@ class RemoteAlbumEntityCompanion
i0.Expression<String>? description,
i0.Expression<DateTime>? createdAt,
i0.Expression<DateTime>? updatedAt,
i0.Expression<String>? ownerId,
i0.Expression<String>? thumbnailAssetId,
i0.Expression<bool>? isActivityEnabled,
i0.Expression<int>? order,
@@ -1062,7 +879,6 @@ class RemoteAlbumEntityCompanion
if (description != null) 'description': description,
if (createdAt != null) 'created_at': createdAt,
if (updatedAt != null) 'updated_at': updatedAt,
if (ownerId != null) 'owner_id': ownerId,
if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId,
if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled,
if (order != null) 'order': order,
@@ -1075,7 +891,6 @@ class RemoteAlbumEntityCompanion
i0.Value<String>? description,
i0.Value<DateTime>? createdAt,
i0.Value<DateTime>? updatedAt,
i0.Value<String>? ownerId,
i0.Value<String?>? thumbnailAssetId,
i0.Value<bool>? isActivityEnabled,
i0.Value<i2.AlbumAssetOrder>? order,
@@ -1086,7 +901,6 @@ class RemoteAlbumEntityCompanion
description: description ?? this.description,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
ownerId: ownerId ?? this.ownerId,
thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId,
isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled,
order: order ?? this.order,
@@ -1111,9 +925,6 @@ class RemoteAlbumEntityCompanion
if (updatedAt.present) {
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
}
if (ownerId.present) {
map['owner_id'] = i0.Variable<String>(ownerId.value);
}
if (thumbnailAssetId.present) {
map['thumbnail_asset_id'] = i0.Variable<String>(thumbnailAssetId.value);
}
@@ -1136,7 +947,6 @@ class RemoteAlbumEntityCompanion
..write('description: $description, ')
..write('createdAt: $createdAt, ')
..write('updatedAt: $updatedAt, ')
..write('ownerId: $ownerId, ')
..write('thumbnailAssetId: $thumbnailAssetId, ')
..write('isActivityEnabled: $isActivityEnabled, ')
..write('order: $order')

View File

@@ -66,7 +66,7 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
type: type,
createdAt: createdAt,
updatedAt: updatedAt,
durationInSeconds: durationInSeconds,
durationMs: durationMs,
isFavorite: isFavorite,
height: height,
width: width,

View File

@@ -19,7 +19,7 @@ typedef $$RemoteAssetEntityTableCreateCompanionBuilder =
i0.Value<DateTime> updatedAt,
i0.Value<int?> width,
i0.Value<int?> height,
i0.Value<int?> durationInSeconds,
i0.Value<int?> durationMs,
required String id,
required String checksum,
i0.Value<bool> isFavorite,
@@ -41,7 +41,7 @@ typedef $$RemoteAssetEntityTableUpdateCompanionBuilder =
i0.Value<DateTime> updatedAt,
i0.Value<int?> width,
i0.Value<int?> height,
i0.Value<int?> durationInSeconds,
i0.Value<int?> durationMs,
i0.Value<String> id,
i0.Value<String> checksum,
i0.Value<bool> isFavorite,
@@ -142,8 +142,8 @@ class $$RemoteAssetEntityTableFilterComposer
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<int> get durationInSeconds => $composableBuilder(
column: $table.durationInSeconds,
i0.ColumnFilters<int> get durationMs => $composableBuilder(
column: $table.durationMs,
builder: (column) => i0.ColumnFilters(column),
);
@@ -270,8 +270,8 @@ class $$RemoteAssetEntityTableOrderingComposer
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<int> get durationInSeconds => $composableBuilder(
column: $table.durationInSeconds,
i0.ColumnOrderings<int> get durationMs => $composableBuilder(
column: $table.durationMs,
builder: (column) => i0.ColumnOrderings(column),
);
@@ -385,8 +385,8 @@ class $$RemoteAssetEntityTableAnnotationComposer
i0.GeneratedColumn<int> get height =>
$composableBuilder(column: $table.height, builder: (column) => column);
i0.GeneratedColumn<int> get durationInSeconds => $composableBuilder(
column: $table.durationInSeconds,
i0.GeneratedColumn<int> get durationMs => $composableBuilder(
column: $table.durationMs,
builder: (column) => column,
);
@@ -499,7 +499,7 @@ class $$RemoteAssetEntityTableTableManager
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(),
i0.Value<String> id = const i0.Value.absent(),
i0.Value<String> checksum = const i0.Value.absent(),
i0.Value<bool> isFavorite = const i0.Value.absent(),
@@ -520,7 +520,7 @@ class $$RemoteAssetEntityTableTableManager
updatedAt: updatedAt,
width: width,
height: height,
durationInSeconds: durationInSeconds,
durationMs: durationMs,
id: id,
checksum: checksum,
isFavorite: isFavorite,
@@ -542,7 +542,7 @@ class $$RemoteAssetEntityTableTableManager
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(),
required String id,
required String checksum,
i0.Value<bool> isFavorite = const i0.Value.absent(),
@@ -562,7 +562,7 @@ class $$RemoteAssetEntityTableTableManager
updatedAt: updatedAt,
width: width,
height: height,
durationInSeconds: durationInSeconds,
durationMs: durationMs,
id: id,
checksum: checksum,
isFavorite: isFavorite,
@@ -724,17 +724,17 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
type: i0.DriftSqlType.int,
requiredDuringInsert: false,
);
static const i0.VerificationMeta _durationInSecondsMeta =
const i0.VerificationMeta('durationInSeconds');
static const i0.VerificationMeta _durationMsMeta = const i0.VerificationMeta(
'durationMs',
);
@override
late final i0.GeneratedColumn<int> durationInSeconds =
i0.GeneratedColumn<int>(
'duration_in_seconds',
aliasedName,
true,
type: i0.DriftSqlType.int,
requiredDuringInsert: false,
);
late final i0.GeneratedColumn<int> durationMs = i0.GeneratedColumn<int>(
'duration_ms',
aliasedName,
true,
type: i0.DriftSqlType.int,
requiredDuringInsert: false,
);
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
@override
late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>(
@@ -886,7 +886,7 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
updatedAt,
width,
height,
durationInSeconds,
durationMs,
id,
checksum,
isFavorite,
@@ -944,13 +944,10 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
height.isAcceptableOrUnknown(data['height']!, _heightMeta),
);
}
if (data.containsKey('duration_in_seconds')) {
if (data.containsKey('duration_ms')) {
context.handle(
_durationInSecondsMeta,
durationInSeconds.isAcceptableOrUnknown(
data['duration_in_seconds']!,
_durationInSecondsMeta,
),
_durationMsMeta,
durationMs.isAcceptableOrUnknown(data['duration_ms']!, _durationMsMeta),
);
}
if (data.containsKey('id')) {
@@ -1066,9 +1063,9 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
i0.DriftSqlType.int,
data['${effectivePrefix}height'],
),
durationInSeconds: attachedDatabase.typeMapping.read(
durationMs: attachedDatabase.typeMapping.read(
i0.DriftSqlType.int,
data['${effectivePrefix}duration_in_seconds'],
data['${effectivePrefix}duration_ms'],
),
id: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
@@ -1148,7 +1145,7 @@ class RemoteAssetEntityData extends i0.DataClass
final DateTime updatedAt;
final int? width;
final int? height;
final int? durationInSeconds;
final int? durationMs;
final String id;
final String checksum;
final bool isFavorite;
@@ -1168,7 +1165,7 @@ class RemoteAssetEntityData extends i0.DataClass
required this.updatedAt,
this.width,
this.height,
this.durationInSeconds,
this.durationMs,
required this.id,
required this.checksum,
required this.isFavorite,
@@ -1199,8 +1196,8 @@ class RemoteAssetEntityData extends i0.DataClass
if (!nullToAbsent || height != null) {
map['height'] = i0.Variable<int>(height);
}
if (!nullToAbsent || durationInSeconds != null) {
map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds);
if (!nullToAbsent || durationMs != null) {
map['duration_ms'] = i0.Variable<int>(durationMs);
}
map['id'] = i0.Variable<String>(id);
map['checksum'] = i0.Variable<String>(checksum);
@@ -1247,7 +1244,7 @@ class RemoteAssetEntityData extends i0.DataClass
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
width: serializer.fromJson<int?>(json['width']),
height: serializer.fromJson<int?>(json['height']),
durationInSeconds: serializer.fromJson<int?>(json['durationInSeconds']),
durationMs: serializer.fromJson<int?>(json['durationMs']),
id: serializer.fromJson<String>(json['id']),
checksum: serializer.fromJson<String>(json['checksum']),
isFavorite: serializer.fromJson<bool>(json['isFavorite']),
@@ -1276,7 +1273,7 @@ class RemoteAssetEntityData extends i0.DataClass
'updatedAt': serializer.toJson<DateTime>(updatedAt),
'width': serializer.toJson<int?>(width),
'height': serializer.toJson<int?>(height),
'durationInSeconds': serializer.toJson<int?>(durationInSeconds),
'durationMs': serializer.toJson<int?>(durationMs),
'id': serializer.toJson<String>(id),
'checksum': serializer.toJson<String>(checksum),
'isFavorite': serializer.toJson<bool>(isFavorite),
@@ -1301,7 +1298,7 @@ class RemoteAssetEntityData extends i0.DataClass
DateTime? updatedAt,
i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(),
String? id,
String? checksum,
bool? isFavorite,
@@ -1321,9 +1318,7 @@ class RemoteAssetEntityData extends i0.DataClass
updatedAt: updatedAt ?? this.updatedAt,
width: width.present ? width.value : this.width,
height: height.present ? height.value : this.height,
durationInSeconds: durationInSeconds.present
? durationInSeconds.value
: this.durationInSeconds,
durationMs: durationMs.present ? durationMs.value : this.durationMs,
id: id ?? this.id,
checksum: checksum ?? this.checksum,
isFavorite: isFavorite ?? this.isFavorite,
@@ -1349,9 +1344,9 @@ class RemoteAssetEntityData extends i0.DataClass
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
width: data.width.present ? data.width.value : this.width,
height: data.height.present ? data.height.value : this.height,
durationInSeconds: data.durationInSeconds.present
? data.durationInSeconds.value
: this.durationInSeconds,
durationMs: data.durationMs.present
? data.durationMs.value
: this.durationMs,
id: data.id.present ? data.id.value : this.id,
checksum: data.checksum.present ? data.checksum.value : this.checksum,
isFavorite: data.isFavorite.present
@@ -1384,7 +1379,7 @@ class RemoteAssetEntityData extends i0.DataClass
..write('updatedAt: $updatedAt, ')
..write('width: $width, ')
..write('height: $height, ')
..write('durationInSeconds: $durationInSeconds, ')
..write('durationMs: $durationMs, ')
..write('id: $id, ')
..write('checksum: $checksum, ')
..write('isFavorite: $isFavorite, ')
@@ -1409,7 +1404,7 @@ class RemoteAssetEntityData extends i0.DataClass
updatedAt,
width,
height,
durationInSeconds,
durationMs,
id,
checksum,
isFavorite,
@@ -1433,7 +1428,7 @@ class RemoteAssetEntityData extends i0.DataClass
other.updatedAt == this.updatedAt &&
other.width == this.width &&
other.height == this.height &&
other.durationInSeconds == this.durationInSeconds &&
other.durationMs == this.durationMs &&
other.id == this.id &&
other.checksum == this.checksum &&
other.isFavorite == this.isFavorite &&
@@ -1456,7 +1451,7 @@ class RemoteAssetEntityCompanion
final i0.Value<DateTime> updatedAt;
final i0.Value<int?> width;
final i0.Value<int?> height;
final i0.Value<int?> durationInSeconds;
final i0.Value<int?> durationMs;
final i0.Value<String> id;
final i0.Value<String> checksum;
final i0.Value<bool> isFavorite;
@@ -1476,7 +1471,7 @@ class RemoteAssetEntityCompanion
this.updatedAt = const i0.Value.absent(),
this.width = const i0.Value.absent(),
this.height = const i0.Value.absent(),
this.durationInSeconds = const i0.Value.absent(),
this.durationMs = const i0.Value.absent(),
this.id = const i0.Value.absent(),
this.checksum = const i0.Value.absent(),
this.isFavorite = const i0.Value.absent(),
@@ -1497,7 +1492,7 @@ class RemoteAssetEntityCompanion
this.updatedAt = const i0.Value.absent(),
this.width = const i0.Value.absent(),
this.height = const i0.Value.absent(),
this.durationInSeconds = const i0.Value.absent(),
this.durationMs = const i0.Value.absent(),
required String id,
required String checksum,
this.isFavorite = const i0.Value.absent(),
@@ -1523,7 +1518,7 @@ class RemoteAssetEntityCompanion
i0.Expression<DateTime>? updatedAt,
i0.Expression<int>? width,
i0.Expression<int>? height,
i0.Expression<int>? durationInSeconds,
i0.Expression<int>? durationMs,
i0.Expression<String>? id,
i0.Expression<String>? checksum,
i0.Expression<bool>? isFavorite,
@@ -1544,7 +1539,7 @@ class RemoteAssetEntityCompanion
if (updatedAt != null) 'updated_at': updatedAt,
if (width != null) 'width': width,
if (height != null) 'height': height,
if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds,
if (durationMs != null) 'duration_ms': durationMs,
if (id != null) 'id': id,
if (checksum != null) 'checksum': checksum,
if (isFavorite != null) 'is_favorite': isFavorite,
@@ -1567,7 +1562,7 @@ class RemoteAssetEntityCompanion
i0.Value<DateTime>? updatedAt,
i0.Value<int?>? width,
i0.Value<int?>? height,
i0.Value<int?>? durationInSeconds,
i0.Value<int?>? durationMs,
i0.Value<String>? id,
i0.Value<String>? checksum,
i0.Value<bool>? isFavorite,
@@ -1588,7 +1583,7 @@ class RemoteAssetEntityCompanion
updatedAt: updatedAt ?? this.updatedAt,
width: width ?? this.width,
height: height ?? this.height,
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
durationMs: durationMs ?? this.durationMs,
id: id ?? this.id,
checksum: checksum ?? this.checksum,
isFavorite: isFavorite ?? this.isFavorite,
@@ -1627,8 +1622,8 @@ class RemoteAssetEntityCompanion
if (height.present) {
map['height'] = i0.Variable<int>(height.value);
}
if (durationInSeconds.present) {
map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds.value);
if (durationMs.present) {
map['duration_ms'] = i0.Variable<int>(durationMs.value);
}
if (id.present) {
map['id'] = i0.Variable<String>(id.value);
@@ -1680,7 +1675,7 @@ class RemoteAssetEntityCompanion
..write('updatedAt: $updatedAt, ')
..write('width: $width, ')
..write('height: $height, ')
..write('durationInSeconds: $durationInSeconds, ')
..write('durationMs: $durationMs, ')
..write('id: $id, ')
..write('checksum: $checksum, ')
..write('isFavorite: $isFavorite, ')

View File

@@ -42,7 +42,7 @@ extension TrashedLocalAssetEntityDataDomainExtension on TrashedLocalAssetEntityD
type: type,
createdAt: createdAt,
updatedAt: updatedAt,
durationInSeconds: durationInSeconds,
durationMs: durationMs,
isFavorite: isFavorite,
height: height,
width: width,

View File

@@ -16,7 +16,7 @@ typedef $$TrashedLocalAssetEntityTableCreateCompanionBuilder =
i0.Value<DateTime> updatedAt,
i0.Value<int?> width,
i0.Value<int?> height,
i0.Value<int?> durationInSeconds,
i0.Value<int?> durationMs,
required String id,
required String albumId,
i0.Value<String?> checksum,
@@ -33,7 +33,7 @@ typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
i0.Value<DateTime> updatedAt,
i0.Value<int?> width,
i0.Value<int?> height,
i0.Value<int?> durationInSeconds,
i0.Value<int?> durationMs,
i0.Value<String> id,
i0.Value<String> albumId,
i0.Value<String?> checksum,
@@ -84,8 +84,8 @@ class $$TrashedLocalAssetEntityTableFilterComposer
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<int> get durationInSeconds => $composableBuilder(
column: $table.durationInSeconds,
i0.ColumnFilters<int> get durationMs => $composableBuilder(
column: $table.durationMs,
builder: (column) => i0.ColumnFilters(column),
);
@@ -171,8 +171,8 @@ class $$TrashedLocalAssetEntityTableOrderingComposer
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<int> get durationInSeconds => $composableBuilder(
column: $table.durationInSeconds,
i0.ColumnOrderings<int> get durationMs => $composableBuilder(
column: $table.durationMs,
builder: (column) => i0.ColumnOrderings(column),
);
@@ -240,8 +240,8 @@ class $$TrashedLocalAssetEntityTableAnnotationComposer
i0.GeneratedColumn<int> get height =>
$composableBuilder(column: $table.height, builder: (column) => column);
i0.GeneratedColumn<int> get durationInSeconds => $composableBuilder(
column: $table.durationInSeconds,
i0.GeneratedColumn<int> get durationMs => $composableBuilder(
column: $table.durationMs,
builder: (column) => column,
);
@@ -326,7 +326,7 @@ class $$TrashedLocalAssetEntityTableTableManager
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(),
i0.Value<String> id = const i0.Value.absent(),
i0.Value<String> albumId = const i0.Value.absent(),
i0.Value<String?> checksum = const i0.Value.absent(),
@@ -342,7 +342,7 @@ class $$TrashedLocalAssetEntityTableTableManager
updatedAt: updatedAt,
width: width,
height: height,
durationInSeconds: durationInSeconds,
durationMs: durationMs,
id: id,
albumId: albumId,
checksum: checksum,
@@ -359,7 +359,7 @@ class $$TrashedLocalAssetEntityTableTableManager
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(),
required String id,
required String albumId,
i0.Value<String?> checksum = const i0.Value.absent(),
@@ -375,7 +375,7 @@ class $$TrashedLocalAssetEntityTableTableManager
updatedAt: updatedAt,
width: width,
height: height,
durationInSeconds: durationInSeconds,
durationMs: durationMs,
id: id,
albumId: albumId,
checksum: checksum,
@@ -498,17 +498,17 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
type: i0.DriftSqlType.int,
requiredDuringInsert: false,
);
static const i0.VerificationMeta _durationInSecondsMeta =
const i0.VerificationMeta('durationInSeconds');
static const i0.VerificationMeta _durationMsMeta = const i0.VerificationMeta(
'durationMs',
);
@override
late final i0.GeneratedColumn<int> durationInSeconds =
i0.GeneratedColumn<int>(
'duration_in_seconds',
aliasedName,
true,
type: i0.DriftSqlType.int,
requiredDuringInsert: false,
);
late final i0.GeneratedColumn<int> durationMs = i0.GeneratedColumn<int>(
'duration_ms',
aliasedName,
true,
type: i0.DriftSqlType.int,
requiredDuringInsert: false,
);
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
@override
late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>(
@@ -599,7 +599,7 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
updatedAt,
width,
height,
durationInSeconds,
durationMs,
id,
albumId,
checksum,
@@ -652,13 +652,10 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
height.isAcceptableOrUnknown(data['height']!, _heightMeta),
);
}
if (data.containsKey('duration_in_seconds')) {
if (data.containsKey('duration_ms')) {
context.handle(
_durationInSecondsMeta,
durationInSeconds.isAcceptableOrUnknown(
data['duration_in_seconds']!,
_durationInSecondsMeta,
),
_durationMsMeta,
durationMs.isAcceptableOrUnknown(data['duration_ms']!, _durationMsMeta),
);
}
if (data.containsKey('id')) {
@@ -733,9 +730,9 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
i0.DriftSqlType.int,
data['${effectivePrefix}height'],
),
durationInSeconds: attachedDatabase.typeMapping.read(
durationMs: attachedDatabase.typeMapping.read(
i0.DriftSqlType.int,
data['${effectivePrefix}duration_in_seconds'],
data['${effectivePrefix}duration_ms'],
),
id: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
@@ -800,7 +797,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
final DateTime updatedAt;
final int? width;
final int? height;
final int? durationInSeconds;
final int? durationMs;
final String id;
final String albumId;
final String? checksum;
@@ -815,7 +812,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
required this.updatedAt,
this.width,
this.height,
this.durationInSeconds,
this.durationMs,
required this.id,
required this.albumId,
this.checksum,
@@ -841,8 +838,8 @@ class TrashedLocalAssetEntityData extends i0.DataClass
if (!nullToAbsent || height != null) {
map['height'] = i0.Variable<int>(height);
}
if (!nullToAbsent || durationInSeconds != null) {
map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds);
if (!nullToAbsent || durationMs != null) {
map['duration_ms'] = i0.Variable<int>(durationMs);
}
map['id'] = i0.Variable<String>(id);
map['album_id'] = i0.Variable<String>(albumId);
@@ -880,7 +877,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
width: serializer.fromJson<int?>(json['width']),
height: serializer.fromJson<int?>(json['height']),
durationInSeconds: serializer.fromJson<int?>(json['durationInSeconds']),
durationMs: serializer.fromJson<int?>(json['durationMs']),
id: serializer.fromJson<String>(json['id']),
albumId: serializer.fromJson<String>(json['albumId']),
checksum: serializer.fromJson<String?>(json['checksum']),
@@ -905,7 +902,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
'updatedAt': serializer.toJson<DateTime>(updatedAt),
'width': serializer.toJson<int?>(width),
'height': serializer.toJson<int?>(height),
'durationInSeconds': serializer.toJson<int?>(durationInSeconds),
'durationMs': serializer.toJson<int?>(durationMs),
'id': serializer.toJson<String>(id),
'albumId': serializer.toJson<String>(albumId),
'checksum': serializer.toJson<String?>(checksum),
@@ -929,7 +926,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
DateTime? updatedAt,
i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(),
String? id,
String? albumId,
i0.Value<String?> checksum = const i0.Value.absent(),
@@ -944,9 +941,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
updatedAt: updatedAt ?? this.updatedAt,
width: width.present ? width.value : this.width,
height: height.present ? height.value : this.height,
durationInSeconds: durationInSeconds.present
? durationInSeconds.value
: this.durationInSeconds,
durationMs: durationMs.present ? durationMs.value : this.durationMs,
id: id ?? this.id,
albumId: albumId ?? this.albumId,
checksum: checksum.present ? checksum.value : this.checksum,
@@ -965,9 +960,9 @@ class TrashedLocalAssetEntityData extends i0.DataClass
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
width: data.width.present ? data.width.value : this.width,
height: data.height.present ? data.height.value : this.height,
durationInSeconds: data.durationInSeconds.present
? data.durationInSeconds.value
: this.durationInSeconds,
durationMs: data.durationMs.present
? data.durationMs.value
: this.durationMs,
id: data.id.present ? data.id.value : this.id,
albumId: data.albumId.present ? data.albumId.value : this.albumId,
checksum: data.checksum.present ? data.checksum.value : this.checksum,
@@ -993,7 +988,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
..write('updatedAt: $updatedAt, ')
..write('width: $width, ')
..write('height: $height, ')
..write('durationInSeconds: $durationInSeconds, ')
..write('durationMs: $durationMs, ')
..write('id: $id, ')
..write('albumId: $albumId, ')
..write('checksum: $checksum, ')
@@ -1013,7 +1008,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
updatedAt,
width,
height,
durationInSeconds,
durationMs,
id,
albumId,
checksum,
@@ -1032,7 +1027,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
other.updatedAt == this.updatedAt &&
other.width == this.width &&
other.height == this.height &&
other.durationInSeconds == this.durationInSeconds &&
other.durationMs == this.durationMs &&
other.id == this.id &&
other.albumId == this.albumId &&
other.checksum == this.checksum &&
@@ -1050,7 +1045,7 @@ class TrashedLocalAssetEntityCompanion
final i0.Value<DateTime> updatedAt;
final i0.Value<int?> width;
final i0.Value<int?> height;
final i0.Value<int?> durationInSeconds;
final i0.Value<int?> durationMs;
final i0.Value<String> id;
final i0.Value<String> albumId;
final i0.Value<String?> checksum;
@@ -1065,7 +1060,7 @@ class TrashedLocalAssetEntityCompanion
this.updatedAt = const i0.Value.absent(),
this.width = const i0.Value.absent(),
this.height = const i0.Value.absent(),
this.durationInSeconds = const i0.Value.absent(),
this.durationMs = const i0.Value.absent(),
this.id = const i0.Value.absent(),
this.albumId = const i0.Value.absent(),
this.checksum = const i0.Value.absent(),
@@ -1081,7 +1076,7 @@ class TrashedLocalAssetEntityCompanion
this.updatedAt = const i0.Value.absent(),
this.width = const i0.Value.absent(),
this.height = const i0.Value.absent(),
this.durationInSeconds = const i0.Value.absent(),
this.durationMs = const i0.Value.absent(),
required String id,
required String albumId,
this.checksum = const i0.Value.absent(),
@@ -1101,7 +1096,7 @@ class TrashedLocalAssetEntityCompanion
i0.Expression<DateTime>? updatedAt,
i0.Expression<int>? width,
i0.Expression<int>? height,
i0.Expression<int>? durationInSeconds,
i0.Expression<int>? durationMs,
i0.Expression<String>? id,
i0.Expression<String>? albumId,
i0.Expression<String>? checksum,
@@ -1117,7 +1112,7 @@ class TrashedLocalAssetEntityCompanion
if (updatedAt != null) 'updated_at': updatedAt,
if (width != null) 'width': width,
if (height != null) 'height': height,
if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds,
if (durationMs != null) 'duration_ms': durationMs,
if (id != null) 'id': id,
if (albumId != null) 'album_id': albumId,
if (checksum != null) 'checksum': checksum,
@@ -1135,7 +1130,7 @@ class TrashedLocalAssetEntityCompanion
i0.Value<DateTime>? updatedAt,
i0.Value<int?>? width,
i0.Value<int?>? height,
i0.Value<int?>? durationInSeconds,
i0.Value<int?>? durationMs,
i0.Value<String>? id,
i0.Value<String>? albumId,
i0.Value<String?>? checksum,
@@ -1151,7 +1146,7 @@ class TrashedLocalAssetEntityCompanion
updatedAt: updatedAt ?? this.updatedAt,
width: width ?? this.width,
height: height ?? this.height,
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
durationMs: durationMs ?? this.durationMs,
id: id ?? this.id,
albumId: albumId ?? this.albumId,
checksum: checksum ?? this.checksum,
@@ -1185,8 +1180,8 @@ class TrashedLocalAssetEntityCompanion
if (height.present) {
map['height'] = i0.Variable<int>(height.value);
}
if (durationInSeconds.present) {
map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds.value);
if (durationMs.present) {
map['duration_ms'] = i0.Variable<int>(durationMs.value);
}
if (id.present) {
map['id'] = i0.Variable<String>(id.value);
@@ -1227,7 +1222,7 @@ class TrashedLocalAssetEntityCompanion
..write('updatedAt: $updatedAt, ')
..write('width: $width, ')
..write('height: $height, ')
..write('durationInSeconds: $durationInSeconds, ')
..write('durationMs: $durationMs, ')
..write('id: $id, ')
..write('albumId: $albumId, ')
..write('checksum: $checksum, ')

View File

@@ -5,11 +5,13 @@ import 'package:drift_flutter/drift_flutter.dart';
import 'package:flutter/foundation.dart';
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.dart';
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart';
import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.dart';
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.dart';
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/memory.entity.dart';
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
@@ -18,10 +20,12 @@ import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.dart';
import 'package:immich_mobile/infrastructure/entities/stack.entity.dart';
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.drift.dart';
@@ -50,6 +54,7 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.steps.da
StoreEntity,
TrashedLocalAssetEntity,
AssetEditEntity,
AssetOcrEntity,
],
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
)
@@ -81,7 +86,7 @@ class Drift extends $Drift {
}
@override
int get schemaVersion => 22;
int get schemaVersion => 25;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -222,6 +227,34 @@ class Drift extends $Drift {
await m.createTable(v22.assetEditEntity);
await m.createIndex(v22.idxAssetEditAssetId);
},
from22To23: (m, v23) async {
await m.renameColumn(v23.localAssetEntity, 'duration_in_seconds', v23.localAssetEntity.durationMs);
await m.renameColumn(v23.remoteAssetEntity, 'duration_in_seconds', v23.remoteAssetEntity.durationMs);
await m.renameColumn(
v23.trashedLocalAssetEntity,
'duration_in_seconds',
v23.trashedLocalAssetEntity.durationMs,
);
await localAssetEntity.update().write(
LocalAssetEntityCompanion.custom(durationMs: v23.localAssetEntity.durationMs * const Constant(1000)),
);
await remoteAssetEntity.update().write(
RemoteAssetEntityCompanion.custom(durationMs: v23.remoteAssetEntity.durationMs * const Constant(1000)),
);
await trashedLocalAssetEntity.update().write(
TrashedLocalAssetEntityCompanion.custom(
durationMs: v23.trashedLocalAssetEntity.durationMs * const Constant(1000),
),
);
},
from23To24: (m, v24) async {
await customStatement('DROP INDEX IF EXISTS idx_remote_album_owner_id');
await m.alterTable(TableMigration(v24.remoteAlbumEntity));
},
from24To25: (m, v25) async {
await m.create(v25.assetOcrEntity);
},
),
);

View File

@@ -43,9 +43,11 @@ import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity
as i20;
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart'
as i21;
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart'
as i22;
import 'package:drift/internal/modular.dart' as i23;
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
as i23;
import 'package:drift/internal/modular.dart' as i24;
abstract class $Drift extends i0.GeneratedDatabase {
$Drift(i0.QueryExecutor e) : super(e);
@@ -89,9 +91,12 @@ abstract class $Drift extends i0.GeneratedDatabase {
.$TrashedLocalAssetEntityTable(this);
late final i21.$AssetEditEntityTable assetEditEntity = i21
.$AssetEditEntityTable(this);
i22.MergedAssetDrift get mergedAssetDrift => i23.ReadDatabaseContainer(
late final i22.$AssetOcrEntityTable assetOcrEntity = i22.$AssetOcrEntityTable(
this,
).accessor<i22.MergedAssetDrift>(i22.MergedAssetDrift.new);
);
i23.MergedAssetDrift get mergedAssetDrift => i24.ReadDatabaseContainer(
this,
).accessor<i23.MergedAssetDrift>(i23.MergedAssetDrift.new);
@override
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
@@ -105,7 +110,6 @@ abstract class $Drift extends i0.GeneratedDatabase {
localAlbumEntity,
localAlbumAssetEntity,
i7.idxLocalAlbumAssetAlbumAsset,
i5.idxRemoteAlbumOwnerId,
i4.idxLocalAssetChecksum,
i4.idxLocalAssetCloudId,
i3.idxStackPrimaryAssetId,
@@ -130,6 +134,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
storeEntity,
trashedLocalAssetEntity,
assetEditEntity,
assetOcrEntity,
i10.idxPartnerSharedWithId,
i11.idxLatLng,
i12.idxRemoteAlbumAssetAlbumAsset,
@@ -160,15 +165,6 @@ abstract class $Drift extends i0.GeneratedDatabase {
),
result: [i0.TableUpdate('stack_entity', kind: i0.UpdateKind.delete)],
),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName(
'user_entity',
limitUpdateKind: i0.UpdateKind.delete,
),
result: [
i0.TableUpdate('remote_album_entity', kind: i0.UpdateKind.delete),
],
),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName(
'remote_asset_entity',
@@ -338,6 +334,13 @@ abstract class $Drift extends i0.GeneratedDatabase {
),
result: [i0.TableUpdate('asset_edit_entity', kind: i0.UpdateKind.delete)],
),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName(
'remote_asset_entity',
limitUpdateKind: i0.UpdateKind.delete,
),
result: [i0.TableUpdate('asset_ocr_entity', kind: i0.UpdateKind.delete)],
),
]);
@override
i0.DriftDatabaseOptions get options =>
@@ -399,4 +402,6 @@ class $DriftManager {
);
i21.$$AssetEditEntityTableTableManager get assetEditEntity =>
i21.$$AssetEditEntityTableTableManager(_db, _db.assetEditEntity);
i22.$$AssetOcrEntityTableTableManager get assetOcrEntity =>
i22.$$AssetOcrEntityTableTableManager(_db, _db.assetOcrEntity);
}

File diff suppressed because it is too large Load Diff

View File

@@ -297,7 +297,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
updatedAt: Value(asset.updatedAt),
width: Value(asset.width),
height: Value(asset.height),
durationInSeconds: Value(asset.durationInSeconds),
durationMs: Value(asset.durationMs),
id: asset.id,
orientation: Value(asset.orientation),
isFavorite: Value(asset.isFavorite),
@@ -329,7 +329,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
updatedAt: Value(asset.updatedAt),
width: Value(asset.width),
height: Value(asset.height),
durationInSeconds: Value(asset.durationInSeconds),
durationMs: Value(asset.durationMs),
id: asset.id,
checksum: const Value(null),
orientation: Value(asset.orientation),

View File

@@ -0,0 +1,36 @@
import 'package:immich_mobile/domain/models/ocr.model.dart';
import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
class OcrRepository extends DriftDatabaseRepository {
final Drift _db;
const OcrRepository(this._db) : super(_db);
Future<List<Ocr>?> get(String assetId) async {
final query = _db.select(_db.assetOcrEntity)..where((row) => row.assetId.equals(assetId));
final result = await query.get();
return result.map((e) => e.toDto()).toList();
}
}
extension on AssetOcrEntityData {
Ocr toDto() {
return Ocr(
id: id,
assetId: assetId,
x1: x1,
y1: y1,
x2: x2,
y2: y2,
x3: x3,
y3: y3,
x4: x4,
y4: y4,
boxScore: boxScore,
textScore: textScore,
text: recognizedText,
isVisible: isVisible,
);
}
}

View File

@@ -32,17 +32,23 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
useColumns: false,
),
leftOuterJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), useColumns: false),
leftOuterJoin(
_db.remoteAlbumUserEntity,
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
useColumns: false,
),
innerJoin(
_db.userEntity,
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) &
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
useColumns: false,
),
]);
query
..where(_db.remoteAssetEntity.deletedAt.isNull())
..addColumns([assetCount])
..addColumns([_db.userEntity.name])
..addColumns([_db.userEntity.name, _db.userEntity.id])
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
..groupBy([_db.remoteAlbumEntity.id]);
@@ -63,6 +69,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
.readTable(_db.remoteAlbumEntity)
.toDto(
assetCount: row.read(assetCount) ?? 0,
ownerId: row.read(_db.userEntity.id)!,
ownerName: row.read(_db.userEntity.name)!,
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
),
@@ -85,20 +92,22 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
useColumns: false,
),
leftOuterJoin(
_db.userEntity,
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
useColumns: false,
),
leftOuterJoin(
_db.remoteAlbumUserEntity,
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
useColumns: false,
),
innerJoin(
_db.userEntity,
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) &
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
useColumns: false,
),
])
..where(_db.remoteAlbumEntity.id.equals(albumId) & _db.remoteAssetEntity.deletedAt.isNull())
..addColumns([assetCount])
..addColumns([_db.userEntity.name])
..addColumns([_db.userEntity.name, _db.userEntity.id])
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
..groupBy([_db.remoteAlbumEntity.id]);
@@ -108,6 +117,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
.readTable(_db.remoteAlbumEntity)
.toDto(
assetCount: row.read(assetCount) ?? 0,
ownerId: row.read(_db.userEntity.id)!,
ownerName: row.read(_db.userEntity.name)!,
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
),
@@ -116,12 +126,29 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
}
Future<RemoteAlbum?> getByName(String albumName, String ownerId) {
final query = _db.remoteAlbumEntity.select()
..where((row) => row.name.equals(albumName) & row.ownerId.equals(ownerId))
..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
final query = _db.remoteAlbumEntity.select().join([
innerJoin(
_db.remoteAlbumUserEntity,
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
_db.remoteAlbumUserEntity.userId.equals(ownerId) &
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
useColumns: false,
),
]);
query
..addColumns([_db.remoteAlbumUserEntity.userId])
..where(_db.remoteAlbumEntity.name.equals(albumName))
..orderBy([OrderingTerm.desc(_db.remoteAlbumEntity.createdAt)])
..limit(1);
return query.map((row) => row.toDto(ownerName: '', isShared: false)).getSingleOrNull();
return query
.map(
(row) => row
.readTable(_db.remoteAlbumEntity)
.toDto(ownerId: row.read(_db.remoteAlbumUserEntity.userId)!, ownerName: '', isShared: false),
)
.getSingleOrNull();
}
Future<void> create(RemoteAlbum album, List<String> assetIds) async {
@@ -129,7 +156,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
final entity = RemoteAlbumEntityCompanion(
id: Value(album.id),
name: Value(album.name),
ownerId: Value(album.ownerId),
createdAt: Value(album.createdAt),
updatedAt: Value(album.updatedAt),
description: Value(album.description),
@@ -140,6 +166,14 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
await _db.remoteAlbumEntity.insertOne(entity);
await _db.remoteAlbumUserEntity.insertOne(
RemoteAlbumUserEntityCompanion(
albumId: Value(album.id),
userId: Value(album.ownerId),
role: const Value(AlbumUserRole.owner),
),
);
if (assetIds.isNotEmpty) {
final albumAssets = assetIds.map(
(assetId) => RemoteAlbumAssetEntityCompanion(albumId: Value(album.id), assetId: Value(assetId)),
@@ -157,7 +191,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
RemoteAlbumEntityCompanion(
id: Value(album.id),
name: Value(album.name),
ownerId: Value(album.ownerId),
createdAt: Value(album.createdAt),
updatedAt: Value(album.updatedAt),
description: Value(album.description),
@@ -197,7 +230,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
Future<List<UserDto>> getSharedUsers(String albumId) async {
final albumUserRows = await (_db.select(
_db.remoteAlbumUserEntity,
)..where((row) => row.albumId.equals(albumId))).get();
)..where((row) => row.albumId.equals(albumId) & row.role.isNotValue(AlbumUserRole.owner.index))).get();
if (albumUserRows.isEmpty) {
return [];
@@ -295,19 +328,21 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
useColumns: false,
),
leftOuterJoin(
_db.userEntity,
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
useColumns: false,
),
leftOuterJoin(
_db.remoteAlbumUserEntity,
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
useColumns: false,
),
innerJoin(
_db.userEntity,
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) &
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
useColumns: false,
),
])
..where(_db.remoteAlbumEntity.id.equals(albumId))
..addColumns([_db.userEntity.name])
..addColumns([_db.userEntity.name, _db.userEntity.id])
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
..groupBy([_db.remoteAlbumEntity.id]);
@@ -315,6 +350,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
final album = row
.readTable(_db.remoteAlbumEntity)
.toDto(
ownerId: row.read(_db.userEntity.id)!,
ownerName: row.read(_db.userEntity.name)!,
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
);
@@ -355,6 +391,37 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
return _db.managers.remoteAlbumEntity.count();
}
Future<UserDto> getOwner(String albumId) {
final query =
_db.userEntity.select().join([
innerJoin(
_db.remoteAlbumUserEntity,
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId),
useColumns: false,
),
])..where(
_db.remoteAlbumUserEntity.albumId.equals(albumId) &
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
);
return query
.map(
(row) => UserDto(
id: row.read(_db.userEntity.id)!,
email: row.read(_db.userEntity.email)!,
name: row.read(_db.userEntity.name)!,
memoryEnabled: true,
inTimeline: false,
isPartnerSharedBy: false,
isPartnerSharedWith: false,
profileChangedAt: row.read(_db.userEntity.profileChangedAt)!,
hasProfileImage: row.read(_db.userEntity.hasProfileImage)!,
avatarColor: AvatarColor.values[row.read(_db.userEntity.avatarColor)!],
),
)
.getSingle();
}
Future<List<String>> getLinkedAssetIds(String userId, String localAlbumId, String remoteAlbumId) async {
// Find remote asset ids that:
// 1. Belong to the provided local album (via local_album_asset_entity)
@@ -416,21 +483,23 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
useColumns: false,
),
leftOuterJoin(
_db.userEntity,
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
useColumns: false,
),
leftOuterJoin(
_db.remoteAlbumUserEntity,
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
useColumns: false,
),
innerJoin(
_db.userEntity,
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) &
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
useColumns: false,
),
])
..where(_db.remoteAlbumEntity.id.isIn(albumIds) & _db.remoteAssetEntity.deletedAt.isNull())
..addColumns([assetCount])
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
..addColumns([_db.userEntity.name])
..addColumns([_db.userEntity.name, _db.userEntity.id])
..groupBy([_db.remoteAlbumEntity.id]);
return query
@@ -438,6 +507,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
(row) => row
.readTable(_db.remoteAlbumEntity)
.toDto(
ownerId: row.read(_db.userEntity.id)!,
ownerName: row.read(_db.userEntity.name) ?? '',
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
assetCount: row.read(assetCount) ?? 0,
@@ -448,7 +518,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
}
extension on RemoteAlbumEntityData {
RemoteAlbum toDto({int assetCount = 0, required String ownerName, required bool isShared}) {
RemoteAlbum toDto({int assetCount = 0, required String ownerName, required String ownerId, required bool isShared}) {
return RemoteAlbum(
id: id,
name: name,

View File

@@ -53,7 +53,10 @@ class SyncApiRepository {
SyncRequestType.partnersV1,
SyncRequestType.partnerAssetsV1,
SyncRequestType.partnerAssetExifsV1,
SyncRequestType.albumsV1,
if (serverVersion < const SemVer(major: 3, minor: 0, patch: 0))
SyncRequestType.albumsV1
else
SyncRequestType.albumsV2,
SyncRequestType.albumUsersV1,
SyncRequestType.albumAssetsV1,
SyncRequestType.albumAssetExifsV1,
@@ -66,6 +69,7 @@ class SyncApiRepository {
SyncRequestType.peopleV1,
if (serverVersion < const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV1,
if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV2,
if (serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)) SyncRequestType.assetOcrV1,
],
reset: shouldReset,
).toJson(),
@@ -162,6 +166,7 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson,
SyncEntityType.partnerAssetExifBackfillV1: SyncAssetExifV1.fromJson,
SyncEntityType.albumV1: SyncAlbumV1.fromJson,
SyncEntityType.albumV2: SyncAlbumV2.fromJson,
SyncEntityType.albumDeleteV1: SyncAlbumDeleteV1.fromJson,
SyncEntityType.albumUserV1: SyncAlbumUserV1.fromJson,
SyncEntityType.albumUserBackfillV1: SyncAlbumUserV1.fromJson,
@@ -193,6 +198,8 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
SyncEntityType.assetFaceV1: SyncAssetFaceV1.fromJson,
SyncEntityType.assetFaceV2: SyncAssetFaceV2.fromJson,
SyncEntityType.assetFaceDeleteV1: SyncAssetFaceDeleteV1.fromJson,
SyncEntityType.assetOcrV1: SyncAssetOcrV1.fromJson,
SyncEntityType.assetOcrDeleteV1: SyncAssetOcrDeleteV1.fromJson,
SyncEntityType.syncCompleteV1: _SyncEmptyDto.fromJson,
};

View File

@@ -9,8 +9,10 @@ import 'package:immich_mobile/domain/models/asset_edit.model.dart';
import 'package:immich_mobile/domain/models/memory.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
import 'package:immich_mobile/extensions/string_extensions.dart';
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart';
@@ -29,7 +31,7 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart' as api show AssetVisibility, AlbumUserRole, UserMetadataKey, AssetEditAction;
import 'package:openapi/api.dart' hide AlbumUserRole, UserMetadataKey, AssetEditAction, AssetVisibility;
import 'package:openapi/api.dart' hide UserMetadataKey, AssetEditAction, AssetVisibility, AlbumUserRole;
class SyncStreamRepository extends DriftDatabaseRepository {
final Logger _logger = Logger('DriftSyncStreamRepository');
@@ -61,6 +63,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
await _db.userMetadataEntity.deleteAll();
await _db.remoteAssetCloudIdEntity.deleteAll();
await _db.assetEditEntity.deleteAll();
await _db.assetOcrEntity.deleteAll();
});
await _db.customStatement('PRAGMA foreign_keys = ON');
});
@@ -190,7 +193,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
type: Value(asset.type.toAssetType()),
createdAt: Value.absentIfNull(asset.fileCreatedAt),
updatedAt: Value.absentIfNull(asset.fileModifiedAt),
durationInSeconds: Value(asset.duration?.toDuration()?.inSeconds ?? 0),
durationMs: Value(asset.duration?.toDuration()?.inMilliseconds ?? 0),
checksum: Value(asset.checksum),
isFavorite: Value(asset.isFavorite),
ownerId: Value(asset.ownerId),
@@ -396,6 +399,47 @@ class SyncStreamRepository extends DriftDatabaseRepository {
}
Future<void> updateAlbumsV1(Iterable<SyncAlbumV1> data) async {
try {
await _db.transaction(() async {
await _db.batch((batch) {
for (final album in data) {
final companion = RemoteAlbumEntityCompanion(
name: Value(album.name),
description: Value(album.description),
isActivityEnabled: Value(album.isActivityEnabled),
order: Value(album.order.toAlbumAssetOrder()),
thumbnailAssetId: Value(album.thumbnailAssetId),
createdAt: Value(album.createdAt),
updatedAt: Value(album.updatedAt),
);
batch.insert(
_db.remoteAlbumEntity,
companion.copyWith(id: Value(album.id)),
onConflict: DoUpdate((_) => companion),
);
}
});
await _db.batch((batch) {
for (final album in data) {
final companion = RemoteAlbumUserEntityCompanion(
albumId: Value(album.id),
userId: Value(album.ownerId),
role: const Value(AlbumUserRole.owner),
);
batch.insert(_db.remoteAlbumUserEntity, companion, onConflict: DoUpdate((_) => companion));
}
});
});
} catch (error, stack) {
_logger.severe('Error: updateAlbumsV1', error, stack);
rethrow;
}
}
Future<void> updateAlbumsV2(Iterable<SyncAlbumV2> data) async {
try {
await _db.batch((batch) {
for (final album in data) {
@@ -405,7 +449,6 @@ class SyncStreamRepository extends DriftDatabaseRepository {
isActivityEnabled: Value(album.isActivityEnabled),
order: Value(album.order.toAlbumAssetOrder()),
thumbnailAssetId: Value(album.thumbnailAssetId),
ownerId: Value(album.ownerId),
createdAt: Value(album.createdAt),
updatedAt: Value(album.updatedAt),
);
@@ -418,7 +461,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
}
});
} catch (error, stack) {
_logger.severe('Error: updateAlbumsV1', error, stack);
_logger.severe('Error: updateAlbumsV2', error, stack);
rethrow;
}
}
@@ -756,6 +799,53 @@ class SyncStreamRepository extends DriftDatabaseRepository {
}
}
Future<void> updateAssetOcrV1(Iterable<SyncAssetOcrV1> data) async {
try {
await _db.batch((batch) {
for (final assetOcr in data) {
final companion = AssetOcrEntityCompanion(
id: Value(assetOcr.id),
assetId: Value(assetOcr.assetId),
recognizedText: Value(assetOcr.text),
x1: Value(assetOcr.x1),
y1: Value(assetOcr.y1),
x2: Value(assetOcr.x2),
y2: Value(assetOcr.y2),
x3: Value(assetOcr.x3),
y3: Value(assetOcr.y3),
x4: Value(assetOcr.x4),
y4: Value(assetOcr.y4),
boxScore: Value(assetOcr.boxScore),
textScore: Value(assetOcr.textScore),
isVisible: Value(assetOcr.isVisible),
);
batch.insert(
_db.assetOcrEntity,
companion.copyWith(id: Value(assetOcr.id)),
onConflict: DoUpdate((_) => companion),
);
}
});
} catch (error, stack) {
_logger.severe('Error: updateAssetOcrV1', error, stack);
rethrow;
}
}
Future<void> deleteAssetOcrV1(Iterable<SyncAssetOcrDeleteV1> data) async {
try {
await _db.batch((batch) {
for (final assetOcr in data) {
batch.deleteWhere(_db.assetOcrEntity, (row) => row.id.equals(assetOcr.id));
}
});
} catch (error, stack) {
_logger.severe('Error: deleteAssetOcrV1', error, stack);
rethrow;
}
}
Future<void> pruneAssets() async {
try {
await _db.transaction(() async {
@@ -820,6 +910,7 @@ extension on api.AlbumUserRole {
AlbumUserRole toAlbumUserRole() => switch (this) {
api.AlbumUserRole.editor => AlbumUserRole.editor,
api.AlbumUserRole.viewer => AlbumUserRole.viewer,
api.AlbumUserRole.owner => AlbumUserRole.owner,
_ => throw Exception('Unknown AlbumUserRole value: $this'),
};
}
@@ -843,18 +934,6 @@ extension on api.UserMetadataKey {
};
}
extension on String {
Duration? toDuration() {
try {
final parts = split(':').map((e) => double.parse(e).toInt()).toList(growable: false);
return Duration(hours: parts[0], minutes: parts[1], seconds: parts[2]);
} catch (_) {
return null;
}
}
}
extension on UserAvatarColor {
AvatarColor? toAvatarColor() => AvatarColor.values.firstWhereOrNull((c) => c.name == value);
}

View File

@@ -83,7 +83,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
width: row.width,
height: row.height,
isFavorite: row.isFavorite,
durationInSeconds: row.durationInSeconds,
durationMs: row.durationMs,
livePhotoVideoId: row.livePhotoVideoId,
stackId: row.stackId,
isEdited: row.isEdited,
@@ -99,7 +99,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
width: row.width,
height: row.height,
isFavorite: row.isFavorite,
durationInSeconds: row.durationInSeconds,
durationMs: row.durationMs,
orientation: row.orientation,
playbackStyle: AssetPlaybackStyle.values[row.playbackStyle],
cloudId: row.iCloudId,

View File

@@ -82,7 +82,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
updatedAt: Value(item.asset.updatedAt),
width: Value(item.asset.width),
height: Value(item.asset.height),
durationInSeconds: Value(item.asset.durationInSeconds),
durationMs: Value(item.asset.durationMs),
isFavorite: Value(item.asset.isFavorite),
orientation: Value(item.asset.orientation),
playbackStyle: Value(item.asset.playbackStyle),
@@ -145,7 +145,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
type: Value(asset.type),
width: Value(asset.width),
height: Value(asset.height),
durationInSeconds: Value(asset.durationInSeconds),
durationMs: Value(asset.durationMs),
isFavorite: Value(asset.isFavorite),
orientation: Value(asset.orientation),
playbackStyle: Value(asset.playbackStyle),
@@ -193,7 +193,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
updatedAt: Value(e.updatedAt),
width: Value(e.width),
height: Value(e.height),
durationInSeconds: Value(e.durationInSeconds),
durationMs: Value(e.durationMs),
checksum: Value(e.checksum),
isFavorite: Value(e.isFavorite),
orientation: Value(e.orientation),
@@ -244,7 +244,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
updatedAt: Value(e.asset.updatedAt),
width: Value(e.asset.width),
height: Value(e.asset.height),
durationInSeconds: Value(e.asset.durationInSeconds),
durationMs: Value(e.asset.durationMs),
checksum: Value(e.asset.checksum),
isFavorite: Value(e.asset.isFavorite),
orientation: Value(e.asset.orientation),

View File

@@ -8,5 +8,5 @@ mixin AssetEntityMixin on Table {
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
IntColumn get width => integer().nullable()();
IntColumn get height => integer().nullable()();
IntColumn get durationInSeconds => integer().nullable()();
IntColumn get durationMs => integer().nullable()();
}

View File

@@ -170,32 +170,19 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
final isColdStart = currentRouteName == null || currentRouteName == SplashScreenRoute.name;
PageRouteInfo? route;
if (deepLink.uri.scheme == "immich") {
route = await deepLinkHandler.handleScheme(deepLink, ref);
} else if (deepLink.uri.host == "my.immich.app") {
route = await deepLinkHandler.handleMyImmichApp(deepLink, ref);
} else {
return DeepLink.path(deepLink.path);
final proposedRoute = await deepLinkHandler.handleScheme(deepLink, ref, isColdStart);
return proposedRoute;
}
if (route == null) {
return isColdStart ? DeepLink.defaultPath : DeepLink.none;
if (deepLink.uri.host == "my.immich.app") {
final proposedRoute = await deepLinkHandler.handleMyImmichApp(deepLink, ref, isColdStart);
return proposedRoute;
}
// We need to replace the route if the destination is the current route
if (!isColdStart) {
unawaited(
ref.read(appRouterProvider).pushAndPopUntil(route, predicate: (r) => r.settings.name != route!.routeName),
);
return DeepLink.none;
}
return DeepLink([
// we need something to segue back to if the app was cold started
if (isColdStart) const TabShellRoute(children: [MainTimelineRoute()]),
route,
]);
return DeepLink.path(deepLink.path);
}
@override

View File

@@ -97,7 +97,7 @@ class PlatformAsset {
this.updatedAt,
this.width,
this.height,
required this.durationInSeconds,
required this.durationMs,
required this.orientation,
required this.isFavorite,
this.adjustmentTime,
@@ -120,7 +120,7 @@ class PlatformAsset {
int? height;
int durationInSeconds;
int durationMs;
int orientation;
@@ -143,7 +143,7 @@ class PlatformAsset {
updatedAt,
width,
height,
durationInSeconds,
durationMs,
orientation,
isFavorite,
adjustmentTime,
@@ -167,7 +167,7 @@ class PlatformAsset {
updatedAt: result[4] as int?,
width: result[5] as int?,
height: result[6] as int?,
durationInSeconds: result[7]! as int,
durationMs: result[7]! as int,
orientation: result[8]! as int,
isFavorite: result[9]! as bool,
adjustmentTime: result[10] as int?,
@@ -193,7 +193,7 @@ class PlatformAsset {
_deepEquals(updatedAt, other.updatedAt) &&
_deepEquals(width, other.width) &&
_deepEquals(height, other.height) &&
_deepEquals(durationInSeconds, other.durationInSeconds) &&
_deepEquals(durationMs, other.durationMs) &&
_deepEquals(orientation, other.orientation) &&
_deepEquals(isFavorite, other.isFavorite) &&
_deepEquals(adjustmentTime, other.adjustmentTime) &&

View File

@@ -112,10 +112,7 @@ class _AssetPropertiesSectionState extends ConsumerState<_AssetPropertiesSection
_PropertyItem(label: 'Updated At', value: asset.updatedAt.toString()),
_PropertyItem(label: 'Width', value: asset.width?.toString()),
_PropertyItem(label: 'Height', value: asset.height?.toString()),
_PropertyItem(
label: 'Duration',
value: asset.durationInSeconds != null ? '${asset.durationInSeconds} seconds' : null,
),
_PropertyItem(label: 'Duration', value: asset.durationMs != null ? '${asset.durationMs} ms' : null),
_PropertyItem(label: 'Is Favorite', value: asset.isFavorite.toString()),
_PropertyItem(label: 'Live Photo Video ID', value: asset.livePhotoVideoId),
_PropertyItem(label: 'Is Edited', value: asset.isEdited.toString()),

View File

@@ -13,6 +13,7 @@ import 'package:immich_mobile/extensions/scroll_extensions.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_details.widget.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widget.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/ocr_overlay.widget.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
@@ -356,6 +357,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
_showingDetails = ref.watch(assetViewerProvider.select((s) => s.showingDetails));
final stackIndex = ref.watch(assetViewerProvider.select((s) => s.stackIndex));
final isPlayingMotionVideo = ref.watch(isPlayingMotionVideoProvider);
final showingOcr = ref.watch(assetViewerProvider.select((s) => s.showingOcr));
final asset = ref.read(timelineServiceProvider).getAssetSafe(widget.index);
if (asset == null) {
@@ -404,6 +406,15 @@ class _AssetPageState extends ConsumerState<AssetPage> {
isPlayingMotionVideo: isPlayingMotionVideo,
),
),
if (showingOcr && displayAsset.width != null && displayAsset.height != null)
Positioned.fill(
child: OcrOverlay(
asset: displayAsset,
imageSize: Size(displayAsset.width!.toDouble(), displayAsset.height!.toDouble()),
viewportSize: Size(viewportWidth, viewportHeight),
controller: _viewController,
),
),
IgnorePointer(
ignoring: !_showingDetails,
child: Column(

View File

@@ -0,0 +1,276 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/ocr.model.dart';
import 'package:immich_mobile/providers/infrastructure/ocr.provider.dart';
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
class OcrOverlay extends ConsumerStatefulWidget {
final BaseAsset asset;
final Size imageSize;
final Size viewportSize;
final PhotoViewControllerBase? controller;
const OcrOverlay({
super.key,
required this.asset,
required this.imageSize,
required this.viewportSize,
this.controller,
});
@override
ConsumerState<OcrOverlay> createState() => _OcrOverlayState();
}
class _OcrOverlayState extends ConsumerState<OcrOverlay> {
int? _selectedBoxIndex;
// Current transform read from the PhotoView controller.
// Null until the controller has emitted at least one real event or until
// we can seed a reliable value from controller.value on init.
PhotoViewControllerValue? _controllerValue;
StreamSubscription<PhotoViewControllerValue>? _controllerSub;
@override
void initState() {
super.initState();
_attachController(widget.controller);
}
@override
void didUpdateWidget(OcrOverlay oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.controller != widget.controller) {
_detachController();
_attachController(widget.controller);
}
}
@override
void dispose() {
_detachController();
super.dispose();
}
void _attachController(PhotoViewControllerBase? controller) {
if (controller == null) return;
// Seed with the current value only when scaleBoundaries is already set.
// Before the image finishes loading, PhotoView uses childSize = outerSize
// (viewport) as a placeholder, which sets scale = 1.0. That placeholder
// is wrong for any image that doesn't exactly fill the viewport.
// Once scaleBoundaries is set the value is trustworthy (the image has rendered
// at least one frame and setScaleInvisibly has been called with the real
// initial/zoomed scale).
if (controller.scaleBoundaries != null) {
_controllerValue = controller.value;
}
_controllerSub = controller.outputStateStream.listen((value) {
if (mounted) setState(() => _controllerValue = value);
});
}
void _detachController() {
_controllerSub?.cancel();
_controllerSub = null;
}
@override
Widget build(BuildContext context) {
if (widget.asset is! RemoteAsset) {
return const SizedBox.shrink();
}
final ocrData = ref.watch(ocrAssetProvider((widget.asset as RemoteAsset).id));
return ocrData.when(
data: (data) {
if (data == null || data.isEmpty) {
return const SizedBox.shrink();
}
return _buildOcrBoxes(data);
},
loading: () => const SizedBox.shrink(),
error: (_, __) => const SizedBox.shrink(),
);
}
Widget _buildOcrBoxes(List<Ocr> ocrData) {
// Use the actual decoded image size from PhotoView's scaleBoundaries when
// available. The image provider may serve a downscaled preview (e.g. Immich
// serves a ~1440px preview for large originals), so the decoded dimensions
// can differ significantly from the stored asset dimensions. Using the wrong
// size would scale every coordinate by the ratio between the two resolutions.
final imageSize = widget.controller?.scaleBoundaries?.childSize ?? widget.imageSize;
final scale =
_controllerValue?.scale ??
math.min(widget.viewportSize.width / imageSize.width, widget.viewportSize.height / imageSize.height);
final position = _controllerValue?.position ?? Offset.zero;
return _buildBoxStack(ocrData, imageSize, scale, position);
}
Widget _buildBoxStack(List<Ocr> ocrData, Size imageSize, double scale, Offset position) {
final imageWidth = imageSize.width;
final imageHeight = imageSize.height;
final viewportWidth = widget.viewportSize.width;
final viewportHeight = widget.viewportSize.height;
// Image center in viewport space, accounting for pan
final cx = viewportWidth / 2 + position.dx;
final cy = viewportHeight / 2 + position.dy;
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
setState(() {
_selectedBoxIndex = null;
});
},
child: ClipRect(
child: Stack(
children: [
// Fills the viewport so taps outside boxes deselect
SizedBox(width: viewportWidth, height: viewportHeight),
...ocrData.asMap().entries.map((entry) {
final index = entry.key;
final ocr = entry.value;
final isSelected = _selectedBoxIndex == index;
// Map normalized image coords (01) to viewport space
final x1 = cx + (ocr.x1 - 0.5) * imageWidth * scale;
final y1 = cy + (ocr.y1 - 0.5) * imageHeight * scale;
final x2 = cx + (ocr.x2 - 0.5) * imageWidth * scale;
final y2 = cy + (ocr.y2 - 0.5) * imageHeight * scale;
final x3 = cx + (ocr.x3 - 0.5) * imageWidth * scale;
final y3 = cy + (ocr.y3 - 0.5) * imageHeight * scale;
final x4 = cx + (ocr.x4 - 0.5) * imageWidth * scale;
final y4 = cy + (ocr.y4 - 0.5) * imageHeight * scale;
// Bounding rectangle for hit testing and Positioned placement
final minX = [x1, x2, x3, x4].reduce((a, b) => a < b ? a : b);
final maxX = [x1, x2, x3, x4].reduce((a, b) => a > b ? a : b);
final minY = [y1, y2, y3, y4].reduce((a, b) => a < b ? a : b);
final maxY = [y1, y2, y3, y4].reduce((a, b) => a > b ? a : b);
final angle = math.atan2(y2 - y1, x2 - x1);
final centerX = (minX + maxX) / 2;
final centerY = (minY + maxY) / 2;
return Positioned(
left: minX,
top: minY,
child: GestureDetector(
onTap: () {
setState(() {
_selectedBoxIndex = isSelected ? null : index;
});
},
behavior: HitTestBehavior.translucent,
child: SizedBox(
width: maxX - minX,
height: maxY - minY,
child: Stack(
children: [
CustomPaint(
painter: _OcrBoxPainter(
points: [
Offset(x1 - minX, y1 - minY),
Offset(x2 - minX, y2 - minY),
Offset(x3 - minX, y3 - minY),
Offset(x4 - minX, y4 - minY),
],
isSelected: isSelected,
context: context,
),
size: Size(maxX - minX, maxY - minY),
),
if (isSelected)
Positioned(
left: centerX - minX,
top: centerY - minY,
child: FractionalTranslation(
translation: const Offset(-0.5, -0.5),
child: Transform.rotate(
angle: angle,
alignment: Alignment.center,
child: Container(
margin: const EdgeInsets.all(2),
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: Colors.grey[800]?.withValues(alpha: 0.4),
borderRadius: const BorderRadius.all(Radius.circular(4)),
),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: math.max(50, maxX - minX),
maxHeight: math.max(20, maxY - minY),
),
child: FittedBox(
fit: BoxFit.scaleDown,
child: SelectableText(
ocr.text,
style: TextStyle(
color: Colors.white,
fontSize: math.max(12, (maxY - minY) * 0.6),
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
),
),
),
),
),
],
),
),
),
);
}),
],
),
),
);
}
}
class _OcrBoxPainter extends CustomPainter {
final List<Offset> points;
final bool isSelected;
final BuildContext context;
const _OcrBoxPainter({required this.points, required this.isSelected, required this.context});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = isSelected ? Colors.blue : Colors.lightBlue
..style = PaintingStyle.stroke
..strokeWidth = 2.0;
final fillPaint = Paint()
..color = (isSelected ? Colors.blue : Colors.lightBlue).withValues(alpha: 0.1)
..style = PaintingStyle.fill;
final path = Path()
..moveTo(points[0].dx, points[0].dy)
..lineTo(points[1].dx, points[1].dy)
..lineTo(points[2].dx, points[2].dy)
..lineTo(points[3].dx, points[3].dy)
..close();
canvas.drawPath(path, fillPaint);
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(_OcrBoxPainter oldDelegate) {
return oldDelegate.isSelected != isSelected || oldDelegate.points != points;
}
}

View File

@@ -148,7 +148,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
if (ref.read(assetViewerProvider).showingDetails) return;
final autoPlayVideo = AppSetting.get(Setting.autoPlayVideo);
if (autoPlayVideo) await _notifier.play();
if (autoPlayVideo || widget.asset.isMotionPhoto) await _notifier.play();
}
void _onPlaybackEnded() {

View File

@@ -4,6 +4,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/infrastructure/ocr.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart';
@@ -14,7 +16,6 @@ import 'package:immich_mobile/providers/infrastructure/current_album.provider.da
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
import 'package:immich_mobile/providers/routes.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/routing/router.dart';
class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
const ViewerTopAppBar({super.key});
@@ -32,6 +33,7 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
final isOwner = asset is RemoteAsset && asset.ownerId == user?.id;
final isInLockedView = ref.watch(inLockedViewProvider);
final isReadonlyModeEnabled = ref.watch(readonlyModeProvider);
final hasOcr = asset is RemoteAsset && ref.watch(ocrAssetProvider(asset.id)).valueOrNull?.isNotEmpty == true;
final showingDetails = ref.watch(assetViewerProvider.select((state) => state.showingDetails));
@@ -43,8 +45,15 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
double opacity = ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0);
final originalTheme = context.themeData;
final showingOcr = ref.watch(assetViewerProvider.select((state) => state.showingOcr));
final actions = <Widget>[
if (hasOcr)
IconButton(
icon: Icon(showingOcr ? Icons.text_fields : Icons.text_fields_outlined),
onPressed: ref.read(assetViewerProvider.notifier).toggleOcr,
color: showingOcr ? context.primaryColor : null,
),
if (asset.isMotionPhoto) const MotionPhotoActionButton(iconOnly: true),
if (album != null && album.isActivityEnabled && album.isShared)
IconButton(

View File

@@ -129,7 +129,7 @@ class _ThumbnailTileState extends ConsumerState<ThumbnailTile> {
}
animation.addStatusListener(animationStatusListener);
return to.widget;
return direction == HeroFlightDirection.push ? from.widget : to.widget;
},
),
),

View File

@@ -40,8 +40,7 @@ class DriftMemoryBottomInfo extends StatelessWidget {
child: MaterialButton(
minWidth: 0,
onPressed: () async {
await context.maybePop();
await context.navigateTo(const TabShellRoute(children: [MainTimelineRoute()]));
await context.router.navigate(const TabShellRoute(children: [MainTimelineRoute()]));
EventStream.shared.emit(ScrollToDateEvent(fileCreatedDate));
},
shape: const CircleBorder(),

View File

@@ -8,6 +8,7 @@ class AssetViewerState {
final bool showingDetails;
final bool showingControls;
final bool isZoomed;
final bool showingOcr;
final BaseAsset? currentAsset;
final int stackIndex;
@@ -16,6 +17,7 @@ class AssetViewerState {
this.showingDetails = false,
this.showingControls = true,
this.isZoomed = false,
this.showingOcr = false,
this.currentAsset,
this.stackIndex = 0,
});
@@ -25,6 +27,7 @@ class AssetViewerState {
bool? showingDetails,
bool? showingControls,
bool? isZoomed,
bool? showingOcr,
BaseAsset? currentAsset,
int? stackIndex,
}) {
@@ -33,6 +36,7 @@ class AssetViewerState {
showingDetails: showingDetails ?? this.showingDetails,
showingControls: showingControls ?? this.showingControls,
isZoomed: isZoomed ?? this.isZoomed,
showingOcr: showingOcr ?? this.showingOcr,
currentAsset: currentAsset ?? this.currentAsset,
stackIndex: stackIndex ?? this.stackIndex,
);
@@ -40,7 +44,7 @@ class AssetViewerState {
@override
String toString() {
return 'AssetViewerState(opacity: $backgroundOpacity, showingDetails: $showingDetails, controls: $showingControls, isZoomed: $isZoomed)';
return 'AssetViewerState(opacity: $backgroundOpacity, showingDetails: $showingDetails, controls: $showingControls, isZoomed: $isZoomed, showingOcr: $showingOcr)';
}
@override
@@ -52,6 +56,7 @@ class AssetViewerState {
other.showingDetails == showingDetails &&
other.showingControls == showingControls &&
other.isZoomed == isZoomed &&
other.showingOcr == showingOcr &&
other.currentAsset == currentAsset &&
other.stackIndex == stackIndex;
}
@@ -62,6 +67,7 @@ class AssetViewerState {
showingDetails.hashCode ^
showingControls.hashCode ^
isZoomed.hashCode ^
showingOcr.hashCode ^
currentAsset.hashCode ^
stackIndex.hashCode;
}
@@ -131,6 +137,10 @@ class AssetViewerStateNotifier extends Notifier<AssetViewerState> {
}
state = state.copyWith(stackIndex: index);
}
void toggleOcr() {
state = state.copyWith(showingOcr: !state.showingOcr);
}
}
final assetViewerProvider = NotifierProvider<AssetViewerStateNotifier, AssetViewerState>(AssetViewerStateNotifier.new);

View File

@@ -0,0 +1,14 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/ocr.model.dart';
import 'package:immich_mobile/domain/services/ocr.service.dart';
import 'package:immich_mobile/infrastructure/repositories/ocr.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
final ocrRepositoryProvider = Provider<OcrRepository>((ref) => OcrRepository(ref.watch(driftProvider)));
final ocrServiceProvider = Provider<OcrService>((ref) => OcrService(ref.watch(ocrRepositoryProvider)));
final ocrAssetProvider = FutureProvider.family<List<Ocr>?, String>((ref, assetId) async {
final service = ref.watch(ocrServiceProvider);
return service.get(assetId);
});

View File

@@ -7,6 +7,7 @@ import 'package:immich_mobile/domain/services/remote_album.service.dart';
import 'package:immich_mobile/models/albums/album_search.model.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:logging/logging.dart';
class RemoteAlbumState {
@@ -81,7 +82,17 @@ class RemoteAlbumNotifier extends Notifier<RemoteAlbumState> {
List<String> assetIds = const [],
}) async {
try {
final album = await _remoteAlbumService.createAlbum(title: title, description: description, assetIds: assetIds);
final currentUser = ref.read(currentUserProvider);
if (currentUser == null) {
throw Exception('User not logged in');
}
final album = await _remoteAlbumService.createAlbum(
title: title,
owner: currentUser,
description: description,
assetIds: assetIds,
);
state = state.copyWith(albums: [...state.albums, album]);

View File

@@ -1,9 +1,10 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/api.repository.dart';
// ignore: import_rule_openapi
import 'package:openapi/api.dart';
import 'package:openapi/api.dart' hide AlbumUserRole;
final driftAlbumApiRepositoryProvider = Provider(
(ref) => DriftAlbumApiRepository(ref.watch(apiServiceProvider).albumsApi),
@@ -14,12 +15,17 @@ class DriftAlbumApiRepository extends ApiRepository {
DriftAlbumApiRepository(this._api);
Future<RemoteAlbum> createDriftAlbum(String name, {required Iterable<String> assetIds, String? description}) async {
Future<RemoteAlbum> createDriftAlbum(
String name,
UserDto owner, {
required Iterable<String> assetIds,
String? description,
}) async {
final responseDto = await checkNull(
_api.createAlbum(CreateAlbumDto(albumName: name, description: description, assetIds: assetIds.toList())),
);
return responseDto.toRemoteAlbum();
return responseDto.toRemoteAlbum(owner);
}
Future<({List<String> removed, List<String> failed})> removeAssets(String albumId, Iterable<String> assetIds) async {
@@ -50,7 +56,8 @@ class DriftAlbumApiRepository extends ApiRepository {
}
Future<RemoteAlbum> updateAlbum(
String albumId, {
String albumId,
UserDto owner, {
String? name,
String? description,
String? thumbnailAssetId,
@@ -75,17 +82,16 @@ class DriftAlbumApiRepository extends ApiRepository {
),
);
return responseDto.toRemoteAlbum();
return responseDto.toRemoteAlbum(owner);
}
Future<void> deleteAlbum(String albumId) {
return _api.deleteAlbum(albumId);
}
Future<RemoteAlbum> addUsers(String albumId, Iterable<String> userIds) async {
Future<void> addUsers(String albumId, Iterable<String> userIds) async {
final albumUsers = userIds.map((userId) => AlbumUserAddDto(userId: userId)).toList();
final response = await checkNull(_api.addUsersToAlbum(albumId, AddUsersDto(albumUsers: albumUsers)));
return response.toRemoteAlbum();
await checkNull(_api.addUsersToAlbum(albumId, AddUsersDto(albumUsers: albumUsers)));
}
Future<void> removeUser(String albumId, {required String userId}) async {
@@ -99,11 +105,12 @@ class DriftAlbumApiRepository extends ApiRepository {
}
extension on AlbumResponseDto {
RemoteAlbum toRemoteAlbum() {
RemoteAlbum toRemoteAlbum(final UserDto user) {
return RemoteAlbum(
id: id,
name: albumName,
ownerId: owner.id,
ownerId: user.id,
ownerName: user.name,
description: description,
createdAt: createdAt,
updatedAt: updatedAt,
@@ -111,7 +118,6 @@ extension on AlbumResponseDto {
isActivityEnabled: isActivityEnabled,
order: order == AssetOrder.asc ? AlbumAssetOrder.asc : AlbumAssetOrder.desc,
assetCount: assetCount,
ownerName: owner.name,
isShared: albumUsers.length > 2,
);
}

View File

@@ -7,13 +7,15 @@ import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/auth.service.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
class AuthGuard extends AutoRouteGuard {
final ApiService _apiService;
final AuthService _authService;
final _log = Logger("AuthGuard");
AuthGuard(this._apiService);
AuthGuard(this._apiService, this._authService);
@override
void onNavigation(NavigationResolver resolver, StackRouter router) async {
resolver.next(true);
@@ -27,7 +29,7 @@ class AuthGuard extends AutoRouteGuard {
if (res == null || res.authStatus != true) {
// If the access token is invalid, take user back to login
_log.fine('User token is invalid. Redirecting to login');
unawaited(router.replaceAll([const LoginRoute()]));
unawaited(router.replaceAll([const LoginRoute()]).then((_) => _authService.clearLocalData()));
}
} on StoreKeyNotFoundException catch (_) {
// If there is no access token, take us to the login page
@@ -38,7 +40,7 @@ class AuthGuard extends AutoRouteGuard {
// On an unauthorized request, take us to the login page
if (e.code == HttpStatus.unauthorized) {
_log.warning("Unauthorized access token.");
unawaited(router.replaceAll([const LoginRoute()]));
unawaited(router.replaceAll([const LoginRoute()]).then((_) => _authService.clearLocalData()));
return;
}
} catch (e) {

View File

@@ -73,6 +73,7 @@ import 'package:immich_mobile/routing/auth_guard.dart';
import 'package:immich_mobile/routing/duplicate_guard.dart';
import 'package:immich_mobile/routing/locked_guard.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/auth.service.dart';
import 'package:immich_mobile/services/local_auth.service.dart';
import 'package:immich_mobile/services/secure_storage.service.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
@@ -82,6 +83,7 @@ part 'router.gr.dart';
final appRouterProvider = Provider(
(ref) => AppRouter(
ref.watch(apiServiceProvider),
ref.watch(authServiceProvider),
ref.watch(galleryPermissionNotifier.notifier),
ref.watch(secureStorageServiceProvider),
ref.watch(localAuthServiceProvider),
@@ -96,11 +98,12 @@ class AppRouter extends RootStackRouter {
AppRouter(
ApiService apiService,
AuthService authService,
GalleryPermissionNotifier galleryPermissionNotifier,
SecureStorageService secureStorageService,
LocalAuthService localAuthService,
) {
_authGuard = AuthGuard(apiService);
_authGuard = AuthGuard(apiService, authService);
_duplicateGuard = const DuplicateGuard();
_lockedGuard = LockedGuard(apiService, secureStorageService, localAuthService);
}

View File

@@ -241,7 +241,8 @@ class ActionService {
}
Future<bool> setAlbumCover(String albumId, String assetId) async {
final updatedAlbum = await _albumApiRepository.updateAlbum(albumId, thumbnailAssetId: assetId);
final owner = await _remoteAlbumRepository.getOwner(albumId);
final updatedAlbum = await _albumApiRepository.updateAlbum(albumId, owner, thumbnailAssetId: assetId);
await _remoteAlbumRepository.update(updatedAlbum);
return true;
}

View File

@@ -45,12 +45,21 @@ class DeepLinkService {
this._currentUser,
);
Future<PageRouteInfo?> handleScheme(PlatformDeepLink link, WidgetRef ref) async {
DeepLink _handleColdStart(PageRouteInfo<dynamic> route, bool isColdStart) {
return DeepLink([
// we need something to segue back to if the app was cold started
// TODO: use MainTimelineRoute this when beta is default
if (isColdStart) const TabShellRoute(),
route,
]);
}
Future<DeepLink> handleScheme(PlatformDeepLink link, WidgetRef ref, bool isColdStart) async {
// get everything after the scheme, since Uri cannot parse path
final intent = link.uri.host;
final queryParams = link.uri.queryParameters;
return switch (intent) {
PageRouteInfo<dynamic>? deepLinkRoute = switch (intent) {
"memory" => await _buildMemoryDeepLink(queryParams['id'] ?? ''),
"asset" => await _buildAssetDeepLink(queryParams['id'] ?? '', ref),
"album" => await _buildAlbumDeepLink(queryParams['id'] ?? ''),
@@ -58,9 +67,20 @@ class DeepLinkService {
"activity" => await _buildActivityDeepLink(queryParams['albumId'] ?? ''),
_ => null,
};
// Deep link resolution failed, safely handle it based on the app state
if (deepLinkRoute == null) {
if (isColdStart) {
return DeepLink.defaultPath;
}
return DeepLink.none;
}
return _handleColdStart(deepLinkRoute, isColdStart);
}
Future<PageRouteInfo?> handleMyImmichApp(PlatformDeepLink link, WidgetRef ref) async {
Future<DeepLink> handleMyImmichApp(PlatformDeepLink link, WidgetRef ref, bool isColdStart) async {
final path = link.uri.path;
const uuidRegex = r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}';
@@ -68,20 +88,27 @@ class DeepLinkService {
final albumRegex = RegExp('/albums/($uuidRegex)');
final peopleRegex = RegExp('/people/($uuidRegex)');
PageRouteInfo<dynamic>? deepLinkRoute;
if (assetRegex.hasMatch(path)) {
final assetId = assetRegex.firstMatch(path)?.group(1) ?? '';
return _buildAssetDeepLink(assetId, ref);
}
if (albumRegex.hasMatch(path)) {
deepLinkRoute = await _buildAssetDeepLink(assetId, ref);
} else if (albumRegex.hasMatch(path)) {
final albumId = albumRegex.firstMatch(path)?.group(1) ?? '';
return _buildAlbumDeepLink(albumId);
}
if (peopleRegex.hasMatch(path)) {
deepLinkRoute = await _buildAlbumDeepLink(albumId);
} else if (peopleRegex.hasMatch(path)) {
final peopleId = peopleRegex.firstMatch(path)?.group(1) ?? '';
return _buildPeopleDeepLink(peopleId);
deepLinkRoute = await _buildPeopleDeepLink(peopleId);
} else if (path == "/memory") {
deepLinkRoute = await _buildMemoryDeepLink(null);
}
return null;
// Deep link resolution failed, safely handle it based on the app state
if (deepLinkRoute == null) {
if (isColdStart) return DeepLink.defaultPath;
return DeepLink.none;
}
return _handleColdStart(deepLinkRoute, isColdStart);
}
Future<PageRouteInfo?> _buildMemoryDeepLink(String? memoryId) async {

View File

@@ -271,8 +271,7 @@ enum ActionButtonType {
onPressed: buildContext == null
? null
: () async {
await buildContext.maybePop();
await buildContext.navigateTo(const TabShellRoute(children: [MainTimelineRoute()]));
await buildContext.router.navigate(const TabShellRoute(children: [MainTimelineRoute()]));
EventStream.shared.emit(ScrollToDateEvent(context.asset.createdAt));
},
),

View File

@@ -1,5 +1,5 @@
[tools]
flutter = "3.41.6"
flutter = "3.41.7"
[tools."github:CQLabs/homebrew-dcm"]
version = "1.30.0"

View File

@@ -568,6 +568,7 @@ Class | Method | HTTP request | Description
- [SyncAlbumUserDeleteV1](doc//SyncAlbumUserDeleteV1.md)
- [SyncAlbumUserV1](doc//SyncAlbumUserV1.md)
- [SyncAlbumV1](doc//SyncAlbumV1.md)
- [SyncAlbumV2](doc//SyncAlbumV2.md)
- [SyncAssetDeleteV1](doc//SyncAssetDeleteV1.md)
- [SyncAssetEditDeleteV1](doc//SyncAssetEditDeleteV1.md)
- [SyncAssetEditV1](doc//SyncAssetEditV1.md)
@@ -577,6 +578,8 @@ Class | Method | HTTP request | Description
- [SyncAssetFaceV2](doc//SyncAssetFaceV2.md)
- [SyncAssetMetadataDeleteV1](doc//SyncAssetMetadataDeleteV1.md)
- [SyncAssetMetadataV1](doc//SyncAssetMetadataV1.md)
- [SyncAssetOcrDeleteV1](doc//SyncAssetOcrDeleteV1.md)
- [SyncAssetOcrV1](doc//SyncAssetOcrV1.md)
- [SyncAssetV1](doc//SyncAssetV1.md)
- [SyncAuthUserV1](doc//SyncAuthUserV1.md)
- [SyncEntityType](doc//SyncEntityType.md)

View File

@@ -316,6 +316,7 @@ part 'model/sync_album_to_asset_v1.dart';
part 'model/sync_album_user_delete_v1.dart';
part 'model/sync_album_user_v1.dart';
part 'model/sync_album_v1.dart';
part 'model/sync_album_v2.dart';
part 'model/sync_asset_delete_v1.dart';
part 'model/sync_asset_edit_delete_v1.dart';
part 'model/sync_asset_edit_v1.dart';
@@ -325,6 +326,8 @@ part 'model/sync_asset_face_v1.dart';
part 'model/sync_asset_face_v2.dart';
part 'model/sync_asset_metadata_delete_v1.dart';
part 'model/sync_asset_metadata_v1.dart';
part 'model/sync_asset_ocr_delete_v1.dart';
part 'model/sync_asset_ocr_v1.dart';
part 'model/sync_asset_v1.dart';
part 'model/sync_auth_user_v1.dart';
part 'model/sync_entity_type.dart';

View File

@@ -678,6 +678,8 @@ class ApiClient {
return SyncAlbumUserV1.fromJson(value);
case 'SyncAlbumV1':
return SyncAlbumV1.fromJson(value);
case 'SyncAlbumV2':
return SyncAlbumV2.fromJson(value);
case 'SyncAssetDeleteV1':
return SyncAssetDeleteV1.fromJson(value);
case 'SyncAssetEditDeleteV1':
@@ -696,6 +698,10 @@ class ApiClient {
return SyncAssetMetadataDeleteV1.fromJson(value);
case 'SyncAssetMetadataV1':
return SyncAssetMetadataV1.fromJson(value);
case 'SyncAssetOcrDeleteV1':
return SyncAssetOcrDeleteV1.fromJson(value);
case 'SyncAssetOcrV1':
return SyncAssetOcrV1.fromJson(value);
case 'SyncAssetV1':
return SyncAssetV1.fromJson(value);
case 'SyncAuthUserV1':

View File

@@ -26,8 +26,6 @@ class AlbumResponseDto {
required this.isActivityEnabled,
this.lastModifiedAssetTimestamp,
this.order,
required this.owner,
required this.ownerId,
required this.shared,
this.startDate,
required this.updatedAt,
@@ -39,6 +37,7 @@ class AlbumResponseDto {
/// Thumbnail asset ID
String? albumThumbnailAssetId;
/// First entry is always the album owner. Second entry is the auth user, if it differs from the owner. The rest are ordered alphabetically.
List<AlbumUserResponseDto> albumUsers;
/// Number of assets
@@ -90,11 +89,6 @@ class AlbumResponseDto {
///
AssetOrder? order;
UserResponseDto owner;
/// Owner user ID
String ownerId;
/// Is shared album
bool shared;
@@ -125,8 +119,6 @@ class AlbumResponseDto {
other.isActivityEnabled == isActivityEnabled &&
other.lastModifiedAssetTimestamp == lastModifiedAssetTimestamp &&
other.order == order &&
other.owner == owner &&
other.ownerId == ownerId &&
other.shared == shared &&
other.startDate == startDate &&
other.updatedAt == updatedAt;
@@ -147,14 +139,12 @@ class AlbumResponseDto {
(isActivityEnabled.hashCode) +
(lastModifiedAssetTimestamp == null ? 0 : lastModifiedAssetTimestamp!.hashCode) +
(order == null ? 0 : order!.hashCode) +
(owner.hashCode) +
(ownerId.hashCode) +
(shared.hashCode) +
(startDate == null ? 0 : startDate!.hashCode) +
(updatedAt.hashCode);
@override
String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, contributorCounts=$contributorCounts, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, owner=$owner, ownerId=$ownerId, shared=$shared, startDate=$startDate, updatedAt=$updatedAt]';
String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, contributorCounts=$contributorCounts, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, shared=$shared, startDate=$startDate, updatedAt=$updatedAt]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -187,8 +177,6 @@ class AlbumResponseDto {
} else {
// json[r'order'] = null;
}
json[r'owner'] = this.owner;
json[r'ownerId'] = this.ownerId;
json[r'shared'] = this.shared;
if (this.startDate != null) {
json[r'startDate'] = this.startDate!.toUtc().toIso8601String();
@@ -221,8 +209,6 @@ class AlbumResponseDto {
isActivityEnabled: mapValueOfType<bool>(json, r'isActivityEnabled')!,
lastModifiedAssetTimestamp: mapDateTime(json, r'lastModifiedAssetTimestamp', r''),
order: AssetOrder.fromJson(json[r'order']),
owner: UserResponseDto.fromJson(json[r'owner'])!,
ownerId: mapValueOfType<String>(json, r'ownerId')!,
shared: mapValueOfType<bool>(json, r'shared')!,
startDate: mapDateTime(json, r'startDate', r''),
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
@@ -282,8 +268,6 @@ class AlbumResponseDto {
'hasSharedLink',
'id',
'isActivityEnabled',
'owner',
'ownerId',
'shared',
'updatedAt',
};

View File

@@ -24,11 +24,13 @@ class AlbumUserRole {
String toJson() => value;
static const editor = AlbumUserRole._(r'editor');
static const owner = AlbumUserRole._(r'owner');
static const viewer = AlbumUserRole._(r'viewer');
/// List of all possible values in this [enum][AlbumUserRole].
static const values = <AlbumUserRole>[
editor,
owner,
viewer,
];
@@ -69,6 +71,7 @@ class AlbumUserRoleTypeTransformer {
if (data != null) {
switch (data) {
case r'editor': return AlbumUserRole.editor;
case r'owner': return AlbumUserRole.owner;
case r'viewer': return AlbumUserRole.viewer;
default:
if (!allowNull) {

View File

@@ -24,13 +24,11 @@ class AssetMediaStatus {
String toJson() => value;
static const created = AssetMediaStatus._(r'created');
static const replaced = AssetMediaStatus._(r'replaced');
static const duplicate = AssetMediaStatus._(r'duplicate');
/// List of all possible values in this [enum][AssetMediaStatus].
static const values = <AssetMediaStatus>[
created,
replaced,
duplicate,
];
@@ -71,7 +69,6 @@ class AssetMediaStatusTypeTransformer {
if (data != null) {
switch (data) {
case r'created': return AssetMediaStatus.created;
case r'replaced': return AssetMediaStatus.replaced;
case r'duplicate': return AssetMediaStatus.duplicate;
default:
if (!allowNull) {

View File

@@ -0,0 +1,170 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SyncAlbumV2 {
/// Returns a new [SyncAlbumV2] instance.
SyncAlbumV2({
required this.createdAt,
required this.description,
required this.id,
required this.isActivityEnabled,
required this.name,
required this.order,
required this.thumbnailAssetId,
required this.updatedAt,
});
/// Created at
DateTime createdAt;
/// Album description
String description;
/// Album ID
String id;
/// Is activity enabled
bool isActivityEnabled;
/// Album name
String name;
AssetOrder order;
/// Thumbnail asset ID
String? thumbnailAssetId;
/// Updated at
DateTime updatedAt;
@override
bool operator ==(Object other) => identical(this, other) || other is SyncAlbumV2 &&
other.createdAt == createdAt &&
other.description == description &&
other.id == id &&
other.isActivityEnabled == isActivityEnabled &&
other.name == name &&
other.order == order &&
other.thumbnailAssetId == thumbnailAssetId &&
other.updatedAt == updatedAt;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(createdAt.hashCode) +
(description.hashCode) +
(id.hashCode) +
(isActivityEnabled.hashCode) +
(name.hashCode) +
(order.hashCode) +
(thumbnailAssetId == null ? 0 : thumbnailAssetId!.hashCode) +
(updatedAt.hashCode);
@override
String toString() => 'SyncAlbumV2[createdAt=$createdAt, description=$description, id=$id, isActivityEnabled=$isActivityEnabled, name=$name, order=$order, thumbnailAssetId=$thumbnailAssetId, updatedAt=$updatedAt]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
? this.createdAt.millisecondsSinceEpoch
: this.createdAt.toUtc().toIso8601String();
json[r'description'] = this.description;
json[r'id'] = this.id;
json[r'isActivityEnabled'] = this.isActivityEnabled;
json[r'name'] = this.name;
json[r'order'] = this.order;
if (this.thumbnailAssetId != null) {
json[r'thumbnailAssetId'] = this.thumbnailAssetId;
} else {
// json[r'thumbnailAssetId'] = null;
}
json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
? this.updatedAt.millisecondsSinceEpoch
: this.updatedAt.toUtc().toIso8601String();
return json;
}
/// Returns a new [SyncAlbumV2] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SyncAlbumV2? fromJson(dynamic value) {
upgradeDto(value, "SyncAlbumV2");
if (value is Map) {
final json = value.cast<String, dynamic>();
return SyncAlbumV2(
createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!,
description: mapValueOfType<String>(json, r'description')!,
id: mapValueOfType<String>(json, r'id')!,
isActivityEnabled: mapValueOfType<bool>(json, r'isActivityEnabled')!,
name: mapValueOfType<String>(json, r'name')!,
order: AssetOrder.fromJson(json[r'order'])!,
thumbnailAssetId: mapValueOfType<String>(json, r'thumbnailAssetId'),
updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!,
);
}
return null;
}
static List<SyncAlbumV2> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SyncAlbumV2>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SyncAlbumV2.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SyncAlbumV2> mapFromJson(dynamic json) {
final map = <String, SyncAlbumV2>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SyncAlbumV2.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SyncAlbumV2-objects as value to a dart map
static Map<String, List<SyncAlbumV2>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SyncAlbumV2>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SyncAlbumV2.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'createdAt',
'description',
'id',
'isActivityEnabled',
'name',
'order',
'thumbnailAssetId',
'updatedAt',
};
}

View File

@@ -0,0 +1,120 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SyncAssetOcrDeleteV1 {
/// Returns a new [SyncAssetOcrDeleteV1] instance.
SyncAssetOcrDeleteV1({
required this.assetId,
required this.deletedAt,
required this.id,
});
/// Original asset ID of the deleted OCR entry
String assetId;
/// Timestamp when the OCR entry was deleted
DateTime deletedAt;
/// Audit row ID of the deleted OCR entry
String id;
@override
bool operator ==(Object other) => identical(this, other) || other is SyncAssetOcrDeleteV1 &&
other.assetId == assetId &&
other.deletedAt == deletedAt &&
other.id == id;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(assetId.hashCode) +
(deletedAt.hashCode) +
(id.hashCode);
@override
String toString() => 'SyncAssetOcrDeleteV1[assetId=$assetId, deletedAt=$deletedAt, id=$id]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assetId'] = this.assetId;
json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
? this.deletedAt.millisecondsSinceEpoch
: this.deletedAt.toUtc().toIso8601String();
json[r'id'] = this.id;
return json;
}
/// Returns a new [SyncAssetOcrDeleteV1] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SyncAssetOcrDeleteV1? fromJson(dynamic value) {
upgradeDto(value, "SyncAssetOcrDeleteV1");
if (value is Map) {
final json = value.cast<String, dynamic>();
return SyncAssetOcrDeleteV1(
assetId: mapValueOfType<String>(json, r'assetId')!,
deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!,
id: mapValueOfType<String>(json, r'id')!,
);
}
return null;
}
static List<SyncAssetOcrDeleteV1> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SyncAssetOcrDeleteV1>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SyncAssetOcrDeleteV1.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SyncAssetOcrDeleteV1> mapFromJson(dynamic json) {
final map = <String, SyncAssetOcrDeleteV1>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SyncAssetOcrDeleteV1.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SyncAssetOcrDeleteV1-objects as value to a dart map
static Map<String, List<SyncAssetOcrDeleteV1>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SyncAssetOcrDeleteV1>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SyncAssetOcrDeleteV1.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'assetId',
'deletedAt',
'id',
};
}

View File

@@ -0,0 +1,217 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SyncAssetOcrV1 {
/// Returns a new [SyncAssetOcrV1] instance.
SyncAssetOcrV1({
required this.assetId,
required this.boxScore,
required this.id,
required this.isVisible,
required this.text,
required this.textScore,
required this.x1,
required this.x2,
required this.x3,
required this.x4,
required this.y1,
required this.y2,
required this.y3,
required this.y4,
});
/// Asset ID
String assetId;
/// Confidence score of the bounding box
double boxScore;
/// OCR entry ID
String id;
/// Whether the OCR entry is visible
bool isVisible;
/// Recognized text content
String text;
/// Confidence score of the recognized text
double textScore;
/// Top-left X coordinate (normalized 01)
double x1;
/// Top-right X coordinate (normalized 01)
double x2;
/// Bottom-right X coordinate (normalized 01)
double x3;
/// Bottom-left X coordinate (normalized 01)
double x4;
/// Top-left Y coordinate (normalized 01)
double y1;
/// Top-right Y coordinate (normalized 01)
double y2;
/// Bottom-right Y coordinate (normalized 01)
double y3;
/// Bottom-left Y coordinate (normalized 01)
double y4;
@override
bool operator ==(Object other) => identical(this, other) || other is SyncAssetOcrV1 &&
other.assetId == assetId &&
other.boxScore == boxScore &&
other.id == id &&
other.isVisible == isVisible &&
other.text == text &&
other.textScore == textScore &&
other.x1 == x1 &&
other.x2 == x2 &&
other.x3 == x3 &&
other.x4 == x4 &&
other.y1 == y1 &&
other.y2 == y2 &&
other.y3 == y3 &&
other.y4 == y4;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(assetId.hashCode) +
(boxScore.hashCode) +
(id.hashCode) +
(isVisible.hashCode) +
(text.hashCode) +
(textScore.hashCode) +
(x1.hashCode) +
(x2.hashCode) +
(x3.hashCode) +
(x4.hashCode) +
(y1.hashCode) +
(y2.hashCode) +
(y3.hashCode) +
(y4.hashCode);
@override
String toString() => 'SyncAssetOcrV1[assetId=$assetId, boxScore=$boxScore, id=$id, isVisible=$isVisible, text=$text, textScore=$textScore, x1=$x1, x2=$x2, x3=$x3, x4=$x4, y1=$y1, y2=$y2, y3=$y3, y4=$y4]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assetId'] = this.assetId;
json[r'boxScore'] = this.boxScore;
json[r'id'] = this.id;
json[r'isVisible'] = this.isVisible;
json[r'text'] = this.text;
json[r'textScore'] = this.textScore;
json[r'x1'] = this.x1;
json[r'x2'] = this.x2;
json[r'x3'] = this.x3;
json[r'x4'] = this.x4;
json[r'y1'] = this.y1;
json[r'y2'] = this.y2;
json[r'y3'] = this.y3;
json[r'y4'] = this.y4;
return json;
}
/// Returns a new [SyncAssetOcrV1] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SyncAssetOcrV1? fromJson(dynamic value) {
upgradeDto(value, "SyncAssetOcrV1");
if (value is Map) {
final json = value.cast<String, dynamic>();
return SyncAssetOcrV1(
assetId: mapValueOfType<String>(json, r'assetId')!,
boxScore: (mapValueOfType<num>(json, r'boxScore')!).toDouble(),
id: mapValueOfType<String>(json, r'id')!,
isVisible: mapValueOfType<bool>(json, r'isVisible')!,
text: mapValueOfType<String>(json, r'text')!,
textScore: (mapValueOfType<num>(json, r'textScore')!).toDouble(),
x1: (mapValueOfType<num>(json, r'x1')!).toDouble(),
x2: (mapValueOfType<num>(json, r'x2')!).toDouble(),
x3: (mapValueOfType<num>(json, r'x3')!).toDouble(),
x4: (mapValueOfType<num>(json, r'x4')!).toDouble(),
y1: (mapValueOfType<num>(json, r'y1')!).toDouble(),
y2: (mapValueOfType<num>(json, r'y2')!).toDouble(),
y3: (mapValueOfType<num>(json, r'y3')!).toDouble(),
y4: (mapValueOfType<num>(json, r'y4')!).toDouble(),
);
}
return null;
}
static List<SyncAssetOcrV1> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SyncAssetOcrV1>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SyncAssetOcrV1.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SyncAssetOcrV1> mapFromJson(dynamic json) {
final map = <String, SyncAssetOcrV1>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SyncAssetOcrV1.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SyncAssetOcrV1-objects as value to a dart map
static Map<String, List<SyncAssetOcrV1>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SyncAssetOcrV1>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SyncAssetOcrV1.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'assetId',
'boxScore',
'id',
'isVisible',
'text',
'textScore',
'x1',
'x2',
'x3',
'x4',
'y1',
'y2',
'y3',
'y4',
};
}

View File

@@ -33,6 +33,8 @@ class SyncEntityType {
static const assetEditDeleteV1 = SyncEntityType._(r'AssetEditDeleteV1');
static const assetMetadataV1 = SyncEntityType._(r'AssetMetadataV1');
static const assetMetadataDeleteV1 = SyncEntityType._(r'AssetMetadataDeleteV1');
static const assetOcrV1 = SyncEntityType._(r'AssetOcrV1');
static const assetOcrDeleteV1 = SyncEntityType._(r'AssetOcrDeleteV1');
static const partnerV1 = SyncEntityType._(r'PartnerV1');
static const partnerDeleteV1 = SyncEntityType._(r'PartnerDeleteV1');
static const partnerAssetV1 = SyncEntityType._(r'PartnerAssetV1');
@@ -44,6 +46,7 @@ class SyncEntityType {
static const partnerStackDeleteV1 = SyncEntityType._(r'PartnerStackDeleteV1');
static const partnerStackV1 = SyncEntityType._(r'PartnerStackV1');
static const albumV1 = SyncEntityType._(r'AlbumV1');
static const albumV2 = SyncEntityType._(r'AlbumV2');
static const albumDeleteV1 = SyncEntityType._(r'AlbumDeleteV1');
static const albumUserV1 = SyncEntityType._(r'AlbumUserV1');
static const albumUserBackfillV1 = SyncEntityType._(r'AlbumUserBackfillV1');
@@ -86,6 +89,8 @@ class SyncEntityType {
assetEditDeleteV1,
assetMetadataV1,
assetMetadataDeleteV1,
assetOcrV1,
assetOcrDeleteV1,
partnerV1,
partnerDeleteV1,
partnerAssetV1,
@@ -97,6 +102,7 @@ class SyncEntityType {
partnerStackDeleteV1,
partnerStackV1,
albumV1,
albumV2,
albumDeleteV1,
albumUserV1,
albumUserBackfillV1,
@@ -174,6 +180,8 @@ class SyncEntityTypeTypeTransformer {
case r'AssetEditDeleteV1': return SyncEntityType.assetEditDeleteV1;
case r'AssetMetadataV1': return SyncEntityType.assetMetadataV1;
case r'AssetMetadataDeleteV1': return SyncEntityType.assetMetadataDeleteV1;
case r'AssetOcrV1': return SyncEntityType.assetOcrV1;
case r'AssetOcrDeleteV1': return SyncEntityType.assetOcrDeleteV1;
case r'PartnerV1': return SyncEntityType.partnerV1;
case r'PartnerDeleteV1': return SyncEntityType.partnerDeleteV1;
case r'PartnerAssetV1': return SyncEntityType.partnerAssetV1;
@@ -185,6 +193,7 @@ class SyncEntityTypeTypeTransformer {
case r'PartnerStackDeleteV1': return SyncEntityType.partnerStackDeleteV1;
case r'PartnerStackV1': return SyncEntityType.partnerStackV1;
case r'AlbumV1': return SyncEntityType.albumV1;
case r'AlbumV2': return SyncEntityType.albumV2;
case r'AlbumDeleteV1': return SyncEntityType.albumDeleteV1;
case r'AlbumUserV1': return SyncEntityType.albumUserV1;
case r'AlbumUserBackfillV1': return SyncEntityType.albumUserBackfillV1;

View File

@@ -24,6 +24,7 @@ class SyncRequestType {
String toJson() => value;
static const albumsV1 = SyncRequestType._(r'AlbumsV1');
static const albumsV2 = SyncRequestType._(r'AlbumsV2');
static const albumUsersV1 = SyncRequestType._(r'AlbumUsersV1');
static const albumToAssetsV1 = SyncRequestType._(r'AlbumToAssetsV1');
static const albumAssetsV1 = SyncRequestType._(r'AlbumAssetsV1');
@@ -32,6 +33,7 @@ class SyncRequestType {
static const assetExifsV1 = SyncRequestType._(r'AssetExifsV1');
static const assetEditsV1 = SyncRequestType._(r'AssetEditsV1');
static const assetMetadataV1 = SyncRequestType._(r'AssetMetadataV1');
static const assetOcrV1 = SyncRequestType._(r'AssetOcrV1');
static const authUsersV1 = SyncRequestType._(r'AuthUsersV1');
static const memoriesV1 = SyncRequestType._(r'MemoriesV1');
static const memoryToAssetsV1 = SyncRequestType._(r'MemoryToAssetsV1');
@@ -49,6 +51,7 @@ class SyncRequestType {
/// List of all possible values in this [enum][SyncRequestType].
static const values = <SyncRequestType>[
albumsV1,
albumsV2,
albumUsersV1,
albumToAssetsV1,
albumAssetsV1,
@@ -57,6 +60,7 @@ class SyncRequestType {
assetExifsV1,
assetEditsV1,
assetMetadataV1,
assetOcrV1,
authUsersV1,
memoriesV1,
memoryToAssetsV1,
@@ -109,6 +113,7 @@ class SyncRequestTypeTypeTransformer {
if (data != null) {
switch (data) {
case r'AlbumsV1': return SyncRequestType.albumsV1;
case r'AlbumsV2': return SyncRequestType.albumsV2;
case r'AlbumUsersV1': return SyncRequestType.albumUsersV1;
case r'AlbumToAssetsV1': return SyncRequestType.albumToAssetsV1;
case r'AlbumAssetsV1': return SyncRequestType.albumAssetsV1;
@@ -117,6 +122,7 @@ class SyncRequestTypeTypeTransformer {
case r'AssetExifsV1': return SyncRequestType.assetExifsV1;
case r'AssetEditsV1': return SyncRequestType.assetEditsV1;
case r'AssetMetadataV1': return SyncRequestType.assetMetadataV1;
case r'AssetOcrV1': return SyncRequestType.assetOcrV1;
case r'AuthUsersV1': return SyncRequestType.authUsersV1;
case r'MemoriesV1': return SyncRequestType.memoriesV1;
case r'MemoryToAssetsV1': return SyncRequestType.memoryToAssetsV1;

View File

@@ -32,7 +32,7 @@ class PlatformAsset {
final int? updatedAt;
final int? width;
final int? height;
final int durationInSeconds;
final int durationMs;
final int orientation;
final bool isFavorite;
@@ -50,7 +50,7 @@ class PlatformAsset {
this.updatedAt,
this.width,
this.height,
this.durationInSeconds = 0,
this.durationMs = 0,
this.orientation = 0,
this.isFavorite = false,
this.adjustmentTime,

View File

@@ -1989,4 +1989,4 @@ packages:
version: "3.1.3"
sdks:
dart: ">=3.11.0 <4.0.0"
flutter: "3.41.6"
flutter: "3.41.7"

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