Compare commits

..

2 Commits

Author SHA1 Message Date
Yaros 87dd274421 Merge branch 'main' into feat/share-downloading 2026-04-20 17:23:38 +02:00
Yaros 900febb517 feat(mobile): better downloading while sharing 2026-03-26 22:36:05 +01:00
858 changed files with 9196 additions and 40432 deletions
+1 -1
View File
@@ -1 +1 @@
24.15.0 24.14.1
+3 -3
View File
@@ -103,7 +103,7 @@ jobs:
- name: Restore Gradle Cache - name: Restore Gradle Cache
id: cache-gradle-restore id: cache-gradle-restore
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@@ -160,7 +160,7 @@ jobs:
- name: Save Gradle Cache - name: Save Gradle Cache
id: cache-gradle-save id: cache-gradle-save
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
if: github.ref == 'refs/heads/main' if: github.ref == 'refs/heads/main'
with: with:
path: | path: |
@@ -210,7 +210,7 @@ jobs:
working-directory: ./mobile working-directory: ./mobile
- name: Setup Ruby - name: Setup Ruby
uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0 uses: ruby/setup-ruby@e65c17d16e57e481586a6a5a0282698790062f92 # v1.300.0
with: with:
ruby-version: '3.3' ruby-version: '3.3'
bundler-cache: true bundler-cache: true
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Check for breaking API changes - name: Check for breaking API changes
uses: oasdiff/oasdiff-action/breaking@f8cb9308b42121e793f835bd14c0b8090420430c # v0.0.39 uses: oasdiff/oasdiff-action/breaking@e6faebce24cf20ac38653d0d2c7f4aa80aaafc79 # v0.0.38
with: with:
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
revision: open-api/immich-openapi-specs.json revision: open-api/immich-openapi-specs.json
+3 -3
View File
@@ -57,7 +57,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -70,7 +70,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
# ️ Command-line programs to run using the OS shell. # ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -83,6 +83,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh # ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with: with:
category: '/language:${{matrix.language}}' category: '/language:${{matrix.language}}'
+1 -1
View File
@@ -211,7 +211,7 @@ jobs:
run: 'mise run //deployment:tf apply' run: 'mise run //deployment:tf apply'
- name: Comment - name: Comment
uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0 uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
if: ${{ steps.parameters.outputs.event == 'pr' }} if: ${{ steps.parameters.outputs.event == 'pr' }}
with: with:
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
run: 'mise run //deployment:tf destroy -- -refresh=false' run: 'mise run //deployment:tf destroy -- -refresh=false'
- name: Comment - name: Comment
uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0 uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
with: with:
token: ${{ steps.token.outputs.token }} token: ${{ steps.token.outputs.token }}
number: ${{ github.event.number }} number: ${{ github.event.number }}
+1 -1
View File
@@ -142,7 +142,7 @@ jobs:
github-token: ${{ steps.generate-token.outputs.token }} github-token: ${{ steps.generate-token.outputs.token }}
- name: Create draft release - name: Create draft release
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2 uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with: with:
draft: true draft: true
tag_name: ${{ needs.bump_version.outputs.version }} tag_name: ${{ needs.bump_version.outputs.version }}
-2
View File
@@ -392,8 +392,6 @@ jobs:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml' cache-dependency-path: '**/pnpm-lock.yaml'
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@035e80a7d4355d5f087ffb95db9e4a0944c04e56 # use-mise-action-v1.1.3
- name: Run pnpm install - name: Run pnpm install
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile
- name: Run medium tests - name: Run medium tests
+13 -2
View File
@@ -13,6 +13,10 @@
"editor.wordBasedSuggestions": "off" "editor.wordBasedSuggestions": "off"
}, },
"[javascript]": { "[javascript]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
@@ -25,11 +29,18 @@
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
"[svelte]": { "[svelte]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "svelte.svelte-vscode", "editor.defaultFormatter": "svelte.svelte-vscode",
"editor.formatOnSave": true, "editor.formatOnSave": true
"tailwindCSS.lint.suggestCanonicalClasses": "ignore"
}, },
"[typescript]": { "[typescript]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
+1 -1
View File
@@ -1 +1 @@
24.15.0 24.14.1
+1 -1
View File
@@ -68,6 +68,6 @@
"micromatch": "^4.0.8" "micromatch": "^4.0.8"
}, },
"volta": { "volta": {
"node": "24.15.0" "node": "24.14.1"
} }
} }
+2 -2
View File
@@ -1,6 +1,6 @@
[tools] [tools]
terragrunt = "1.0.2" terragrunt = "1.0.0"
opentofu = "1.11.6" opentofu = "1.11.5"
[tasks."tg:fmt"] [tasks."tg:fmt"]
run = "terragrunt hclfmt" run = "terragrunt hclfmt"
+1 -1
View File
@@ -85,7 +85,7 @@ services:
container_name: immich_prometheus container_name: immich_prometheus
ports: ports:
- 9090:9090 - 9090:9090
image: prom/prometheus@sha256:e4254400b85610324913f0dc4acf92603d9984e7519414c5a12811aa6146acc3 image: prom/prometheus@sha256:dda13e28bf95a5e5ca5b8ed56852006094c1c8e8871d9c9dbeed30aa6e55271f
volumes: volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml - ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus - prometheus-data:/prometheus
+1 -1
View File
@@ -1 +1 @@
24.15.0 24.14.1
@@ -81,7 +81,7 @@ VectorChord is the successor extension to pgvecto.rs, allowing for higher perfor
### Migrating from pgvecto.rs ### Migrating from pgvecto.rs
Support for pgvecto.rs has been dropped as of 3.0, hence all users currently using pgvecto.rs should migrate to VectorChord. There are two primary approaches to do so. Support for pgvecto.rs will be dropped in a later release, hence we recommend all users currently using pgvecto.rs to migrate to VectorChord at their convenience. There are two primary approaches to do so.
The easiest option is to have both extensions installed during the migration: The easiest option is to have both extensions installed during the migration:
-1
View File
@@ -18,7 +18,6 @@ For the full list, refer to the [Immich source code](https://github.com/immich-a
| `JPEG 2000` | `.jp2` | :white_check_mark: | | | `JPEG 2000` | `.jp2` | :white_check_mark: | |
| `JPEG` | `.jpeg` `.jpg` `.jpe` `.insp` | :white_check_mark: | | | `JPEG` | `.jpeg` `.jpg` `.jpe` `.insp` | :white_check_mark: | |
| `JPEG XL` | `.jxl` | :white_check_mark: | | | `JPEG XL` | `.jxl` | :white_check_mark: | |
| `MPO` | `.mpo` | :white_check_mark: | Multi-Picture |
| `PNG` | `.png` | :white_check_mark: | | | `PNG` | `.png` | :white_check_mark: | |
| `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop | | `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop |
| `RAW` | `.raw` | :white_check_mark: | | | `RAW` | `.raw` | :white_check_mark: | |
+1 -1
View File
@@ -39,7 +39,7 @@ You can learn how to set up Tailscale together with Immich with the [tutorial vi
### Cons ### Cons
- The Tailscale client usually needs to run as root on your devices and it increases the attack surface slightly compared to a minimal Wireguard server. e.g., an [RCE vulnerability](https://github.com/tailscale/tailscale/security/advisories/GHSA-vqp6-rc3h-83cp) was discovered in the Windows Tailscale client in November 2022. - The Tailscale client usually needs to run as root on your devices and it increases the attack surface slightly compared to a minimal Wireguard server. e.g., an [RCE vulnerability](https://github.com/tailscale/tailscale/security/advisories/GHSA-vqp6-rc3h-83cp) was discovered in the Windows Tailscale client in November 2022.
- Tailscale is a paid service. However, there is a generous [free tier](https://tailscale.com/pricing/) suitable for personal use. - Tailscale is a paid service. However, there is a generous [free tier](https://tailscale.com/pricing/) that permits up to 3 users and up to 100 devices.
- Tailscale needs to be installed and running on both server-side and client-side. - Tailscale needs to be installed and running on both server-side and client-side.
## Option 3: Reverse Proxy ## Option 3: Reverse Proxy
+1 -1
View File
@@ -81,7 +81,7 @@ Information on the current workers can be found [here](/administration/jobs-work
| `DB_PASSWORD` | Database password | `postgres` | server, database<sup>\*1</sup> | | `DB_PASSWORD` | Database password | `postgres` | server, database<sup>\*1</sup> |
| `DB_DATABASE_NAME` | Database name | `immich` | server, database<sup>\*1</sup> | | `DB_DATABASE_NAME` | Database name | `immich` | server, database<sup>\*1</sup> |
| `DB_SSL_MODE` | Database SSL mode | | server | | `DB_SSL_MODE` | Database SSL mode | | server |
| `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database vector extension (one of [`vectorchord`, `pgvector`]) | | server | | `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database vector extension (one of [`vectorchord`, `pgvector`, `pgvecto.rs`]) | | server |
| `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server | | `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server |
| `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])<sup>\*3</sup> | `SSD` | database | | `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])<sup>\*3</sup> | `SSD` | database |
+4
View File
@@ -130,3 +130,7 @@ These storage mediums have different performance characteristics. As a result, t
#### Can I use the new database image as a general PostgreSQL image outside of Immich? #### Can I use the new database image as a general PostgreSQL image outside of Immich?
Its a standard PostgreSQL container image that additionally contains the VectorChord, pgvector, and (optionally) pgvecto.rs extensions. If you were using the previous pgvecto.rs image for other purposes, you can similarly do so with this image. Its a standard PostgreSQL container image that additionally contains the VectorChord, pgvector, and (optionally) pgvecto.rs extensions. If you were using the previous pgvecto.rs image for other purposes, you can similarly do so with this image.
#### If pgvecto.rs and pgvector still work, why should I switch to VectorChord?
VectorChord is faster, more stable, uses less RAM, and (with the settings Immich uses) offers higher-quality results than pgvector and pgvecto.rs. This translates to better search and facial recognition experiences. In addition, pgvecto.rs support will be dropped in the future, so changing it sooner will avoid disruption.
+7 -7
View File
@@ -17,10 +17,10 @@
"write-heading-ids": "docusaurus write-heading-ids" "write-heading-ids": "docusaurus write-heading-ids"
}, },
"dependencies": { "dependencies": {
"@docusaurus/core": "~3.10.0", "@docusaurus/core": "~3.9.0",
"@docusaurus/preset-classic": "~3.10.0", "@docusaurus/preset-classic": "~3.9.0",
"@docusaurus/theme-common": "~3.10.0", "@docusaurus/theme-common": "~3.9.0",
"@docusaurus/theme-mermaid": "~3.10.0", "@docusaurus/theme-mermaid": "~3.9.0",
"@mdi/js": "^7.3.67", "@mdi/js": "^7.3.67",
"@mdi/react": "^1.6.1", "@mdi/react": "^1.6.1",
"@mdx-js/react": "^3.0.0", "@mdx-js/react": "^3.0.0",
@@ -36,9 +36,9 @@
"url": "^0.11.0" "url": "^0.11.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "~3.10.0", "@docusaurus/module-type-aliases": "~3.9.0",
"@docusaurus/tsconfig": "^3.10.0", "@docusaurus/tsconfig": "^3.10.0",
"@docusaurus/types": "^3.10.0", "@docusaurus/types": "^3.7.0",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"typescript": "^6.0.0" "typescript": "^6.0.0"
}, },
@@ -58,6 +58,6 @@
"node": ">=20" "node": ">=20"
}, },
"volta": { "volta": {
"node": "24.15.0" "node": "24.14.1"
} }
} }
+1 -1
View File
@@ -1 +1 @@
24.15.0 24.14.1
+1 -1
View File
@@ -58,6 +58,6 @@
"vitest": "^4.0.0" "vitest": "^4.0.0"
}, },
"volta": { "volta": {
"node": "24.15.0" "node": "24.14.1"
} }
} }
+42 -4
View File
@@ -2,44 +2,82 @@ import { expect } from 'vitest';
export const errorDto = { export const errorDto = {
unauthorized: { unauthorized: {
error: 'Unauthorized',
statusCode: 401,
message: 'Authentication required', message: 'Authentication required',
correlationId: expect.any(String),
}, },
unauthorizedWithMessage: (message: string) => ({ unauthorizedWithMessage: (message: string) => ({
error: 'Unauthorized',
statusCode: 401,
message, message,
correlationId: expect.any(String),
}), }),
forbidden: { forbidden: {
error: 'Forbidden',
statusCode: 403,
message: expect.any(String), message: expect.any(String),
correlationId: expect.any(String),
}, },
missingPermission: (permission: string) => ({ missingPermission: (permission: string) => ({
error: 'Forbidden',
statusCode: 403,
message: `Missing required permission: ${permission}`, message: `Missing required permission: ${permission}`,
correlationId: expect.any(String),
}), }),
wrongPassword: { wrongPassword: {
error: 'Bad Request',
statusCode: 400,
message: 'Wrong password', message: 'Wrong password',
correlationId: expect.any(String),
}, },
invalidToken: { invalidToken: {
error: 'Unauthorized',
statusCode: 401,
message: 'Invalid user token', message: 'Invalid user token',
correlationId: expect.any(String),
}, },
invalidShareKey: { invalidShareKey: {
error: 'Unauthorized',
statusCode: 401,
message: 'Invalid share key', message: 'Invalid share key',
correlationId: expect.any(String),
}, },
passwordRequired: { passwordRequired: {
error: 'Unauthorized',
statusCode: 401,
message: 'Password required', message: 'Password required',
correlationId: expect.any(String),
}, },
badRequest: (message: any = null) => ({ badRequest: (message: any = null) => ({
error: 'Bad Request',
statusCode: 400,
message: message ?? expect.anything(), message: message ?? expect.anything(),
}), correlationId: expect.any(String),
validationError: (errors?: ReadonlyArray<{ path: ReadonlyArray<string | number>; message: string }>) => ({
message: 'Validation failed',
errors: errors ? expect.arrayContaining(errors.map((e) => expect.objectContaining(e))) : expect.any(Array),
}), }),
noPermission: { noPermission: {
error: 'Bad Request',
statusCode: 400,
message: expect.stringContaining('Not found or no'), message: expect.stringContaining('Not found or no'),
correlationId: expect.any(String),
}, },
incorrectLogin: { incorrectLogin: {
error: 'Unauthorized',
statusCode: 401,
message: 'Incorrect email or password', message: 'Incorrect email or password',
correlationId: expect.any(String),
}, },
alreadyHasAdmin: { alreadyHasAdmin: {
error: 'Bad Request',
statusCode: 400,
message: 'The server already has an admin', message: 'The server already has an admin',
correlationId: expect.any(String),
},
invalidEmail: {
error: 'Bad Request',
statusCode: 400,
message: ['email must be an email'],
correlationId: expect.any(String),
}, },
}; };
+34 -66
View File
@@ -154,31 +154,23 @@ describe('/albums', () => {
expect(body).toEqual( expect(body).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedLink, albumName: user1SharedLink,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedEditorUser, albumName: user1SharedEditorUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedViewerUser, albumName: user1SharedViewerUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user2.userId,
albumName: user2SharedUser, albumName: user2SharedUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) },
]),
shared: true, shared: true,
}), }),
]), ]),
@@ -192,31 +184,23 @@ describe('/albums', () => {
expect(body).toEqual( expect(body).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedEditorUser, albumName: user1SharedEditorUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedViewerUser, albumName: user1SharedViewerUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedLink, albumName: user1SharedLink,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1NotShared, albumName: user1NotShared,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: false, shared: false,
}), }),
]), ]),
@@ -232,31 +216,23 @@ describe('/albums', () => {
expect(body).toEqual( expect(body).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedEditorUser, albumName: user1SharedEditorUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedViewerUser, albumName: user1SharedViewerUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedLink, albumName: user1SharedLink,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: true, shared: true,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user2.userId,
albumName: user2SharedUser, albumName: user2SharedUser,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) },
]),
shared: true, shared: true,
}), }),
]), ]),
@@ -272,10 +248,8 @@ describe('/albums', () => {
expect(body).toEqual( expect(body).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
ownerId: user1.userId,
albumName: user1NotShared, albumName: user1NotShared,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
]),
shared: false, shared: false,
}), }),
]), ]),
@@ -312,17 +286,13 @@ describe('/albums', () => {
expect(body).toEqual( expect(body).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
ownerId: user4.userId,
albumName: user4DeletedAsset, albumName: user4DeletedAsset,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user4.userId }) },
]),
shared: false, shared: false,
}), }),
expect.objectContaining({ expect.objectContaining({
ownerId: user4.userId,
albumName: user4Empty, albumName: user4Empty,
albumUsers: expect.arrayContaining([
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user4.userId }) },
]),
shared: false, shared: false,
}), }),
]), ]),
@@ -392,17 +362,16 @@ describe('/albums', () => {
.set('Authorization', `Bearer ${user1.accessToken}`); .set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual( expect(body).toEqual({
expect.objectContaining({ ...user2Albums[0],
contributorCounts: [{ userId: user1.userId, assetCount: 1 }], contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
assetCount: 1, assetCount: 1,
lastModifiedAssetTimestamp: expect.any(String), lastModifiedAssetTimestamp: expect.any(String),
endDate: expect.any(String), endDate: expect.any(String),
startDate: expect.any(String), startDate: expect.any(String),
albumUsers: expect.any(Array), albumUsers: expect.any(Array),
shared: true, shared: true,
}), });
);
}); });
}); });
@@ -428,13 +397,15 @@ describe('/albums', () => {
id: expect.any(String), id: expect.any(String),
createdAt: expect.any(String), createdAt: expect.any(String),
updatedAt: expect.any(String), updatedAt: expect.any(String),
ownerId: user1.userId,
albumName: 'New album', albumName: 'New album',
description: '', description: '',
albumThumbnailAssetId: null, albumThumbnailAssetId: null,
shared: false, shared: false,
albumUsers: [{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }], albumUsers: [],
hasSharedLink: false, hasSharedLink: false,
assetCount: 0, assetCount: 0,
owner: expect.objectContaining({ email: user1.userEmail }),
isActivityEnabled: true, isActivityEnabled: true,
order: AssetOrder.Desc, order: AssetOrder.Desc,
}); });
@@ -650,11 +621,11 @@ describe('/albums', () => {
expect(status).toBe(200); expect(status).toBe(200);
expect(body).toEqual( expect(body).toEqual(
expect.objectContaining({ expect.objectContaining({
albumUsers: expect.arrayContaining([ albumUsers: [
expect.objectContaining({ expect.objectContaining({
user: expect.objectContaining({ id: user2.userId }), user: expect.objectContaining({ id: user2.userId }),
}), }),
]), ],
}), }),
); );
}); });
@@ -666,7 +637,7 @@ describe('/albums', () => {
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] }); .send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('User already added')); expect(body).toEqual(errorDto.badRequest('Cannot be shared with owner'));
}); });
it('should not be able to add existing user to shared album', async () => { it('should not be able to add existing user to shared album', async () => {
@@ -692,7 +663,7 @@ describe('/albums', () => {
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }], albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
}); });
expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Viewer); expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer);
const { status } = await request(app) const { status } = await request(app)
.put(`/albums/${album.id}/user/${user2.userId}`) .put(`/albums/${album.id}/user/${user2.userId}`)
@@ -707,10 +678,7 @@ describe('/albums', () => {
.set('Authorization', `Bearer ${user1.accessToken}`); .set('Authorization', `Bearer ${user1.accessToken}`);
expect(body).toEqual( expect(body).toEqual(
expect.objectContaining({ expect.objectContaining({
albumUsers: [ albumUsers: [expect.objectContaining({ role: AlbumUserRole.Editor })],
expect.objectContaining({ role: AlbumUserRole.Owner }),
expect.objectContaining({ role: AlbumUserRole.Editor }),
],
}), }),
); );
}); });
@@ -721,7 +689,7 @@ describe('/albums', () => {
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }], albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
}); });
expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Viewer); expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer);
const { status, body } = await request(app) const { status, body } = await request(app)
.put(`/albums/${album.id}/user/${user2.userId}`) .put(`/albums/${album.id}/user/${user2.userId}`)
+7 -21
View File
@@ -110,9 +110,7 @@ describe('/libraries', () => {
}); });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[importPaths] Array must have unique items']));
errorDto.validationError([{ path: ['importPaths'], message: 'Array must have unique items' }]),
);
}); });
it('should not create an external library with duplicate exclusion patterns', async () => { it('should not create an external library with duplicate exclusion patterns', async () => {
@@ -127,9 +125,7 @@ describe('/libraries', () => {
}); });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array must have unique items']));
errorDto.validationError([{ path: ['exclusionPatterns'], message: 'Array must have unique items' }]),
);
}); });
}); });
@@ -161,9 +157,7 @@ describe('/libraries', () => {
.send({ name: '' }); .send({ name: '' });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[name] Too small: expected string to have >=1 characters']));
errorDto.validationError([{ path: ['name'], message: 'Too small: expected string to have >=1 characters' }]),
);
}); });
it('should change the import paths', async () => { it('should change the import paths', async () => {
@@ -187,9 +181,7 @@ describe('/libraries', () => {
.send({ importPaths: [''] }); .send({ importPaths: [''] });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[importPaths] Array items must not be empty']));
errorDto.validationError([{ path: ['importPaths'], message: 'Array items must not be empty' }]),
);
}); });
it('should reject duplicate import paths', async () => { it('should reject duplicate import paths', async () => {
@@ -199,9 +191,7 @@ describe('/libraries', () => {
.send({ importPaths: ['/path', '/path'] }); .send({ importPaths: ['/path', '/path'] });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[importPaths] Array must have unique items']));
errorDto.validationError([{ path: ['importPaths'], message: 'Array must have unique items' }]),
);
}); });
it('should change the exclusion pattern', async () => { it('should change the exclusion pattern', async () => {
@@ -225,9 +215,7 @@ describe('/libraries', () => {
.send({ exclusionPatterns: ['**/*.jpg', '**/*.jpg'] }); .send({ exclusionPatterns: ['**/*.jpg', '**/*.jpg'] });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array must have unique items']));
errorDto.validationError([{ path: ['exclusionPatterns'], message: 'Array must have unique items' }]),
);
}); });
it('should reject an empty exclusion pattern', async () => { it('should reject an empty exclusion pattern', async () => {
@@ -237,9 +225,7 @@ describe('/libraries', () => {
.send({ exclusionPatterns: [''] }); .send({ exclusionPatterns: [''] });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array items must not be empty']));
errorDto.validationError([{ path: ['exclusionPatterns'], message: 'Array items must not be empty' }]),
);
}); });
}); });
+4 -12
View File
@@ -109,9 +109,7 @@ describe('/map', () => {
.get('/map/reverse-geocode?lon=123') .get('/map/reverse-geocode?lon=123')
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[lat] Invalid input: expected number, received NaN']));
errorDto.validationError([{ path: ['lat'], message: 'Invalid input: expected number, received NaN' }]),
);
}); });
it('should throw an error if a lat is not a number', async () => { it('should throw an error if a lat is not a number', async () => {
@@ -119,9 +117,7 @@ describe('/map', () => {
.get('/map/reverse-geocode?lat=abc&lon=123.456') .get('/map/reverse-geocode?lat=abc&lon=123.456')
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[lat] Invalid input: expected number, received NaN']));
errorDto.validationError([{ path: ['lat'], message: 'Invalid input: expected number, received NaN' }]),
);
}); });
it('should throw an error if a lat is out of range', async () => { it('should throw an error if a lat is out of range', async () => {
@@ -129,9 +125,7 @@ describe('/map', () => {
.get('/map/reverse-geocode?lat=91&lon=123.456') .get('/map/reverse-geocode?lat=91&lon=123.456')
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[lat] Too big: expected number to be <=90']));
errorDto.validationError([{ path: ['lat'], message: 'Too big: expected number to be <=90' }]),
);
}); });
it('should throw an error if a lon is not provided', async () => { it('should throw an error if a lon is not provided', async () => {
@@ -139,9 +133,7 @@ describe('/map', () => {
.get('/map/reverse-geocode?lat=75') .get('/map/reverse-geocode?lat=75')
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[lon] Invalid input: expected number, received NaN']));
errorDto.validationError([{ path: ['lon'], message: 'Invalid input: expected number, received NaN' }]),
);
}); });
const reverseGeocodeTestCases = [ const reverseGeocodeTestCases = [
+9 -18
View File
@@ -105,11 +105,7 @@ describe(`/oauth`, () => {
it(`should throw an error if a redirect uri is not provided`, async () => { it(`should throw an error if a redirect uri is not provided`, async () => {
const { status, body } = await request(app).post('/oauth/authorize').send({}); const { status, body } = await request(app).post('/oauth/authorize').send({});
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[redirectUri] Invalid input: expected string, received undefined']));
errorDto.validationError([
{ path: ['redirectUri'], message: 'Invalid input: expected string, received undefined' },
]),
);
}); });
it('should return a redirect uri', async () => { it('should return a redirect uri', async () => {
@@ -168,17 +164,13 @@ describe(`/oauth`, () => {
it(`should throw an error if a url is not provided`, async () => { it(`should throw an error if a url is not provided`, async () => {
const { status, body } = await request(app).post('/oauth/callback').send({}); const { status, body } = await request(app).post('/oauth/callback').send({});
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[url] Invalid input: expected string, received undefined']));
errorDto.validationError([{ path: ['url'], message: 'Invalid input: expected string, received undefined' }]),
);
}); });
it(`should throw an error if the url is empty`, async () => { it(`should throw an error if the url is empty`, async () => {
const { status, body } = await request(app).post('/oauth/callback').send({ url: '' }); const { status, body } = await request(app).post('/oauth/callback').send({ url: '' });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[url] Too small: expected string to have >=1 characters']));
errorDto.validationError([{ path: ['url'], message: 'Too small: expected string to have >=1 characters' }]),
);
}); });
it(`should throw an error if the state is not provided`, async () => { it(`should throw an error if the state is not provided`, async () => {
@@ -340,7 +332,9 @@ describe(`/oauth`, () => {
const { status, body } = await request(app).post('/oauth/callback').send(callbackParams); const { status, body } = await request(app).post('/oauth/callback').send(callbackParams);
expect(status).toBe(500); expect(status).toBe(500);
expect(body).toMatchObject({ expect(body).toMatchObject({
error: 'Internal Server Error',
message: 'Failed to finish oauth', message: 'Failed to finish oauth',
statusCode: 500,
}); });
}); });
@@ -359,7 +353,7 @@ describe(`/oauth`, () => {
const callbackParams = await loginWithOAuth('oauth-no-auto-register'); const callbackParams = await loginWithOAuth('oauth-no-auto-register');
const { status, body } = await request(app).post('/oauth/callback').send(callbackParams); const { status, body } = await request(app).post('/oauth/callback').send(callbackParams);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('OAuth authentication failed')); expect(body).toEqual(errorDto.badRequest('User does not exist and auto registering is disabled.'));
}); });
it('should link to an existing user by email', async () => { it('should link to an existing user by email', async () => {
@@ -383,11 +377,7 @@ describe(`/oauth`, () => {
it(`should throw an error if the logout_token is not provided`, async () => { it(`should throw an error if the logout_token is not provided`, async () => {
const { status, body } = await request(app).post('/oauth/backchannel-logout').send({}); const { status, body } = await request(app).post('/oauth/backchannel-logout').send({});
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest(['[logout_token] Invalid input: expected string, received undefined']));
errorDto.validationError([
{ path: ['logout_token'], message: 'Invalid input: expected string, received undefined' },
]),
);
}); });
it(`should throw an error if an invalid logout token is provided`, async () => { it(`should throw an error if an invalid logout token is provided`, async () => {
@@ -505,10 +495,11 @@ describe(`/oauth`, () => {
}); });
it('should reject OAuth discovery over HTTP', async () => { it('should reject OAuth discovery over HTTP', async () => {
const { status } = await request(app) const { status, body } = await request(app)
.post('/oauth/authorize') .post('/oauth/authorize')
.send({ redirectUri: 'http://127.0.0.1:2285/auth/login' }); .send({ redirectUri: 'http://127.0.0.1:2285/auth/login' });
expect(status).toBe(500); expect(status).toBe(500);
expect(body).toMatchObject({ statusCode: 500 });
}); });
}); });
}); });
@@ -341,9 +341,7 @@ describe('/shared-links', () => {
.set('Authorization', `Bearer ${user1.accessToken}`); .set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest());
errorDto.validationError([{ path: [], message: 'Invalid input: expected object, received undefined' }]),
);
}); });
it('should require an asset/album id', async () => { it('should require an asset/album id', async () => {
+2 -9
View File
@@ -41,9 +41,7 @@ describe('/stacks', () => {
.send({ assetIds: [asset.id] }); .send({ assetIds: [asset.id] });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest());
errorDto.validationError([{ path: ['assetIds'], message: 'Too small: expected array to have >=2 items' }]),
);
}); });
it('should require a valid id', async () => { it('should require a valid id', async () => {
@@ -53,12 +51,7 @@ describe('/stacks', () => {
.send({ assetIds: [uuidDto.invalid, uuidDto.invalid] }); .send({ assetIds: [uuidDto.invalid, uuidDto.invalid] });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(errorDto.badRequest());
errorDto.validationError([
{ path: ['assetIds', 0], message: 'Invalid UUID' },
{ path: ['assetIds', 1], message: 'Invalid UUID' },
]),
);
}); });
it('should require access', async () => { it('should require access', async () => {
+2 -2
View File
@@ -309,7 +309,7 @@ describe('/tags', () => {
.get(`/tags/${uuidDto.invalid}`) .get(`/tags/${uuidDto.invalid}`)
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }])); expect(body).toEqual(errorDto.badRequest(['[id] Invalid UUID']));
}); });
it('should get tag details', async () => { it('should get tag details', async () => {
@@ -427,7 +427,7 @@ describe('/tags', () => {
.delete(`/tags/${uuidDto.invalid}`) .delete(`/tags/${uuidDto.invalid}`)
.set('Authorization', `Bearer ${admin.accessToken}`); .set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }])); expect(body).toEqual(errorDto.badRequest(['[id] Invalid UUID']));
}); });
it('should delete a tag', async () => { it('should delete a tag', async () => {
@@ -108,20 +108,14 @@ describe('/admin/users', () => {
expect(body).toEqual(errorDto.forbidden); expect(body).toEqual(errorDto.forbidden);
}); });
for (const [key, message] of [ for (const key of ['password', 'email', 'name', 'quotaSizeInBytes', 'shouldChangePassword', 'notify']) {
['password', 'Invalid input: expected string, received null'],
['email', 'Invalid input: expected email, received object'],
['name', 'Invalid input: expected string, received null'],
['shouldChangePassword', 'Invalid input: expected boolean, received null'],
['notify', 'Invalid input: expected boolean, received null'],
] as const) {
it(`should not allow null ${key}`, async () => { it(`should not allow null ${key}`, async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.post(`/admin/users`) .post(`/admin/users`)
.set('Authorization', `Bearer ${admin.accessToken}`) .set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ...createUserDto.user1, [key]: null }); .send({ ...createUserDto.user1, [key]: null });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.validationError([{ path: [key], message }])); expect(body).toEqual(errorDto.badRequest());
}); });
} }
@@ -159,19 +153,14 @@ describe('/admin/users', () => {
expect(body).toEqual(errorDto.forbidden); expect(body).toEqual(errorDto.forbidden);
}); });
for (const [key, message] of [ for (const key of ['password', 'email', 'name', 'shouldChangePassword']) {
['password', 'Invalid input: expected string, received null'],
['email', 'Invalid input: expected email, received object'],
['name', 'Invalid input: expected string, received null'],
['shouldChangePassword', 'Invalid input: expected boolean, received null'],
] as const) {
it(`should not allow null ${key}`, async () => { it(`should not allow null ${key}`, async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.put(`/admin/users/${uuidDto.notFound}`) .put(`/admin/users/${uuidDto.notFound}`)
.set('Authorization', `Bearer ${admin.accessToken}`) .set('Authorization', `Bearer ${admin.accessToken}`)
.send({ [key]: null }); .send({ [key]: null });
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(errorDto.validationError([{ path: [key], message }])); expect(body).toEqual(errorDto.badRequest());
}); });
} }
+3 -7
View File
@@ -120,7 +120,7 @@ describe('/users', () => {
.set('Authorization', `Bearer ${nonAdmin.accessToken}`); .set('Authorization', `Bearer ${nonAdmin.accessToken}`);
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toMatchObject(errorDto.badRequest('Email is not available')); expect(body).toMatchObject(errorDto.badRequest('Email already in use by another account'));
}); });
it('should update my email', async () => { it('should update my email', async () => {
@@ -179,9 +179,7 @@ describe('/users', () => {
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(
errorDto.validationError([ errorDto.badRequest(['[download.archiveSize] Invalid input: expected int, received number']),
{ path: ['download', 'archiveSize'], message: 'Invalid input: expected int, received number' },
]),
); );
}); });
@@ -209,9 +207,7 @@ describe('/users', () => {
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual( expect(body).toEqual(
errorDto.validationError([ errorDto.badRequest(['[download.includeEmbeddedVideos] Invalid input: expected boolean, received number']),
{ path: ['download', 'includeEmbeddedVideos'], message: 'Invalid input: expected boolean, received number' },
]),
); );
}); });
@@ -32,12 +32,8 @@ export function generateThumbhash(rng: SeededRandom): string {
return Array.from({ length: 10 }, () => rng.nextInt(0, 256).toString(16).padStart(2, '0')).join(''); return Array.from({ length: 10 }, () => rng.nextInt(0, 256).toString(16).padStart(2, '0')).join('');
} }
export function generateDuration(rng: SeededRandom): number { export function generateDuration(rng: SeededRandom): string {
return ( return `${rng.nextInt(GENERATION_CONSTANTS.MIN_VIDEO_DURATION_SECONDS, GENERATION_CONSTANTS.MAX_VIDEO_DURATION_SECONDS)}.${rng.nextInt(0, 1000).toString().padStart(3, '0')}`;
rng.nextInt(GENERATION_CONSTANTS.MIN_VIDEO_DURATION_SECONDS, GENERATION_CONSTANTS.MAX_VIDEO_DURATION_SECONDS) *
1000 +
rng.nextInt(0, 1000)
);
} }
export function generateUUID(): string { export function generateUUID(): string {
@@ -3,7 +3,6 @@
*/ */
import { import {
AlbumUserRole,
AssetTypeEnum, AssetTypeEnum,
AssetVisibility, AssetVisibility,
UserAvatarColor, UserAvatarColor,
@@ -421,7 +420,9 @@ export function getAlbum(
albumThumbnailAssetId: album.thumbnailAssetId, albumThumbnailAssetId: album.thumbnailAssetId,
createdAt: album.createdAt, createdAt: album.createdAt,
updatedAt: album.updatedAt, updatedAt: album.updatedAt,
albumUsers: [{ user: albumOwner, role: AlbumUserRole.Owner }], ownerId: albumOwner.id,
owner: albumOwner,
albumUsers: [], // Empty array for non-shared album
shared: false, shared: false,
hasSharedLink: false, hasSharedLink: false,
isActivityEnabled: true, isActivityEnabled: true,
@@ -43,7 +43,7 @@ export type MockTimelineAsset = {
isTrashed: boolean; isTrashed: boolean;
isVideo: boolean; isVideo: boolean;
isImage: boolean; isImage: boolean;
duration: number | null; duration: string | null;
projectionType: string | null; projectionType: string | null;
livePhotoVideoId: string | null; livePhotoVideoId: string | null;
city: string | null; city: string | null;
-1
View File
@@ -223,7 +223,6 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI
'.jp2', '.jp2',
'.jpe', '.jpe',
'.jxl', '.jxl',
'.mpo',
'.svg', '.svg',
'.tif', '.tif',
'.tiff', '.tiff',
@@ -304,7 +304,7 @@ test.describe('Timeline', () => {
await page.keyboard.down('Shift'); await page.keyboard.down('Shift');
await thumbnailUtils.withAssetId(page, assets[2].id).hover(); await thumbnailUtils.withAssetId(page, assets[2].id).hover();
await expect( await expect(
thumbnailUtils.locator(page).locator('.absolute.top-0.size-full.bg-immich-primary.opacity-40'), thumbnailUtils.locator(page).locator('.absolute.top-0.h-full.w-full.bg-immich-primary.opacity-40'),
).toHaveCount(3); ).toHaveCount(3);
await thumbnailUtils.selectButton(page, assets[2].id).click(); await thumbnailUtils.selectButton(page, assets[2].id).click();
await page.keyboard.up('Shift'); await page.keyboard.up('Shift');
@@ -349,7 +349,7 @@ test.describe('Timeline', () => {
expect(visibleMockAssetsYearMonths).toContain(month); expect(visibleMockAssetsYearMonths).toContain(month);
} }
}); });
test.skip('Deep link to last photo, scroll up', async ({ page }) => { test('Deep link to last photo, scroll up', async ({ page }) => {
const lastAsset = assets.at(-1)!; const lastAsset = assets.at(-1)!;
await pageUtils.deepLinkPhotosPage(page, lastAsset.id); await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
@@ -361,7 +361,7 @@ test.describe('Timeline', () => {
await thumbnailUtils.expectInViewport(page, '14e5901f-fd7f-40c0-b186-4d7e7fc67968'); await thumbnailUtils.expectInViewport(page, '14e5901f-fd7f-40c0-b186-4d7e7fc67968');
}); });
test.skip('Deep link to first bucket, scroll down', async ({ page }) => { test('Deep link to first bucket, scroll down', async ({ page }) => {
const lastAsset = assets.at(0)!; const lastAsset = assets.at(0)!;
await pageUtils.deepLinkPhotosPage(page, lastAsset.id); await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
await timelineUtils.locator(page).hover(); await timelineUtils.locator(page).hover();
@@ -440,7 +440,7 @@ test.describe('Timeline', () => {
await thumbnailUtils.expectInViewport(page, asset.id); await thumbnailUtils.expectInViewport(page, asset.id);
await thumbnailUtils.expectSelectedDisabled(page, asset.id); await thumbnailUtils.expectSelectedDisabled(page, asset.id);
}); });
test.skip('Add photos to album', async ({ page }) => { test('Add photos to album', async ({ page }) => {
const album = timelineRestData.album; const album = timelineRestData.album;
await pageUtils.openAlbumPage(page, album.id); await pageUtils.openAlbumPage(page, album.id);
await page.locator('nav button[aria-label="Add photos"]').click(); 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 page.getByText('Photos', { exact: true }).click();
await thumbnailUtils.expectInViewport(page, assetToFavorite.id); await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
}); });
test.skip('open /favorites, archive photo, unarchive photo', async ({ page }) => { test('open /favorites, archive photo, unarchive photo', async ({ page }) => {
await pageUtils.openFavorites(page); await pageUtils.openFavorites(page);
const assetToArchive = getAsset(timelineRestData, 'ad31e29f-2069-4574-b9a9-ad86523c92cb')!; const assetToArchive = getAsset(timelineRestData, 'ad31e29f-2069-4574-b9a9-ad86523c92cb')!;
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover(); await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();
-2
View File
@@ -1761,7 +1761,6 @@
"play_original_video": "Play original video", "play_original_video": "Play original video",
"play_original_video_setting_description": "Prefer playback of original videos rather than transcoded videos. If original asset is not compatible it may not playback correctly.", "play_original_video_setting_description": "Prefer playback of original videos rather than transcoded videos. If original asset is not compatible it may not playback correctly.",
"play_transcoded_video": "Play transcoded video", "play_transcoded_video": "Play transcoded video",
"playback_speed": "Playback speed",
"please_auth_to_access": "Please authenticate to access", "please_auth_to_access": "Please authenticate to access",
"port": "Port", "port": "Port",
"preferences_settings_subtitle": "Manage the app's preferences", "preferences_settings_subtitle": "Manage the app's preferences",
@@ -2437,7 +2436,6 @@
"workflows": "Workflows", "workflows": "Workflows",
"workflows_help_text": "Workflows automate actions on your assets based on triggers and filters", "workflows_help_text": "Workflows automate actions on your assets based on triggers and filters",
"wrong_pin_code": "Wrong PIN code", "wrong_pin_code": "Wrong PIN code",
"x_of_total": "{x}/{total}",
"year": "Year", "year": "Year",
"years_ago": "{years, plural, one {# year} other {# years}} ago", "years_ago": "{years, plural, one {# year} other {# years}} ago",
"yes": "Yes", "yes": "Yes",
+6 -6
View File
@@ -48,14 +48,14 @@ FROM python:3.13-slim-trixie@sha256:d168b8d9eb761f4d3fe305ebd04aeb7e7f2de0297cec
RUN apt-get update && \ RUN apt-get update && \
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \ apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.32.7/intel-igc-core-2_2.32.7+21184_amd64.deb && \ wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-core-2_2.28.4+20760_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.32.7/intel-igc-opencl-2_2.32.7+21184_amd64.deb && \ wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-opencl-2_2.28.4+20760_amd64.deb && \
wget -nv https://github.com/intel/compute-runtime/releases/download/26.14.37833.4/intel-opencl-icd_26.14.37833.4-0_amd64.deb && \ wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/intel-opencl-icd_26.05.37020.3-0_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \ wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \ wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \
wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \ wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \
# TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file # TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file
wget -nv https://github.com/intel/compute-runtime/releases/download/26.14.37833.4/libigdgmm12_22.9.0_amd64.deb && \ wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/libigdgmm12_22.9.0_amd64.deb && \
dpkg -i *.deb && \ dpkg -i *.deb && \
rm *.deb && \ rm *.deb && \
apt-get remove wget -yqq && \ apt-get remove wget -yqq && \
@@ -68,7 +68,7 @@ ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
RUN apt-get update && \ RUN apt-get update && \
# Pascal support was dropped in 9.11 # Pascal support was dropped in 9.11
apt-get install --no-install-recommends -yqq libcudnn9-cuda-12=9.10.2.21-1 tzdata && \ apt-get install --no-install-recommends -yqq libcudnn9-cuda-12=9.10.2.21-1 && \
apt-get clean && \ apt-get clean && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
@@ -112,7 +112,7 @@ ARG RKNN_TOOLKIT_VERSION="v2.3.0"
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \ ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
MACHINE_LEARNING_MODEL_ARENA=false MACHINE_LEARNING_MODEL_ARENA=false
ADD --chmod=644 --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 "https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/${RKNN_TOOLKIT_VERSION}/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so" /usr/lib/ ADD --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 "https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/${RKNN_TOOLKIT_VERSION}/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so" /usr/lib/
FROM prod-${DEVICE} AS prod FROM prod-${DEVICE} AS prod
+1 -4
View File
@@ -183,10 +183,7 @@ async def predict(
text: str | None = Form(default=None), text: str | None = Form(default=None),
) -> Any: ) -> Any:
if image is not None: if image is not None:
decoded = await run(lambda: decode_pil(image)) inputs: Image | str = await run(lambda: decode_pil(image))
if decoded.width == 0 or decoded.height == 0:
raise HTTPException(400, "Image has zero width or height")
inputs: Image | str = decoded
elif text is not None: elif text is not None:
inputs = text inputs = text
else: else:
+2 -2
View File
@@ -9,12 +9,12 @@ dependencies = [
"aiocache>=0.12.1,<1.0", "aiocache>=0.12.1,<1.0",
"fastapi>=0.95.2,<1.0", "fastapi>=0.95.2,<1.0",
"gunicorn>=21.1.0", "gunicorn>=21.1.0",
"huggingface-hub>=1.0,<2.0", "huggingface-hub>=0.20.1,<1.0",
"insightface>=0.7.3,<1.0", "insightface>=0.7.3,<1.0",
"numpy<2.4.0", "numpy<2.4.0",
"opencv-python-headless>=4.7.0.72,<5.0", "opencv-python-headless>=4.7.0.72,<5.0",
"orjson>=3.9.5", "orjson>=3.9.5",
"pillow>=12.2,<13", "pillow>=12.2,<12.3",
"pydantic>=2.0.0,<3", "pydantic>=2.0.0,<3",
"pydantic-settings>=2.5.2,<3", "pydantic-settings>=2.5.2,<3",
"python-multipart>=0.0.6,<1.0", "python-multipart>=0.0.6,<1.0",
-13
View File
@@ -1198,19 +1198,6 @@ class TestLoad:
mock_model.model_format = ModelFormat.ONNX mock_model.model_format = ModelFormat.ONNX
@pytest.mark.parametrize("size", [(0, 100), (100, 0), (0, 0)])
def test_predict_rejects_empty_image(size: tuple[int, int], deployed_app: TestClient) -> None:
with mock.patch("immich_ml.main.decode_pil", return_value=Image.new("RGB", size)):
response = deployed_app.post(
"http://localhost:3003/predict",
data={"entries": json.dumps({"clip": {"visual": {"modelName": "ViT-B-32__openai"}}})},
files={"image": b"fake image bytes"},
)
assert response.status_code == 400
assert "zero" in response.json()["detail"].lower()
def test_root_endpoint(deployed_app: TestClient) -> None: def test_root_endpoint(deployed_app: TestClient) -> None:
response = deployed_app.get("http://localhost:3003") response = deployed_app.get("http://localhost:3003")
+6 -15
View File
@@ -14,27 +14,18 @@ config_roots = [
] ]
[tools] [tools]
node = "24.15.0" node = "24.14.1"
flutter = "3.41.7" flutter = "3.41.6"
pnpm = "10.33.1" pnpm = "10.33.0"
terragrunt = "1.0.2" terragrunt = "1.0.0"
opentofu = "1.11.6" opentofu = "1.11.5"
java = "21.0.2" java = "21.0.2"
[tools."github:CQLabs/homebrew-dcm"] [tools."github:CQLabs/homebrew-dcm"]
version = "1.37.0" version = "1.35.1"
bin = "dcm" bin = "dcm"
postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm" postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm"
[tools."github:jellyfin/jellyfin-ffmpeg"]
version = "7.1.3-6"
[tools."github:jellyfin/jellyfin-ffmpeg".platforms]
linux-x64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_linux64-gpl.tar.xz" }
linux-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_linuxarm64-gpl.tar.xz" }
macos-x64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_mac64-gpl.tar.xz" }
macos-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz" }
[settings] [settings]
experimental = true experimental = true
pin = true pin = true
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"dart.flutterSdkPath": ".fvm/versions/3.41.9", "dart.flutterSdkPath": ".fvm/versions/3.41.7",
"dart.lineLength": 120, "dart.lineLength": 120,
"[dart]": { "[dart]": {
"editor.rulers": [ "editor.rulers": [
@@ -216,7 +216,7 @@ data class PlatformAsset (
val updatedAt: Long? = null, val updatedAt: Long? = null,
val width: Long? = null, val width: Long? = null,
val height: Long? = null, val height: Long? = null,
val durationMs: Long, val durationInSeconds: Long,
val orientation: Long, val orientation: Long,
val isFavorite: Boolean, val isFavorite: Boolean,
val adjustmentTime: Long? = null, val adjustmentTime: Long? = null,
@@ -234,14 +234,14 @@ data class PlatformAsset (
val updatedAt = pigeonVar_list[4] as Long? val updatedAt = pigeonVar_list[4] as Long?
val width = pigeonVar_list[5] as Long? val width = pigeonVar_list[5] as Long?
val height = pigeonVar_list[6] as Long? val height = pigeonVar_list[6] as Long?
val durationMs = pigeonVar_list[7] as Long val durationInSeconds = pigeonVar_list[7] as Long
val orientation = pigeonVar_list[8] as Long val orientation = pigeonVar_list[8] as Long
val isFavorite = pigeonVar_list[9] as Boolean val isFavorite = pigeonVar_list[9] as Boolean
val adjustmentTime = pigeonVar_list[10] as Long? val adjustmentTime = pigeonVar_list[10] as Long?
val latitude = pigeonVar_list[11] as Double? val latitude = pigeonVar_list[11] as Double?
val longitude = pigeonVar_list[12] as Double? val longitude = pigeonVar_list[12] as Double?
val playbackStyle = pigeonVar_list[13] as PlatformAssetPlaybackStyle val playbackStyle = pigeonVar_list[13] as PlatformAssetPlaybackStyle
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationMs, orientation, isFavorite, adjustmentTime, latitude, longitude, playbackStyle) return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, adjustmentTime, latitude, longitude, playbackStyle)
} }
} }
fun toList(): List<Any?> { fun toList(): List<Any?> {
@@ -253,7 +253,7 @@ data class PlatformAsset (
updatedAt, updatedAt,
width, width,
height, height,
durationMs, durationInSeconds,
orientation, orientation,
isFavorite, isFavorite,
adjustmentTime, adjustmentTime,
@@ -270,7 +270,7 @@ data class PlatformAsset (
return true return true
} }
val other = other as PlatformAsset 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.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) 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)
} }
override fun hashCode(): Int { override fun hashCode(): Int {
@@ -282,7 +282,7 @@ data class PlatformAsset (
result = 31 * result + MessagesPigeonUtils.deepHash(this.updatedAt) result = 31 * result + MessagesPigeonUtils.deepHash(this.updatedAt)
result = 31 * result + MessagesPigeonUtils.deepHash(this.width) result = 31 * result + MessagesPigeonUtils.deepHash(this.width)
result = 31 * result + MessagesPigeonUtils.deepHash(this.height) result = 31 * result + MessagesPigeonUtils.deepHash(this.height)
result = 31 * result + MessagesPigeonUtils.deepHash(this.durationMs) result = 31 * result + MessagesPigeonUtils.deepHash(this.durationInSeconds)
result = 31 * result + MessagesPigeonUtils.deepHash(this.orientation) result = 31 * result + MessagesPigeonUtils.deepHash(this.orientation)
result = 31 * result + MessagesPigeonUtils.deepHash(this.isFavorite) result = 31 * result + MessagesPigeonUtils.deepHash(this.isFavorite)
result = 31 * result + MessagesPigeonUtils.deepHash(this.adjustmentTime) result = 31 * result + MessagesPigeonUtils.deepHash(this.adjustmentTime)
@@ -178,7 +178,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
val height = c.getInt(heightColumn).toLong() val height = c.getInt(heightColumn).toLong()
// Duration is milliseconds // Duration is milliseconds
val duration = if (rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0L val duration = if (rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0L
else c.getLong(durationColumn) else c.getLong(durationColumn) / 1000
val orientation = c.getInt(orientationColumn) val orientation = c.getInt(orientationColumn)
val isFavorite = if (favoriteColumn == -1) false else c.getInt(favoriteColumn) != 0 val isFavorite = if (favoriteColumn == -1) false else c.getInt(favoriteColumn) != 0
+1 -1
View File
@@ -1 +1 @@
version: '>=1.29.0 <=1.37.0' version: '>=1.29.0 <=1.36.0'
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+16 -16
View File
@@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 54; objectVersion = 77;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@@ -751,7 +751,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240; CURRENT_PROJECT_VERSION = 240;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2W7AC6T8T5; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -760,7 +760,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.121.0; MARKETING_VERSION = 1.121.0;
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.profile; PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile;
PRODUCT_NAME = "Immich-Profile"; PRODUCT_NAME = "Immich-Profile";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -895,7 +895,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240; CURRENT_PROJECT_VERSION = 240;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2W7AC6T8T5; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -904,7 +904,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.121.0; MARKETING_VERSION = 1.121.0;
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.debug; PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug;
PRODUCT_NAME = "Immich-Debug"; PRODUCT_NAME = "Immich-Debug";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -925,7 +925,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240; CURRENT_PROJECT_VERSION = 240;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2W7AC6T8T5; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -958,7 +958,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240; CURRENT_PROJECT_VERSION = 240;
DEVELOPMENT_TEAM = 2W7AC6T8T5; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -975,7 +975,7 @@
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.debug.Widget; PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.Widget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
@@ -1001,7 +1001,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240; CURRENT_PROJECT_VERSION = 240;
DEVELOPMENT_TEAM = 2W7AC6T8T5; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -1041,7 +1041,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240; CURRENT_PROJECT_VERSION = 240;
DEVELOPMENT_TEAM = 2W7AC6T8T5; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -1057,7 +1057,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.profile.Widget; PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.Widget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -1081,7 +1081,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240; CURRENT_PROJECT_VERSION = 240;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2W7AC6T8T5; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -1098,7 +1098,7 @@
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.debug.ShareExtension; PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
@@ -1125,7 +1125,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240; CURRENT_PROJECT_VERSION = 240;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2W7AC6T8T5; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -1166,7 +1166,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240; CURRENT_PROJECT_VERSION = 240;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2W7AC6T8T5; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -1182,7 +1182,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.profile.ShareExtension; PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
+6 -6
View File
@@ -192,7 +192,7 @@ struct PlatformAsset: Hashable {
var updatedAt: Int64? = nil var updatedAt: Int64? = nil
var width: Int64? = nil var width: Int64? = nil
var height: Int64? = nil var height: Int64? = nil
var durationMs: Int64 var durationInSeconds: Int64
var orientation: Int64 var orientation: Int64
var isFavorite: Bool var isFavorite: Bool
var adjustmentTime: Int64? = nil var adjustmentTime: Int64? = nil
@@ -210,7 +210,7 @@ struct PlatformAsset: Hashable {
let updatedAt: Int64? = nilOrValue(pigeonVar_list[4]) let updatedAt: Int64? = nilOrValue(pigeonVar_list[4])
let width: Int64? = nilOrValue(pigeonVar_list[5]) let width: Int64? = nilOrValue(pigeonVar_list[5])
let height: Int64? = nilOrValue(pigeonVar_list[6]) let height: Int64? = nilOrValue(pigeonVar_list[6])
let durationMs = pigeonVar_list[7] as! Int64 let durationInSeconds = pigeonVar_list[7] as! Int64
let orientation = pigeonVar_list[8] as! Int64 let orientation = pigeonVar_list[8] as! Int64
let isFavorite = pigeonVar_list[9] as! Bool let isFavorite = pigeonVar_list[9] as! Bool
let adjustmentTime: Int64? = nilOrValue(pigeonVar_list[10]) let adjustmentTime: Int64? = nilOrValue(pigeonVar_list[10])
@@ -226,7 +226,7 @@ struct PlatformAsset: Hashable {
updatedAt: updatedAt, updatedAt: updatedAt,
width: width, width: width,
height: height, height: height,
durationMs: durationMs, durationInSeconds: durationInSeconds,
orientation: orientation, orientation: orientation,
isFavorite: isFavorite, isFavorite: isFavorite,
adjustmentTime: adjustmentTime, adjustmentTime: adjustmentTime,
@@ -244,7 +244,7 @@ struct PlatformAsset: Hashable {
updatedAt, updatedAt,
width, width,
height, height,
durationMs, durationInSeconds,
orientation, orientation,
isFavorite, isFavorite,
adjustmentTime, adjustmentTime,
@@ -257,7 +257,7 @@ struct PlatformAsset: Hashable {
if Swift.type(of: lhs) != Swift.type(of: rhs) { if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false 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.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) 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)
} }
func hash(into hasher: inout Hasher) { func hash(into hasher: inout Hasher) {
@@ -269,7 +269,7 @@ struct PlatformAsset: Hashable {
deepHashMessages(value: updatedAt, hasher: &hasher) deepHashMessages(value: updatedAt, hasher: &hasher)
deepHashMessages(value: width, hasher: &hasher) deepHashMessages(value: width, hasher: &hasher)
deepHashMessages(value: height, hasher: &hasher) deepHashMessages(value: height, hasher: &hasher)
deepHashMessages(value: durationMs, hasher: &hasher) deepHashMessages(value: durationInSeconds, hasher: &hasher)
deepHashMessages(value: orientation, hasher: &hasher) deepHashMessages(value: orientation, hasher: &hasher)
deepHashMessages(value: isFavorite, hasher: &hasher) deepHashMessages(value: isFavorite, hasher: &hasher)
deepHashMessages(value: adjustmentTime, hasher: &hasher) deepHashMessages(value: adjustmentTime, hasher: &hasher)
+1 -1
View File
@@ -171,7 +171,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
id: asset.localIdentifier, id: asset.localIdentifier,
name: "", name: "",
type: 0, type: 0,
durationMs: 0, durationInSeconds: 0,
orientation: 0, orientation: 0,
isFavorite: false, isFavorite: false,
playbackStyle: .unknown playbackStyle: .unknown
@@ -21,7 +21,7 @@ extension PHAsset {
updatedAt: modificationDate.map { Int64($0.timeIntervalSince1970) }, updatedAt: modificationDate.map { Int64($0.timeIntervalSince1970) },
width: Int64(pixelWidth), width: Int64(pixelWidth),
height: Int64(pixelHeight), height: Int64(pixelHeight),
durationMs: Int64(duration * 1000), durationInSeconds: Int64(duration),
orientation: 0, orientation: 0,
isFavorite: isFavorite, isFavorite: isFavorite,
adjustmentTime: adjustmentTimestamp, adjustmentTime: adjustmentTimestamp,
+26 -26
View File
@@ -1,35 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>AppGroupId</key> <key>AppGroupId</key>
<string>$(CUSTOM_GROUP_ID)</string> <string>$(CUSTOM_GROUP_ID)</string>
<key>NSExtension</key> <key>NSExtension</key>
<dict> <dict>
<key>NSExtensionAttributes</key> <key>NSExtensionAttributes</key>
<dict> <dict>
<key>IntentsSupported</key> <key>IntentsSupported</key>
<array> <array>
<string>INSendMessageIntent</string> <string>INSendMessageIntent</string>
</array> </array>
<key>NSExtensionActivationRule</key> <key>NSExtensionActivationRule</key>
<string>SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments, <string>SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments,
$attachment, ( ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url" $attachment, ( ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url"
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image" || ANY || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image" || ANY
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" || ANY
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" || ANY
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" ) ).@count &gt; 0 $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" ) ).@count &gt; 0
).@count &gt; 0 </string> ).@count &gt; 0 </string>
<key>PHSupportedMediaTypes</key> <key>PHSupportedMediaTypes</key>
<array> <array>
<string>Video</string> <string>Video</string>
<string>Image</string> <string>Image</string>
</array> </array>
</dict> </dict>
<key>NSExtensionMainStoryboard</key> <key>NSExtensionMainStoryboard</key>
<string>MainInterface</string> <string>MainInterface</string>
<key>NSExtensionPointIdentifier</key> <key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string> <string>com.apple.share-services</string>
</dict> </dict>
</dict> </dict>
</plist> </plist>
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.app.immich.share</string> <string>group.app.immich.share</string>
</array> </array>
</dict> </dict>
</plist> </plist>
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.app.immich.share</string> <string>group.app.immich.share</string>
</array> </array>
</dict> </dict>
</plist> </plist>
+1 -1
View File
@@ -1,5 +1,5 @@
app_identifier "app.alextran.immich" # The bundle identifier of your app app_identifier "app.alextran.immich" # The bundle identifier of your app
apple_id "altran@futo.org" # Your Apple email address apple_id "alex.tran1502@gmail.com" # Your Apple email address
# For more information about the Appfile, see: # For more information about the Appfile, see:
+41 -41
View File
@@ -17,11 +17,10 @@ default_platform(:ios)
platform :ios do platform :ios do
# Constants # Constants
TEAM_ID = "2W7AC6T8T5" TEAM_ID = "2F67MQ8R79"
CODE_SIGN_IDENTITY = "Apple Distribution: FUTO Holdings, Inc. (#{TEAM_ID})" CODE_SIGN_IDENTITY = "Apple Distribution: Hau Tran (#{TEAM_ID})"
BASE_BUNDLE_ID = "app.alextran.immich" BASE_BUNDLE_ID = "app.alextran.immich"
DEV_BUNDLE_ID = "tech.futo.immich.testflight"
# Helper method to get App Store Connect API key # Helper method to get App Store Connect API key
def get_api_key def get_api_key
app_store_connect_api_key( app_store_connect_api_key(
@@ -45,45 +44,47 @@ def get_version_from_pubspec
end end
# Helper method to configure code signing for all targets # Helper method to configure code signing for all targets
def configure_code_signing(base_bundle_id:, profile_name_main:, profile_name_share:, profile_name_widget:) def configure_code_signing(bundle_id_suffix: "", profile_name_main:, profile_name_share:, profile_name_widget:)
bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}"
# Runner (main app) # Runner (main app)
update_code_signing_settings( update_code_signing_settings(
use_automatic_signing: false, use_automatic_signing: false,
path: "./Runner.xcodeproj", path: "./Runner.xcodeproj",
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
code_sign_identity: CODE_SIGN_IDENTITY, code_sign_identity: CODE_SIGN_IDENTITY,
bundle_identifier: base_bundle_id, bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}",
profile_name: profile_name_main, profile_name: profile_name_main,
targets: ["Runner"] targets: ["Runner"]
) )
# ShareExtension # ShareExtension
update_code_signing_settings( update_code_signing_settings(
use_automatic_signing: false, use_automatic_signing: false,
path: "./Runner.xcodeproj", path: "./Runner.xcodeproj",
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
code_sign_identity: CODE_SIGN_IDENTITY, code_sign_identity: CODE_SIGN_IDENTITY,
bundle_identifier: "#{base_bundle_id}.ShareExtension", bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.ShareExtension",
profile_name: profile_name_share, profile_name: profile_name_share,
targets: ["ShareExtension"] targets: ["ShareExtension"]
) )
# WidgetExtension # WidgetExtension
update_code_signing_settings( update_code_signing_settings(
use_automatic_signing: false, use_automatic_signing: false,
path: "./Runner.xcodeproj", path: "./Runner.xcodeproj",
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
code_sign_identity: CODE_SIGN_IDENTITY, code_sign_identity: CODE_SIGN_IDENTITY,
bundle_identifier: "#{base_bundle_id}.Widget", bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.Widget",
profile_name: profile_name_widget, profile_name: profile_name_widget,
targets: ["WidgetExtension"] targets: ["WidgetExtension"]
) )
end end
# Helper method to build and upload to TestFlight # Helper method to build and upload to TestFlight
def build_and_upload( def build_and_upload(
api_key:, api_key:,
base_bundle_id:, bundle_id_suffix: "",
configuration: "Release", configuration: "Release",
distribute_external: true, distribute_external: true,
version_number: nil, version_number: nil,
@@ -91,8 +92,9 @@ end
profile_name_share:, profile_name_share:,
profile_name_widget: profile_name_widget:
) )
app_identifier = base_bundle_id bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}"
app_identifier = "#{BASE_BUNDLE_ID}#{bundle_suffix}"
# Set version number if provided # Set version number if provided
if version_number if version_number
increment_version_number(version_number: version_number) increment_version_number(version_number: version_number)
@@ -136,31 +138,31 @@ end
desc "iOS Development Build to TestFlight (requires separate bundle ID)" desc "iOS Development Build to TestFlight (requires separate bundle ID)"
lane :gha_testflight_dev do lane :gha_testflight_dev do
api_key = get_api_key api_key = get_api_key
# Download and install provisioning profiles from App Store Connect # Download and install provisioning profiles from App Store Connect
# Certificate is imported by GHA workflow into build.keychain # Certificate is imported by GHA workflow into build.keychain
# Capture profile names after each sigh call # Capture profile names after each sigh call
sigh(api_key: api_key, app_identifier: DEV_BUNDLE_ID, force: true) sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development", force: true)
main_profile_name = lane_context[SharedValues::SIGH_NAME] main_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.ShareExtension", force: true) sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.ShareExtension", force: true)
share_profile_name = lane_context[SharedValues::SIGH_NAME] share_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.Widget", force: true) sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.Widget", force: true)
widget_profile_name = lane_context[SharedValues::SIGH_NAME] widget_profile_name = lane_context[SharedValues::SIGH_NAME]
# Configure code signing for dev bundle IDs using the downloaded profile names # Configure code signing for dev bundle IDs using the downloaded profile names
configure_code_signing( configure_code_signing(
base_bundle_id: DEV_BUNDLE_ID, bundle_id_suffix: "development",
profile_name_main: main_profile_name, profile_name_main: main_profile_name,
profile_name_share: share_profile_name, profile_name_share: share_profile_name,
profile_name_widget: widget_profile_name profile_name_widget: widget_profile_name
) )
# Build and upload # Build and upload
build_and_upload( build_and_upload(
api_key: api_key, api_key: api_key,
base_bundle_id: DEV_BUNDLE_ID, bundle_id_suffix: "development",
configuration: "Profile", configuration: "Profile",
distribute_external: false, distribute_external: false,
profile_name_main: main_profile_name, profile_name_main: main_profile_name,
@@ -187,7 +189,6 @@ end
# Configure code signing for production bundle IDs # Configure code signing for production bundle IDs
configure_code_signing( configure_code_signing(
base_bundle_id: BASE_BUNDLE_ID,
profile_name_main: main_profile_name, profile_name_main: main_profile_name,
profile_name_share: share_profile_name, profile_name_share: share_profile_name,
profile_name_widget: widget_profile_name profile_name_widget: widget_profile_name
@@ -196,7 +197,6 @@ end
# Build and upload with version number # Build and upload with version number
build_and_upload( build_and_upload(
api_key: api_key, api_key: api_key,
base_bundle_id: BASE_BUNDLE_ID,
version_number: get_version_from_pubspec, version_number: get_version_from_pubspec,
distribute_external: false, distribute_external: false,
profile_name_main: main_profile_name, profile_name_main: main_profile_name,
@@ -243,30 +243,30 @@ end
desc "iOS Build Only (no TestFlight upload)" desc "iOS Build Only (no TestFlight upload)"
lane :gha_build_only do lane :gha_build_only do
# Use the same build process as the dev TestFlight lane, just skip the upload # Use the same build process as production, just skip the upload
# This ensures PR builds validate the same way as dev TestFlight builds # This ensures PR builds validate the same way as production builds
api_key = get_api_key api_key = get_api_key
# Download and install provisioning profiles from App Store Connect # Download and install provisioning profiles from App Store Connect
# Certificate is imported by GHA workflow into build.keychain # Certificate is imported by GHA workflow into build.keychain
sigh(api_key: api_key, app_identifier: DEV_BUNDLE_ID, force: true) sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development", force: true)
main_profile_name = lane_context[SharedValues::SIGH_NAME] main_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.ShareExtension", force: true) sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.ShareExtension", force: true)
share_profile_name = lane_context[SharedValues::SIGH_NAME] share_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.Widget", force: true) sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.Widget", force: true)
widget_profile_name = lane_context[SharedValues::SIGH_NAME] widget_profile_name = lane_context[SharedValues::SIGH_NAME]
# Configure code signing for dev bundle IDs # Configure code signing for dev bundle IDs
configure_code_signing( configure_code_signing(
base_bundle_id: DEV_BUNDLE_ID, bundle_id_suffix: "development",
profile_name_main: main_profile_name, profile_name_main: main_profile_name,
profile_name_share: share_profile_name, profile_name_share: share_profile_name,
profile_name_widget: widget_profile_name profile_name_widget: widget_profile_name
) )
# Build the app (same as gha_testflight_dev but without upload) # Build the app (same as gha_testflight_dev but without upload)
build_app( build_app(
scheme: "Runner", scheme: "Runner",
@@ -277,9 +277,9 @@ end
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual", xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
export_options: { export_options: {
provisioningProfiles: { provisioningProfiles: {
DEV_BUNDLE_ID => main_profile_name, "#{BASE_BUNDLE_ID}.development" => main_profile_name,
"#{DEV_BUNDLE_ID}.ShareExtension" => share_profile_name, "#{BASE_BUNDLE_ID}.development.ShareExtension" => share_profile_name,
"#{DEV_BUNDLE_ID}.Widget" => widget_profile_name "#{BASE_BUNDLE_ID}.development.Widget" => widget_profile_name
}, },
signingStyle: "manual", signingStyle: "manual",
signingCertificate: CODE_SIGN_IDENTITY signingCertificate: CODE_SIGN_IDENTITY
@@ -8,7 +8,6 @@ enum AlbumUserRole {
// do not change this order! // do not change this order!
editor, editor,
viewer, viewer,
owner,
} }
// Model for an album stored in the server // Model for an album stored in the server
@@ -25,7 +25,7 @@ sealed class BaseAsset {
final DateTime updatedAt; final DateTime updatedAt;
final int? width; final int? width;
final int? height; final int? height;
final int? durationMs; final int? durationInSeconds;
final bool isFavorite; final bool isFavorite;
final String? livePhotoVideoId; final String? livePhotoVideoId;
final bool isEdited; final bool isEdited;
@@ -38,7 +38,7 @@ sealed class BaseAsset {
required this.updatedAt, required this.updatedAt,
this.width, this.width,
this.height, this.height,
this.durationMs, this.durationInSeconds,
this.isFavorite = false, this.isFavorite = false,
this.livePhotoVideoId, this.livePhotoVideoId,
required this.isEdited, required this.isEdited,
@@ -53,17 +53,15 @@ sealed class BaseAsset {
AssetPlaybackStyle get playbackStyle { AssetPlaybackStyle get playbackStyle {
if (isVideo) return AssetPlaybackStyle.video; if (isVideo) return AssetPlaybackStyle.video;
if (isMotionPhoto) return AssetPlaybackStyle.livePhoto; if (isMotionPhoto) return AssetPlaybackStyle.livePhoto;
if (isImage && durationMs != null && durationMs! > 0) { if (isImage && durationInSeconds != null && durationInSeconds! > 0) return AssetPlaybackStyle.imageAnimated;
return AssetPlaybackStyle.imageAnimated;
}
if (isImage) return AssetPlaybackStyle.image; if (isImage) return AssetPlaybackStyle.image;
return AssetPlaybackStyle.unknown; return AssetPlaybackStyle.unknown;
} }
Duration get duration { Duration get duration {
final durationMs = this.durationMs; final durationInSeconds = this.durationInSeconds;
if (durationMs != null) { if (durationInSeconds != null) {
return Duration(milliseconds: durationMs); return Duration(seconds: durationInSeconds);
} }
return const Duration(); return const Duration();
} }
@@ -90,7 +88,7 @@ sealed class BaseAsset {
updatedAt: $updatedAt, updatedAt: $updatedAt,
width: ${width ?? "<NA>"}, width: ${width ?? "<NA>"},
height: ${height ?? "<NA>"}, height: ${height ?? "<NA>"},
durationMs: ${durationMs ?? "<NA>"}, durationInSeconds: ${durationInSeconds ?? "<NA>"},
isFavorite: $isFavorite, isFavorite: $isFavorite,
isEdited: $isEdited, isEdited: $isEdited,
}'''; }''';
@@ -106,7 +104,7 @@ sealed class BaseAsset {
updatedAt == other.updatedAt && updatedAt == other.updatedAt &&
width == other.width && width == other.width &&
height == other.height && height == other.height &&
durationMs == other.durationMs && durationInSeconds == other.durationInSeconds &&
isFavorite == other.isFavorite && isFavorite == other.isFavorite &&
isEdited == other.isEdited; isEdited == other.isEdited;
} }
@@ -121,7 +119,7 @@ sealed class BaseAsset {
updatedAt.hashCode ^ updatedAt.hashCode ^
width.hashCode ^ width.hashCode ^
height.hashCode ^ height.hashCode ^
durationMs.hashCode ^ durationInSeconds.hashCode ^
isFavorite.hashCode ^ isFavorite.hashCode ^
isEdited.hashCode; isEdited.hashCode;
} }
@@ -23,7 +23,7 @@ class LocalAsset extends BaseAsset {
required super.updatedAt, required super.updatedAt,
super.width, super.width,
super.height, super.height,
super.durationMs, super.durationInSeconds,
super.isFavorite = false, super.isFavorite = false,
super.livePhotoVideoId, super.livePhotoVideoId,
this.orientation = 0, this.orientation = 0,
@@ -58,7 +58,7 @@ class LocalAsset extends BaseAsset {
updatedAt: $updatedAt, updatedAt: $updatedAt,
width: ${width ?? "<NA>"}, width: ${width ?? "<NA>"},
height: ${height ?? "<NA>"}, height: ${height ?? "<NA>"},
durationMs: ${durationMs ?? "<NA>"}, durationInSeconds: ${durationInSeconds ?? "<NA>"},
playbackStyle: $playbackStyle, playbackStyle: $playbackStyle,
remoteId: ${remoteId ?? "<NA>"}, remoteId: ${remoteId ?? "<NA>"},
cloudId: ${cloudId ?? "<NA>"}, cloudId: ${cloudId ?? "<NA>"},
@@ -108,7 +108,7 @@ class LocalAsset extends BaseAsset {
DateTime? updatedAt, DateTime? updatedAt,
int? width, int? width,
int? height, int? height,
int? durationMs, int? durationInSeconds,
bool? isFavorite, bool? isFavorite,
int? orientation, int? orientation,
AssetPlaybackStyle? playbackStyle, AssetPlaybackStyle? playbackStyle,
@@ -128,7 +128,7 @@ class LocalAsset extends BaseAsset {
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
width: width ?? this.width, width: width ?? this.width,
height: height ?? this.height, height: height ?? this.height,
durationMs: durationMs ?? this.durationMs, durationInSeconds: durationInSeconds ?? this.durationInSeconds,
isFavorite: isFavorite ?? this.isFavorite, isFavorite: isFavorite ?? this.isFavorite,
orientation: orientation ?? this.orientation, orientation: orientation ?? this.orientation,
playbackStyle: playbackStyle ?? this.playbackStyle, playbackStyle: playbackStyle ?? this.playbackStyle,
@@ -22,7 +22,7 @@ class RemoteAsset extends BaseAsset {
required super.updatedAt, required super.updatedAt,
super.width, super.width,
super.height, super.height,
super.durationMs, super.durationInSeconds,
super.isFavorite = false, super.isFavorite = false,
this.thumbHash, this.thumbHash,
this.visibility = AssetVisibility.timeline, this.visibility = AssetVisibility.timeline,
@@ -57,7 +57,7 @@ class RemoteAsset extends BaseAsset {
updatedAt: $updatedAt, updatedAt: $updatedAt,
width: ${width ?? "<NA>"}, width: ${width ?? "<NA>"},
height: ${height ?? "<NA>"}, height: ${height ?? "<NA>"},
durationMs: ${durationMs ?? "<NA>"}, durationInSeconds: ${durationInSeconds ?? "<NA>"},
localId: ${localId ?? "<NA>"}, localId: ${localId ?? "<NA>"},
isFavorite: $isFavorite, isFavorite: $isFavorite,
thumbHash: ${thumbHash ?? "<NA>"}, thumbHash: ${thumbHash ?? "<NA>"},
@@ -102,7 +102,7 @@ class RemoteAsset extends BaseAsset {
DateTime? updatedAt, DateTime? updatedAt,
int? width, int? width,
int? height, int? height,
int? durationMs, int? durationInSeconds,
bool? isFavorite, bool? isFavorite,
String? thumbHash, String? thumbHash,
AssetVisibility? visibility, AssetVisibility? visibility,
@@ -121,7 +121,7 @@ class RemoteAsset extends BaseAsset {
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
width: width ?? this.width, width: width ?? this.width,
height: height ?? this.height, height: height ?? this.height,
durationMs: durationMs ?? this.durationMs, durationInSeconds: durationInSeconds ?? this.durationInSeconds,
isFavorite: isFavorite ?? this.isFavorite, isFavorite: isFavorite ?? this.isFavorite,
thumbHash: thumbHash ?? this.thumbHash, thumbHash: thumbHash ?? this.thumbHash,
visibility: visibility ?? this.visibility, visibility: visibility ?? this.visibility,
@@ -146,7 +146,7 @@ class RemoteAssetExif extends RemoteAsset {
required super.updatedAt, required super.updatedAt,
super.width, super.width,
super.height, super.height,
super.durationMs, super.durationInSeconds,
super.isFavorite = false, super.isFavorite = false,
super.thumbHash, super.thumbHash,
super.visibility = AssetVisibility.timeline, super.visibility = AssetVisibility.timeline,
@@ -178,7 +178,7 @@ class RemoteAssetExif extends RemoteAsset {
DateTime? updatedAt, DateTime? updatedAt,
int? width, int? width,
int? height, int? height,
int? durationMs, int? durationInSeconds,
bool? isFavorite, bool? isFavorite,
String? thumbHash, String? thumbHash,
AssetVisibility? visibility, AssetVisibility? visibility,
@@ -198,7 +198,7 @@ class RemoteAssetExif extends RemoteAsset {
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
width: width ?? this.width, width: width ?? this.width,
height: height ?? this.height, height: height ?? this.height,
durationMs: durationMs ?? this.durationMs, durationInSeconds: durationInSeconds ?? this.durationInSeconds,
isFavorite: isFavorite ?? this.isFavorite, isFavorite: isFavorite ?? this.isFavorite,
thumbHash: thumbHash ?? this.thumbHash, thumbHash: thumbHash ?? this.thumbHash,
visibility: visibility ?? this.visibility, visibility: visibility ?? this.visibility,
+1 -1
View File
@@ -29,7 +29,7 @@ class ExifInfo {
bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0; bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0;
String get exposureTime { String get exposureTime {
if (exposureSeconds == null || exposureSeconds! <= 0 || exposureSeconds!.isNaN) { if (exposureSeconds == null) {
return ""; return "";
} }
if (exposureSeconds! < 1) { if (exposureSeconds! < 1) {
@@ -337,7 +337,7 @@ class LocalSyncService {
a.createdAt.isAtSameMomentAs(b.createdAt) && a.createdAt.isAtSameMomentAs(b.createdAt) &&
a.width == b.width && a.width == b.width &&
a.height == b.height && a.height == b.height &&
a.durationMs == b.durationMs; a.durationInSeconds == b.durationInSeconds;
} }
final firstAdjustment = a.adjustmentTime?.millisecondsSinceEpoch ?? 0; final firstAdjustment = a.adjustmentTime?.millisecondsSinceEpoch ?? 0;
@@ -346,7 +346,7 @@ class LocalSyncService {
a.createdAt.isAtSameMomentAs(b.createdAt) && a.createdAt.isAtSameMomentAs(b.createdAt) &&
a.width == b.width && a.width == b.width &&
a.height == b.height && a.height == b.height &&
a.durationMs == b.durationMs && a.durationInSeconds == b.durationInSeconds &&
a.latitude == b.latitude && a.latitude == b.latitude &&
a.longitude == b.longitude; a.longitude == b.longitude;
} }
@@ -432,7 +432,7 @@ extension PlatformToLocalAsset on PlatformAsset {
updatedAt: tryFromSecondsSinceEpoch(updatedAt, isUtc: true) ?? DateTime.timestamp(), updatedAt: tryFromSecondsSinceEpoch(updatedAt, isUtc: true) ?? DateTime.timestamp(),
width: width, width: width,
height: height, height: height,
durationMs: durationMs, durationInSeconds: durationInSeconds,
isFavorite: isFavorite, isFavorite: isFavorite,
orientation: orientation, orientation: orientation,
playbackStyle: _toPlaybackStyle(playbackStyle), playbackStyle: _toPlaybackStyle(playbackStyle),
@@ -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/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.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/models/albums/album_search.model.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
class RemoteAlbumService { class RemoteAlbumService {
final DriftRemoteAlbumRepository _repository; final DriftRemoteAlbumRepository _repository;
@@ -28,6 +28,10 @@ class RemoteAlbumService {
return _repository.get(albumId); return _repository.get(albumId);
} }
Future<RemoteAlbum?> getByName(String albumName, String ownerId) {
return _repository.getByName(albumName, ownerId);
}
Future<List<RemoteAlbum>> sortAlbums( Future<List<RemoteAlbum>> sortAlbums(
List<RemoteAlbum> albums, List<RemoteAlbum> albums,
AlbumSortMode sortMode, { AlbumSortMode sortMode, {
@@ -82,18 +86,8 @@ class RemoteAlbumService {
return filtered; return filtered;
} }
Future<RemoteAlbum> createAlbum({ Future<RemoteAlbum> createAlbum({required String title, required List<String> assetIds, String? description}) async {
required String title, final album = await _albumApiRepository.createDriftAlbum(title, description: description, assetIds: assetIds);
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); await _repository.create(album, assetIds);
return album; return album;
@@ -107,10 +101,8 @@ class RemoteAlbumService {
bool? isActivityEnabled, bool? isActivityEnabled,
AlbumAssetOrder? order, AlbumAssetOrder? order,
}) async { }) async {
final owner = await _repository.getOwner(albumId);
final updatedAlbum = await _albumApiRepository.updateAlbum( final updatedAlbum = await _albumApiRepository.updateAlbum(
albumId, albumId,
owner,
name: name, name: name,
description: description, description: description,
thumbnailAssetId: thumbnailAssetId, thumbnailAssetId: thumbnailAssetId,
@@ -1,11 +1,8 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.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/local_album.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_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/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/repositories/drift_album_api_repository.dart';
import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/debug_print.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@@ -15,7 +12,6 @@ final syncLinkedAlbumServiceProvider = Provider(
ref.watch(localAlbumRepository), ref.watch(localAlbumRepository),
ref.watch(remoteAlbumRepository), ref.watch(remoteAlbumRepository),
ref.watch(driftAlbumApiRepositoryProvider), ref.watch(driftAlbumApiRepositoryProvider),
ref.watch(storeServiceProvider),
), ),
); );
@@ -23,14 +19,8 @@ class SyncLinkedAlbumService {
final DriftLocalAlbumRepository _localAlbumRepository; final DriftLocalAlbumRepository _localAlbumRepository;
final DriftRemoteAlbumRepository _remoteAlbumRepository; final DriftRemoteAlbumRepository _remoteAlbumRepository;
final DriftAlbumApiRepository _albumApiRepository; final DriftAlbumApiRepository _albumApiRepository;
final StoreService _storeService;
SyncLinkedAlbumService( SyncLinkedAlbumService(this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository);
this._localAlbumRepository,
this._remoteAlbumRepository,
this._albumApiRepository,
this._storeService,
);
final _log = Logger("SyncLinkedAlbumService"); final _log = Logger("SyncLinkedAlbumService");
@@ -113,11 +103,7 @@ class SyncLinkedAlbumService {
/// Creates a new remote album and links it to the local album /// Creates a new remote album and links it to the local album
Future<void> _createAndLinkNewRemoteAlbum(LocalAlbum localAlbum) async { Future<void> _createAndLinkNewRemoteAlbum(LocalAlbum localAlbum) async {
dPrint(() => "Creating new remote album for local album: ${localAlbum.name}"); dPrint(() => "Creating new remote album for local album: ${localAlbum.name}");
final newRemoteAlbum = await _albumApiRepository.createDriftAlbum( final newRemoteAlbum = await _albumApiRepository.createDriftAlbum(localAlbum.name, assetIds: []);
localAlbum.name,
_storeService.get(StoreKey.currentUser),
assetIds: [],
);
await _remoteAlbumRepository.create(newRemoteAlbum, []); await _remoteAlbumRepository.create(newRemoteAlbum, []);
return _localAlbumRepository.linkRemoteAlbum(localAlbum.id, newRemoteAlbum.id); return _localAlbumRepository.linkRemoteAlbum(localAlbum.id, newRemoteAlbum.id);
} }
@@ -3,7 +3,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
@@ -193,22 +192,17 @@ class SyncStreamService {
final remoteSyncAssets = data.cast<SyncAssetV1>(); final remoteSyncAssets = data.cast<SyncAssetV1>();
await _syncStreamRepository.updateAssetsV1(remoteSyncAssets); await _syncStreamRepository.updateAssetsV1(remoteSyncAssets);
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList()); final hasPermission = await _localFilesManager.hasManageMediaPermission();
} if (hasPermission) {
return; await _handleRemoteTrashed(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.checksum));
case SyncEntityType.assetV2: await _applyRemoteRestoreToLocal();
final remoteSyncAssets = data.cast<SyncAssetV2>(); } else {
await _syncStreamRepository.updateAssetsV2(remoteSyncAssets); _logger.warning("sync Trashed Assets cannot proceed because MANAGE_MEDIA permission is missing");
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { }
await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList());
} }
return; return;
case SyncEntityType.assetDeleteV1: case SyncEntityType.assetDeleteV1:
final remoteSyncAssets = data.cast<SyncAssetDeleteV1>(); return _syncStreamRepository.deleteAssetsV1(data.cast());
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
await _syncAssetDeletion(remoteSyncAssets.map((e) => e.assetId).toList());
}
return _syncStreamRepository.deleteAssetsV1(remoteSyncAssets);
case SyncEntityType.assetExifV1: case SyncEntityType.assetExifV1:
return _syncStreamRepository.updateAssetsExifV1(data.cast()); return _syncStreamRepository.updateAssetsExifV1(data.cast());
case SyncEntityType.assetEditV1: case SyncEntityType.assetEditV1:
@@ -221,12 +215,8 @@ class SyncStreamService {
return _syncStreamRepository.deleteAssetsMetadataV1(data.cast()); return _syncStreamRepository.deleteAssetsMetadataV1(data.cast());
case SyncEntityType.partnerAssetV1: case SyncEntityType.partnerAssetV1:
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner'); return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner');
case SyncEntityType.partnerAssetV2:
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'partner');
case SyncEntityType.partnerAssetBackfillV1: case SyncEntityType.partnerAssetBackfillV1:
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner backfill'); return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner backfill');
case SyncEntityType.partnerAssetBackfillV2:
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'partner backfill');
case SyncEntityType.partnerAssetDeleteV1: case SyncEntityType.partnerAssetDeleteV1:
return _syncStreamRepository.deleteAssetsV1(data.cast(), debugLabel: "partner"); return _syncStreamRepository.deleteAssetsV1(data.cast(), debugLabel: "partner");
case SyncEntityType.partnerAssetExifV1: case SyncEntityType.partnerAssetExifV1:
@@ -235,8 +225,6 @@ class SyncStreamService {
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'partner backfill'); return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'partner backfill');
case SyncEntityType.albumV1: case SyncEntityType.albumV1:
return _syncStreamRepository.updateAlbumsV1(data.cast()); return _syncStreamRepository.updateAlbumsV1(data.cast());
case SyncEntityType.albumV2:
return _syncStreamRepository.updateAlbumsV2(data.cast());
case SyncEntityType.albumDeleteV1: case SyncEntityType.albumDeleteV1:
return _syncStreamRepository.deleteAlbumsV1(data.cast()); return _syncStreamRepository.deleteAlbumsV1(data.cast());
case SyncEntityType.albumUserV1: case SyncEntityType.albumUserV1:
@@ -247,16 +235,10 @@ class SyncStreamService {
return _syncStreamRepository.deleteAlbumUsersV1(data.cast()); return _syncStreamRepository.deleteAlbumUsersV1(data.cast());
case SyncEntityType.albumAssetCreateV1: case SyncEntityType.albumAssetCreateV1:
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset create'); return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset create');
case SyncEntityType.albumAssetCreateV2:
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset create');
case SyncEntityType.albumAssetUpdateV1: case SyncEntityType.albumAssetUpdateV1:
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset update'); return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset update');
case SyncEntityType.albumAssetUpdateV2:
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset update');
case SyncEntityType.albumAssetBackfillV1: case SyncEntityType.albumAssetBackfillV1:
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset backfill'); return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset backfill');
case SyncEntityType.albumAssetBackfillV2:
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset backfill');
case SyncEntityType.albumAssetExifCreateV1: case SyncEntityType.albumAssetExifCreateV1:
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'album asset exif create'); return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'album asset exif create');
case SyncEntityType.albumAssetExifUpdateV1: case SyncEntityType.albumAssetExifUpdateV1:
@@ -358,47 +340,6 @@ class SyncStreamService {
} }
} }
Future<void> handleWsAssetUploadReadyV2Batch(List<dynamic> batchData) async {
if (batchData.isEmpty) return;
_logger.info('Processing batch of ${batchData.length} AssetUploadReadyV2 events');
final List<SyncAssetV2> assets = [];
final List<SyncAssetExifV1> exifs = [];
try {
for (final data in batchData) {
if (data is! Map<String, dynamic>) {
continue;
}
final payload = data;
final assetData = payload['asset'];
final exifData = payload['exif'];
if (assetData == null || exifData == null) {
continue;
}
final asset = SyncAssetV2.fromJson(assetData);
final exif = SyncAssetExifV1.fromJson(exifData);
if (asset != null && exif != null) {
assets.add(asset);
exifs.add(exif);
}
}
if (assets.isNotEmpty && exifs.isNotEmpty) {
await _syncStreamRepository.updateAssetsV2(assets, debugLabel: 'websocket-batch');
await _syncStreamRepository.updateAssetsExifV1(exifs, debugLabel: 'websocket-batch');
_logger.info('Successfully processed ${assets.length} assets in batch');
}
} catch (error, stackTrace) {
_logger.severe("Error processing AssetUploadReadyV2 websocket batch events", error, stackTrace);
}
}
Future<void> handleWsAssetEditReadyV1(dynamic data) async { Future<void> handleWsAssetEditReadyV1(dynamic data) async {
_logger.info('Processing AssetEditReadyV1 event'); _logger.info('Processing AssetEditReadyV1 event');
@@ -439,67 +380,28 @@ class SyncStreamService {
} }
} }
Future<void> handleWsAssetEditReadyV2(dynamic data) async { Future<void> _handleRemoteTrashed(Iterable<String> checksums) async {
_logger.info('Processing AssetEditReadyV2 event'); if (checksums.isEmpty) {
try {
if (data is! Map<String, dynamic>) {
throw ArgumentError("Invalid data format for AssetEditReadyV2 event");
}
final payload = data;
if (payload['asset'] == null) {
throw ArgumentError("Missing 'asset' field in AssetEditReadyV2 event data");
}
final asset = SyncAssetV2.fromJson(payload['asset']);
if (asset == null) {
throw ArgumentError("Failed to parse 'asset' field in AssetEditReadyV2 event data");
}
final assetEdits = (payload['edit'] as List<dynamic>)
.map((e) => SyncAssetEditV1.fromJson(e))
.whereType<SyncAssetEditV1>()
.toList();
await _syncStreamRepository.updateAssetsV2([asset], debugLabel: 'websocket-edit');
await _syncStreamRepository.replaceAssetEditsV1(asset.id, assetEdits, debugLabel: 'websocket-edit');
_logger.info(
'Successfully processed AssetEditReadyV2 event for asset ${asset.id} with ${assetEdits.length} edits',
);
} catch (error, stackTrace) {
_logger.severe("Error processing AssetEditReadyV2 websocket event", error, stackTrace);
}
}
Future<void> _handleRemoteDeleted(Iterable<String> remoteIds) async {
if (remoteIds.isEmpty) {
return Future.value(); return Future.value();
} else { } else {
final localAssetsToTrash = await _localAssetRepository.getAssetsFromBackupAlbums(remoteIds); final localAssetsToTrash = await _localAssetRepository.getAssetsFromBackupAlbums(checksums);
if (localAssetsToTrash.isNotEmpty) { if (localAssetsToTrash.isNotEmpty) {
await _trashLocalAssets(localAssetsToTrash); final mediaUrls = await Future.wait(
localAssetsToTrash.values
.expand((e) => e)
.map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())),
);
_logger.info("Moving to trash ${mediaUrls.join(", ")} assets");
final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList());
if (result) {
await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash);
}
} else { } else {
_logger.info("No assets found in backup-enabled albums for remote assets: $remoteIds"); _logger.info("No assets found in backup-enabled albums for assets: $checksums");
} }
} }
} }
Future<void> _trashLocalAssets(Map<String, List<LocalAsset>> localAssetsToTrash) async {
final mediaUrls = await Future.wait(
localAssetsToTrash.values
.expand((e) => e)
.map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())),
);
_logger.info("Moving to trash ${mediaUrls.join(", ")} assets");
final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList());
if (result) {
await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash);
}
}
Future<void> _applyRemoteRestoreToLocal() async { Future<void> _applyRemoteRestoreToLocal() async {
final assetsToRestore = await _trashedLocalAssetRepository.getToRestore(); final assetsToRestore = await _trashedLocalAssetRepository.getToRestore();
if (assetsToRestore.isNotEmpty) { if (assetsToRestore.isNotEmpty) {
@@ -509,23 +411,4 @@ class SyncStreamService {
_logger.info("No remote assets found for restoration"); _logger.info("No remote assets found for restoration");
} }
} }
Future<void> _syncAssetTrashStatus(List<String> remoteIds) async {
if (!(await _localFilesManager.hasManageMediaPermission())) {
_logger.warning("Syncing asset trash status cannot proceed because MANAGE_MEDIA permission is missing");
return;
}
await _handleRemoteDeleted(remoteIds);
await _applyRemoteRestoreToLocal();
}
Future<void> _syncAssetDeletion(List<String> remoteIds) async {
if (!(await _localFilesManager.hasManageMediaPermission())) {
_logger.warning("Syncing asset deletion cannot proceed because MANAGE_MEDIA permission is missing");
return;
}
await _handleRemoteDeleted(remoteIds);
}
} }
+2 -32
View File
@@ -186,7 +186,7 @@ class BackgroundSyncManager {
}); });
} }
Future<void> syncWebsocketBatchV1(List<dynamic> batchData) { Future<void> syncWebsocketBatch(List<dynamic> batchData) {
if (_syncWebsocketTask != null) { if (_syncWebsocketTask != null) {
return _syncWebsocketTask!.future; return _syncWebsocketTask!.future;
} }
@@ -196,17 +196,7 @@ class BackgroundSyncManager {
}); });
} }
Future<void> syncWebsocketBatchV2(List<dynamic> batchData) { Future<void> syncWebsocketEdit(dynamic data) {
if (_syncWebsocketTask != null) {
return _syncWebsocketTask!.future;
}
_syncWebsocketTask = _handleWsAssetUploadReadyV2Batch(batchData);
return _syncWebsocketTask!.whenComplete(() {
_syncWebsocketTask = null;
});
}
Future<void> syncWebsocketEditV1(dynamic data) {
if (_syncWebsocketTask != null) { if (_syncWebsocketTask != null) {
return _syncWebsocketTask!.future; return _syncWebsocketTask!.future;
} }
@@ -216,16 +206,6 @@ class BackgroundSyncManager {
}); });
} }
Future<void> syncWebsocketEditV2(dynamic data) {
if (_syncWebsocketTask != null) {
return _syncWebsocketTask!.future;
}
_syncWebsocketTask = _handleWsAssetEditReadyV2(data);
return _syncWebsocketTask!.whenComplete(() {
_syncWebsocketTask = null;
});
}
Future<void> syncLinkedAlbum() { Future<void> syncLinkedAlbum() {
if (_linkedAlbumSyncTask != null) { if (_linkedAlbumSyncTask != null) {
return _linkedAlbumSyncTask!.future; return _linkedAlbumSyncTask!.future;
@@ -262,17 +242,7 @@ Cancelable<void> _handleWsAssetUploadReadyV1Batch(List<dynamic> batchData) => ru
debugLabel: 'websocket-batch', debugLabel: 'websocket-batch',
); );
Cancelable<void> _handleWsAssetUploadReadyV2Batch(List<dynamic> batchData) => runInIsolateGentle(
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetUploadReadyV2Batch(batchData),
debugLabel: 'websocket-batch',
);
Cancelable<void> _handleWsAssetEditReadyV1(dynamic data) => runInIsolateGentle( Cancelable<void> _handleWsAssetEditReadyV1(dynamic data) => runInIsolateGentle(
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV1(data), computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV1(data),
debugLabel: 'websocket-edit', debugLabel: 'websocket-edit',
); );
Cancelable<void> _handleWsAssetEditReadyV2(dynamic data) => runInIsolateGentle(
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV2(data),
debugLabel: 'websocket-edit',
);
+3 -2
View File
@@ -1,5 +1,6 @@
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/domain/models/exif.model.dart';
import 'package:immich_mobile/extensions/string_extensions.dart';
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
import 'package:openapi/api.dart' as api; import 'package:openapi/api.dart' as api;
@@ -13,7 +14,7 @@ extension DTOToAsset on api.AssetResponseDto {
updatedAt: updatedAt, updatedAt: updatedAt,
ownerId: ownerId, ownerId: ownerId,
visibility: visibility.toAssetVisibility(), visibility: visibility.toAssetVisibility(),
durationMs: duration, durationInSeconds: duration?.toDuration()?.inSeconds ?? 0,
height: height?.toInt(), height: height?.toInt(),
width: width?.toInt(), width: width?.toInt(),
isFavorite: isFavorite, isFavorite: isFavorite,
@@ -35,7 +36,7 @@ extension DTOToAsset on api.AssetResponseDto {
updatedAt: updatedAt, updatedAt: updatedAt,
ownerId: ownerId, ownerId: ownerId,
visibility: visibility.toAssetVisibility(), visibility: visibility.toAssetVisibility(),
durationMs: duration, durationInSeconds: duration?.toDuration()?.inSeconds ?? 0,
height: height?.toInt(), height: height?.toInt(),
width: width?.toInt(), width: width?.toInt(),
isFavorite: isFavorite, isFavorite: isFavorite,
+3 -8
View File
@@ -7,16 +7,11 @@ extension StringExtension on String {
} }
extension DurationExtension on String { extension DurationExtension on String {
/// Parses and returns the string of format HH:MM:SS.ffffff as a duration object else null /// Parses and returns the string of format HH:MM:SS as a duration object else null
Duration? toDuration() { Duration? toDuration() {
try { try {
final parts = split(':'); final parts = split(':').map((e) => double.parse(e).toInt()).toList(growable: false);
final hours = double.parse(parts[0]).toInt(); return Duration(hours: parts[0], minutes: parts[1], seconds: parts[2]);
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) { } catch (e) {
return null; return null;
} }
@@ -39,7 +39,7 @@ extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData {
type: type, type: type,
createdAt: createdAt, createdAt: createdAt,
updatedAt: updatedAt, updatedAt: updatedAt,
durationMs: durationMs, durationInSeconds: durationInSeconds,
isFavorite: isFavorite, isFavorite: isFavorite,
height: height, height: height,
width: width, width: width,
@@ -16,7 +16,7 @@ typedef $$LocalAssetEntityTableCreateCompanionBuilder =
i0.Value<DateTime> updatedAt, i0.Value<DateTime> updatedAt,
i0.Value<int?> width, i0.Value<int?> width,
i0.Value<int?> height, i0.Value<int?> height,
i0.Value<int?> durationMs, i0.Value<int?> durationInSeconds,
required String id, required String id,
i0.Value<String?> checksum, i0.Value<String?> checksum,
i0.Value<bool> isFavorite, i0.Value<bool> isFavorite,
@@ -35,7 +35,7 @@ typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
i0.Value<DateTime> updatedAt, i0.Value<DateTime> updatedAt,
i0.Value<int?> width, i0.Value<int?> width,
i0.Value<int?> height, i0.Value<int?> height,
i0.Value<int?> durationMs, i0.Value<int?> durationInSeconds,
i0.Value<String> id, i0.Value<String> id,
i0.Value<String?> checksum, i0.Value<String?> checksum,
i0.Value<bool> isFavorite, i0.Value<bool> isFavorite,
@@ -87,8 +87,8 @@ class $$LocalAssetEntityTableFilterComposer
builder: (column) => i0.ColumnFilters(column), builder: (column) => i0.ColumnFilters(column),
); );
i0.ColumnFilters<int> get durationMs => $composableBuilder( i0.ColumnFilters<int> get durationInSeconds => $composableBuilder(
column: $table.durationMs, column: $table.durationInSeconds,
builder: (column) => i0.ColumnFilters(column), builder: (column) => i0.ColumnFilters(column),
); );
@@ -182,8 +182,8 @@ class $$LocalAssetEntityTableOrderingComposer
builder: (column) => i0.ColumnOrderings(column), builder: (column) => i0.ColumnOrderings(column),
); );
i0.ColumnOrderings<int> get durationMs => $composableBuilder( i0.ColumnOrderings<int> get durationInSeconds => $composableBuilder(
column: $table.durationMs, column: $table.durationInSeconds,
builder: (column) => i0.ColumnOrderings(column), builder: (column) => i0.ColumnOrderings(column),
); );
@@ -260,8 +260,8 @@ class $$LocalAssetEntityTableAnnotationComposer
i0.GeneratedColumn<int> get height => i0.GeneratedColumn<int> get height =>
$composableBuilder(column: $table.height, builder: (column) => column); $composableBuilder(column: $table.height, builder: (column) => column);
i0.GeneratedColumn<int> get durationMs => $composableBuilder( i0.GeneratedColumn<int> get durationInSeconds => $composableBuilder(
column: $table.durationMs, column: $table.durationInSeconds,
builder: (column) => column, builder: (column) => column,
); );
@@ -348,7 +348,7 @@ class $$LocalAssetEntityTableTableManager
i0.Value<DateTime> updatedAt = const i0.Value.absent(), i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<int?> width = const i0.Value.absent(), i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(), i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(), i0.Value<int?> durationInSeconds = const i0.Value.absent(),
i0.Value<String> id = const i0.Value.absent(), i0.Value<String> id = const i0.Value.absent(),
i0.Value<String?> checksum = const i0.Value.absent(), i0.Value<String?> checksum = const i0.Value.absent(),
i0.Value<bool> isFavorite = const i0.Value.absent(), i0.Value<bool> isFavorite = const i0.Value.absent(),
@@ -366,7 +366,7 @@ class $$LocalAssetEntityTableTableManager
updatedAt: updatedAt, updatedAt: updatedAt,
width: width, width: width,
height: height, height: height,
durationMs: durationMs, durationInSeconds: durationInSeconds,
id: id, id: id,
checksum: checksum, checksum: checksum,
isFavorite: isFavorite, isFavorite: isFavorite,
@@ -385,7 +385,7 @@ class $$LocalAssetEntityTableTableManager
i0.Value<DateTime> updatedAt = const i0.Value.absent(), i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<int?> width = const i0.Value.absent(), i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(), i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(), i0.Value<int?> durationInSeconds = const i0.Value.absent(),
required String id, required String id,
i0.Value<String?> checksum = const i0.Value.absent(), i0.Value<String?> checksum = const i0.Value.absent(),
i0.Value<bool> isFavorite = const i0.Value.absent(), i0.Value<bool> isFavorite = const i0.Value.absent(),
@@ -403,7 +403,7 @@ class $$LocalAssetEntityTableTableManager
updatedAt: updatedAt, updatedAt: updatedAt,
width: width, width: width,
height: height, height: height,
durationMs: durationMs, durationInSeconds: durationInSeconds,
id: id, id: id,
checksum: checksum, checksum: checksum,
isFavorite: isFavorite, isFavorite: isFavorite,
@@ -522,17 +522,17 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
type: i0.DriftSqlType.int, type: i0.DriftSqlType.int,
requiredDuringInsert: false, requiredDuringInsert: false,
); );
static const i0.VerificationMeta _durationMsMeta = const i0.VerificationMeta( static const i0.VerificationMeta _durationInSecondsMeta =
'durationMs', const i0.VerificationMeta('durationInSeconds');
);
@override @override
late final i0.GeneratedColumn<int> durationMs = i0.GeneratedColumn<int>( late final i0.GeneratedColumn<int> durationInSeconds =
'duration_ms', i0.GeneratedColumn<int>(
aliasedName, 'duration_in_seconds',
true, aliasedName,
type: i0.DriftSqlType.int, true,
requiredDuringInsert: false, type: i0.DriftSqlType.int,
); requiredDuringInsert: false,
);
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
@override @override
late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>( late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>(
@@ -645,7 +645,7 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
updatedAt, updatedAt,
width, width,
height, height,
durationMs, durationInSeconds,
id, id,
checksum, checksum,
isFavorite, isFavorite,
@@ -700,10 +700,13 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
height.isAcceptableOrUnknown(data['height']!, _heightMeta), height.isAcceptableOrUnknown(data['height']!, _heightMeta),
); );
} }
if (data.containsKey('duration_ms')) { if (data.containsKey('duration_in_seconds')) {
context.handle( context.handle(
_durationMsMeta, _durationInSecondsMeta,
durationMs.isAcceptableOrUnknown(data['duration_ms']!, _durationMsMeta), durationInSeconds.isAcceptableOrUnknown(
data['duration_in_seconds']!,
_durationInSecondsMeta,
),
); );
} }
if (data.containsKey('id')) { if (data.containsKey('id')) {
@@ -797,9 +800,9 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
i0.DriftSqlType.int, i0.DriftSqlType.int,
data['${effectivePrefix}height'], data['${effectivePrefix}height'],
), ),
durationMs: attachedDatabase.typeMapping.read( durationInSeconds: attachedDatabase.typeMapping.read(
i0.DriftSqlType.int, i0.DriftSqlType.int,
data['${effectivePrefix}duration_ms'], data['${effectivePrefix}duration_in_seconds'],
), ),
id: attachedDatabase.typeMapping.read( id: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string, i0.DriftSqlType.string,
@@ -867,7 +870,7 @@ class LocalAssetEntityData extends i0.DataClass
final DateTime updatedAt; final DateTime updatedAt;
final int? width; final int? width;
final int? height; final int? height;
final int? durationMs; final int? durationInSeconds;
final String id; final String id;
final String? checksum; final String? checksum;
final bool isFavorite; final bool isFavorite;
@@ -884,7 +887,7 @@ class LocalAssetEntityData extends i0.DataClass
required this.updatedAt, required this.updatedAt,
this.width, this.width,
this.height, this.height,
this.durationMs, this.durationInSeconds,
required this.id, required this.id,
this.checksum, this.checksum,
required this.isFavorite, required this.isFavorite,
@@ -912,8 +915,8 @@ class LocalAssetEntityData extends i0.DataClass
if (!nullToAbsent || height != null) { if (!nullToAbsent || height != null) {
map['height'] = i0.Variable<int>(height); map['height'] = i0.Variable<int>(height);
} }
if (!nullToAbsent || durationMs != null) { if (!nullToAbsent || durationInSeconds != null) {
map['duration_ms'] = i0.Variable<int>(durationMs); map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds);
} }
map['id'] = i0.Variable<String>(id); map['id'] = i0.Variable<String>(id);
if (!nullToAbsent || checksum != null) { if (!nullToAbsent || checksum != null) {
@@ -955,7 +958,7 @@ class LocalAssetEntityData extends i0.DataClass
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']), updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
width: serializer.fromJson<int?>(json['width']), width: serializer.fromJson<int?>(json['width']),
height: serializer.fromJson<int?>(json['height']), height: serializer.fromJson<int?>(json['height']),
durationMs: serializer.fromJson<int?>(json['durationMs']), durationInSeconds: serializer.fromJson<int?>(json['durationInSeconds']),
id: serializer.fromJson<String>(json['id']), id: serializer.fromJson<String>(json['id']),
checksum: serializer.fromJson<String?>(json['checksum']), checksum: serializer.fromJson<String?>(json['checksum']),
isFavorite: serializer.fromJson<bool>(json['isFavorite']), isFavorite: serializer.fromJson<bool>(json['isFavorite']),
@@ -981,7 +984,7 @@ class LocalAssetEntityData extends i0.DataClass
'updatedAt': serializer.toJson<DateTime>(updatedAt), 'updatedAt': serializer.toJson<DateTime>(updatedAt),
'width': serializer.toJson<int?>(width), 'width': serializer.toJson<int?>(width),
'height': serializer.toJson<int?>(height), 'height': serializer.toJson<int?>(height),
'durationMs': serializer.toJson<int?>(durationMs), 'durationInSeconds': serializer.toJson<int?>(durationInSeconds),
'id': serializer.toJson<String>(id), 'id': serializer.toJson<String>(id),
'checksum': serializer.toJson<String?>(checksum), 'checksum': serializer.toJson<String?>(checksum),
'isFavorite': serializer.toJson<bool>(isFavorite), 'isFavorite': serializer.toJson<bool>(isFavorite),
@@ -1003,7 +1006,7 @@ class LocalAssetEntityData extends i0.DataClass
DateTime? updatedAt, DateTime? updatedAt,
i0.Value<int?> width = const i0.Value.absent(), i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(), i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(), i0.Value<int?> durationInSeconds = const i0.Value.absent(),
String? id, String? id,
i0.Value<String?> checksum = const i0.Value.absent(), i0.Value<String?> checksum = const i0.Value.absent(),
bool? isFavorite, bool? isFavorite,
@@ -1020,7 +1023,9 @@ class LocalAssetEntityData extends i0.DataClass
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
width: width.present ? width.value : this.width, width: width.present ? width.value : this.width,
height: height.present ? height.value : this.height, height: height.present ? height.value : this.height,
durationMs: durationMs.present ? durationMs.value : this.durationMs, durationInSeconds: durationInSeconds.present
? durationInSeconds.value
: this.durationInSeconds,
id: id ?? this.id, id: id ?? this.id,
checksum: checksum.present ? checksum.value : this.checksum, checksum: checksum.present ? checksum.value : this.checksum,
isFavorite: isFavorite ?? this.isFavorite, isFavorite: isFavorite ?? this.isFavorite,
@@ -1041,9 +1046,9 @@ class LocalAssetEntityData extends i0.DataClass
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
width: data.width.present ? data.width.value : this.width, width: data.width.present ? data.width.value : this.width,
height: data.height.present ? data.height.value : this.height, height: data.height.present ? data.height.value : this.height,
durationMs: data.durationMs.present durationInSeconds: data.durationInSeconds.present
? data.durationMs.value ? data.durationInSeconds.value
: this.durationMs, : this.durationInSeconds,
id: data.id.present ? data.id.value : this.id, id: data.id.present ? data.id.value : this.id,
checksum: data.checksum.present ? data.checksum.value : this.checksum, checksum: data.checksum.present ? data.checksum.value : this.checksum,
isFavorite: data.isFavorite.present isFavorite: data.isFavorite.present
@@ -1073,7 +1078,7 @@ class LocalAssetEntityData extends i0.DataClass
..write('updatedAt: $updatedAt, ') ..write('updatedAt: $updatedAt, ')
..write('width: $width, ') ..write('width: $width, ')
..write('height: $height, ') ..write('height: $height, ')
..write('durationMs: $durationMs, ') ..write('durationInSeconds: $durationInSeconds, ')
..write('id: $id, ') ..write('id: $id, ')
..write('checksum: $checksum, ') ..write('checksum: $checksum, ')
..write('isFavorite: $isFavorite, ') ..write('isFavorite: $isFavorite, ')
@@ -1095,7 +1100,7 @@ class LocalAssetEntityData extends i0.DataClass
updatedAt, updatedAt,
width, width,
height, height,
durationMs, durationInSeconds,
id, id,
checksum, checksum,
isFavorite, isFavorite,
@@ -1116,7 +1121,7 @@ class LocalAssetEntityData extends i0.DataClass
other.updatedAt == this.updatedAt && other.updatedAt == this.updatedAt &&
other.width == this.width && other.width == this.width &&
other.height == this.height && other.height == this.height &&
other.durationMs == this.durationMs && other.durationInSeconds == this.durationInSeconds &&
other.id == this.id && other.id == this.id &&
other.checksum == this.checksum && other.checksum == this.checksum &&
other.isFavorite == this.isFavorite && other.isFavorite == this.isFavorite &&
@@ -1136,7 +1141,7 @@ class LocalAssetEntityCompanion
final i0.Value<DateTime> updatedAt; final i0.Value<DateTime> updatedAt;
final i0.Value<int?> width; final i0.Value<int?> width;
final i0.Value<int?> height; final i0.Value<int?> height;
final i0.Value<int?> durationMs; final i0.Value<int?> durationInSeconds;
final i0.Value<String> id; final i0.Value<String> id;
final i0.Value<String?> checksum; final i0.Value<String?> checksum;
final i0.Value<bool> isFavorite; final i0.Value<bool> isFavorite;
@@ -1153,7 +1158,7 @@ class LocalAssetEntityCompanion
this.updatedAt = const i0.Value.absent(), this.updatedAt = const i0.Value.absent(),
this.width = const i0.Value.absent(), this.width = const i0.Value.absent(),
this.height = const i0.Value.absent(), this.height = const i0.Value.absent(),
this.durationMs = const i0.Value.absent(), this.durationInSeconds = const i0.Value.absent(),
this.id = const i0.Value.absent(), this.id = const i0.Value.absent(),
this.checksum = const i0.Value.absent(), this.checksum = const i0.Value.absent(),
this.isFavorite = const i0.Value.absent(), this.isFavorite = const i0.Value.absent(),
@@ -1171,7 +1176,7 @@ class LocalAssetEntityCompanion
this.updatedAt = const i0.Value.absent(), this.updatedAt = const i0.Value.absent(),
this.width = const i0.Value.absent(), this.width = const i0.Value.absent(),
this.height = const i0.Value.absent(), this.height = const i0.Value.absent(),
this.durationMs = const i0.Value.absent(), this.durationInSeconds = const i0.Value.absent(),
required String id, required String id,
this.checksum = const i0.Value.absent(), this.checksum = const i0.Value.absent(),
this.isFavorite = const i0.Value.absent(), this.isFavorite = const i0.Value.absent(),
@@ -1191,7 +1196,7 @@ class LocalAssetEntityCompanion
i0.Expression<DateTime>? updatedAt, i0.Expression<DateTime>? updatedAt,
i0.Expression<int>? width, i0.Expression<int>? width,
i0.Expression<int>? height, i0.Expression<int>? height,
i0.Expression<int>? durationMs, i0.Expression<int>? durationInSeconds,
i0.Expression<String>? id, i0.Expression<String>? id,
i0.Expression<String>? checksum, i0.Expression<String>? checksum,
i0.Expression<bool>? isFavorite, i0.Expression<bool>? isFavorite,
@@ -1209,7 +1214,7 @@ class LocalAssetEntityCompanion
if (updatedAt != null) 'updated_at': updatedAt, if (updatedAt != null) 'updated_at': updatedAt,
if (width != null) 'width': width, if (width != null) 'width': width,
if (height != null) 'height': height, if (height != null) 'height': height,
if (durationMs != null) 'duration_ms': durationMs, if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds,
if (id != null) 'id': id, if (id != null) 'id': id,
if (checksum != null) 'checksum': checksum, if (checksum != null) 'checksum': checksum,
if (isFavorite != null) 'is_favorite': isFavorite, if (isFavorite != null) 'is_favorite': isFavorite,
@@ -1229,7 +1234,7 @@ class LocalAssetEntityCompanion
i0.Value<DateTime>? updatedAt, i0.Value<DateTime>? updatedAt,
i0.Value<int?>? width, i0.Value<int?>? width,
i0.Value<int?>? height, i0.Value<int?>? height,
i0.Value<int?>? durationMs, i0.Value<int?>? durationInSeconds,
i0.Value<String>? id, i0.Value<String>? id,
i0.Value<String?>? checksum, i0.Value<String?>? checksum,
i0.Value<bool>? isFavorite, i0.Value<bool>? isFavorite,
@@ -1247,7 +1252,7 @@ class LocalAssetEntityCompanion
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
width: width ?? this.width, width: width ?? this.width,
height: height ?? this.height, height: height ?? this.height,
durationMs: durationMs ?? this.durationMs, durationInSeconds: durationInSeconds ?? this.durationInSeconds,
id: id ?? this.id, id: id ?? this.id,
checksum: checksum ?? this.checksum, checksum: checksum ?? this.checksum,
isFavorite: isFavorite ?? this.isFavorite, isFavorite: isFavorite ?? this.isFavorite,
@@ -1283,8 +1288,8 @@ class LocalAssetEntityCompanion
if (height.present) { if (height.present) {
map['height'] = i0.Variable<int>(height.value); map['height'] = i0.Variable<int>(height.value);
} }
if (durationMs.present) { if (durationInSeconds.present) {
map['duration_ms'] = i0.Variable<int>(durationMs.value); map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds.value);
} }
if (id.present) { if (id.present) {
map['id'] = i0.Variable<String>(id.value); map['id'] = i0.Variable<String>(id.value);
@@ -1329,7 +1334,7 @@ class LocalAssetEntityCompanion
..write('updatedAt: $updatedAt, ') ..write('updatedAt: $updatedAt, ')
..write('width: $width, ') ..write('width: $width, ')
..write('height: $height, ') ..write('height: $height, ')
..write('durationMs: $durationMs, ') ..write('durationInSeconds: $durationInSeconds, ')
..write('id: $id, ') ..write('id: $id, ')
..write('checksum: $checksum, ') ..write('checksum: $checksum, ')
..write('isFavorite: $isFavorite, ') ..write('isFavorite: $isFavorite, ')
@@ -14,7 +14,7 @@ SELECT
rae.updated_at, rae.updated_at,
rae.width, rae.width,
rae.height, rae.height,
rae.duration_ms, rae.duration_in_seconds,
rae.is_favorite, rae.is_favorite,
rae.thumb_hash, rae.thumb_hash,
rae.checksum, rae.checksum,
@@ -52,7 +52,7 @@ SELECT
lae.updated_at, lae.updated_at,
lae.width, lae.width,
lae.height, lae.height,
lae.duration_ms, lae.duration_in_seconds,
lae.is_favorite, lae.is_favorite,
NULL as thumb_hash, NULL as thumb_hash,
lae.checksum, lae.checksum,
+4 -4
View File
@@ -29,7 +29,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
); );
$arrayStartIndex += generatedlimit.amountOfVariables; $arrayStartIndex += generatedlimit.amountOfVariables;
return customSelect( 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_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}', '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}',
variables: [ variables: [
for (var $ in userIds) i0.Variable<String>($), for (var $ in userIds) i0.Variable<String>($),
...generatedlimit.introducedVariables, ...generatedlimit.introducedVariables,
@@ -54,7 +54,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
updatedAt: row.read<DateTime>('updated_at'), updatedAt: row.read<DateTime>('updated_at'),
width: row.readNullable<int>('width'), width: row.readNullable<int>('width'),
height: row.readNullable<int>('height'), height: row.readNullable<int>('height'),
durationMs: row.readNullable<int>('duration_ms'), durationInSeconds: row.readNullable<int>('duration_in_seconds'),
isFavorite: row.read<bool>('is_favorite'), isFavorite: row.read<bool>('is_favorite'),
thumbHash: row.readNullable<String>('thumb_hash'), thumbHash: row.readNullable<String>('thumb_hash'),
checksum: row.readNullable<String>('checksum'), checksum: row.readNullable<String>('checksum'),
@@ -127,7 +127,7 @@ class MergedAssetResult {
final DateTime updatedAt; final DateTime updatedAt;
final int? width; final int? width;
final int? height; final int? height;
final int? durationMs; final int? durationInSeconds;
final bool isFavorite; final bool isFavorite;
final String? thumbHash; final String? thumbHash;
final String? checksum; final String? checksum;
@@ -150,7 +150,7 @@ class MergedAssetResult {
required this.updatedAt, required this.updatedAt,
this.width, this.width,
this.height, this.height,
this.durationMs, this.durationInSeconds,
required this.isFavorite, required this.isFavorite,
this.thumbHash, this.thumbHash,
this.checksum, this.checksum,
@@ -1,8 +1,10 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/album/album.model.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/remote_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.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 { class RemoteAlbumEntity extends Table with DriftDefaultsMixin {
const RemoteAlbumEntity(); const RemoteAlbumEntity();
@@ -16,6 +18,8 @@ class RemoteAlbumEntity extends Table with DriftDefaultsMixin {
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
TextColumn get ownerId => text().references(UserEntity, #id, onDelete: KeyAction.cascade)();
TextColumn get thumbnailAssetId => TextColumn get thumbnailAssetId =>
text().references(RemoteAssetEntity, #id, onDelete: KeyAction.setNull).nullable()(); text().references(RemoteAssetEntity, #id, onDelete: KeyAction.setNull).nullable()();
@@ -7,9 +7,11 @@ import 'package:immich_mobile/domain/models/album/album.model.dart' as i2;
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart' import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart'
as i3; as i3;
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4; import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
as i5; as i5;
import 'package:drift/internal/modular.dart' as i6; import 'package:drift/internal/modular.dart' as i6;
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
as i7;
typedef $$RemoteAlbumEntityTableCreateCompanionBuilder = typedef $$RemoteAlbumEntityTableCreateCompanionBuilder =
i1.RemoteAlbumEntityCompanion Function({ i1.RemoteAlbumEntityCompanion Function({
@@ -18,6 +20,7 @@ typedef $$RemoteAlbumEntityTableCreateCompanionBuilder =
i0.Value<String> description, i0.Value<String> description,
i0.Value<DateTime> createdAt, i0.Value<DateTime> createdAt,
i0.Value<DateTime> updatedAt, i0.Value<DateTime> updatedAt,
required String ownerId,
i0.Value<String?> thumbnailAssetId, i0.Value<String?> thumbnailAssetId,
i0.Value<bool> isActivityEnabled, i0.Value<bool> isActivityEnabled,
required i2.AlbumAssetOrder order, required i2.AlbumAssetOrder order,
@@ -29,6 +32,7 @@ typedef $$RemoteAlbumEntityTableUpdateCompanionBuilder =
i0.Value<String> description, i0.Value<String> description,
i0.Value<DateTime> createdAt, i0.Value<DateTime> createdAt,
i0.Value<DateTime> updatedAt, i0.Value<DateTime> updatedAt,
i0.Value<String> ownerId,
i0.Value<String?> thumbnailAssetId, i0.Value<String?> thumbnailAssetId,
i0.Value<bool> isActivityEnabled, i0.Value<bool> isActivityEnabled,
i0.Value<i2.AlbumAssetOrder> order, i0.Value<i2.AlbumAssetOrder> order,
@@ -47,10 +51,42 @@ final class $$RemoteAlbumEntityTableReferences
super.$_typedResult, super.$_typedResult,
); );
static i5.$RemoteAssetEntityTable _thumbnailAssetIdTable( 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(
i0.GeneratedDatabase db, i0.GeneratedDatabase db,
) => i6.ReadDatabaseContainer(db) ) => i6.ReadDatabaseContainer(db)
.resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity') .resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity')
.createAlias( .createAlias(
i0.$_aliasNameGenerator( i0.$_aliasNameGenerator(
i6.ReadDatabaseContainer(db) i6.ReadDatabaseContainer(db)
@@ -58,19 +94,19 @@ final class $$RemoteAlbumEntityTableReferences
.thumbnailAssetId, .thumbnailAssetId,
i6.ReadDatabaseContainer( i6.ReadDatabaseContainer(
db, db,
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity').id, ).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity').id,
), ),
); );
i5.$$RemoteAssetEntityTableProcessedTableManager? get thumbnailAssetId { i7.$$RemoteAssetEntityTableProcessedTableManager? get thumbnailAssetId {
final $_column = $_itemColumn<String>('thumbnail_asset_id'); final $_column = $_itemColumn<String>('thumbnail_asset_id');
if ($_column == null) return null; if ($_column == null) return null;
final manager = i5 final manager = i7
.$$RemoteAssetEntityTableTableManager( .$$RemoteAssetEntityTableTableManager(
$_db, $_db,
i6.ReadDatabaseContainer( i6.ReadDatabaseContainer(
$_db, $_db,
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'), ).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
) )
.filter((f) => f.id.sqlEquals($_column)); .filter((f) => f.id.sqlEquals($_column));
final item = $_typedResult.readTableOrNull(_thumbnailAssetIdTable($_db)); final item = $_typedResult.readTableOrNull(_thumbnailAssetIdTable($_db));
@@ -126,24 +162,51 @@ class $$RemoteAlbumEntityTableFilterComposer
builder: (column) => i0.ColumnWithTypeConverterFilters(column), builder: (column) => i0.ColumnWithTypeConverterFilters(column),
); );
i5.$$RemoteAssetEntityTableFilterComposer get thumbnailAssetId { i5.$$UserEntityTableFilterComposer get ownerId {
final i5.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder( final i5.$$UserEntityTableFilterComposer composer = $composerBuilder(
composer: this, composer: this,
getCurrentColumn: (t) => t.thumbnailAssetId, getCurrentColumn: (t) => t.ownerId,
referencedTable: i6.ReadDatabaseContainer( referencedTable: i6.ReadDatabaseContainer(
$db, $db,
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'), ).resultSet<i5.$UserEntityTable>('user_entity'),
getReferencedColumn: (t) => t.id, getReferencedColumn: (t) => t.id,
builder: builder:
( (
joinBuilder, { joinBuilder, {
$addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer, $removeJoinBuilderFromRootComposer,
}) => i5.$$RemoteAssetEntityTableFilterComposer( }) => i5.$$UserEntityTableFilterComposer(
$db: $db, $db: $db,
$table: i6.ReadDatabaseContainer( $table: i6.ReadDatabaseContainer(
$db, $db,
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'), ).resultSet<i5.$UserEntityTable>('user_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
),
);
return composer;
}
i7.$$RemoteAssetEntityTableFilterComposer get thumbnailAssetId {
final i7.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.thumbnailAssetId,
referencedTable: i6.ReadDatabaseContainer(
$db,
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i7.$$RemoteAssetEntityTableFilterComposer(
$db: $db,
$table: i6.ReadDatabaseContainer(
$db,
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder, joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer:
@@ -198,25 +261,52 @@ class $$RemoteAlbumEntityTableOrderingComposer
builder: (column) => i0.ColumnOrderings(column), builder: (column) => i0.ColumnOrderings(column),
); );
i5.$$RemoteAssetEntityTableOrderingComposer get thumbnailAssetId { i5.$$UserEntityTableOrderingComposer get ownerId {
final i5.$$RemoteAssetEntityTableOrderingComposer composer = 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 =
$composerBuilder( $composerBuilder(
composer: this, composer: this,
getCurrentColumn: (t) => t.thumbnailAssetId, getCurrentColumn: (t) => t.thumbnailAssetId,
referencedTable: i6.ReadDatabaseContainer( referencedTable: i6.ReadDatabaseContainer(
$db, $db,
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'), ).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id, getReferencedColumn: (t) => t.id,
builder: builder:
( (
joinBuilder, { joinBuilder, {
$addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer, $removeJoinBuilderFromRootComposer,
}) => i5.$$RemoteAssetEntityTableOrderingComposer( }) => i7.$$RemoteAssetEntityTableOrderingComposer(
$db: $db, $db: $db,
$table: i6.ReadDatabaseContainer( $table: i6.ReadDatabaseContainer(
$db, $db,
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'), ).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder, joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer:
@@ -261,25 +351,52 @@ class $$RemoteAlbumEntityTableAnnotationComposer
i0.GeneratedColumnWithTypeConverter<i2.AlbumAssetOrder, int> get order => i0.GeneratedColumnWithTypeConverter<i2.AlbumAssetOrder, int> get order =>
$composableBuilder(column: $table.order, builder: (column) => column); $composableBuilder(column: $table.order, builder: (column) => column);
i5.$$RemoteAssetEntityTableAnnotationComposer get thumbnailAssetId { i5.$$UserEntityTableAnnotationComposer get ownerId {
final i5.$$RemoteAssetEntityTableAnnotationComposer composer = 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 =
$composerBuilder( $composerBuilder(
composer: this, composer: this,
getCurrentColumn: (t) => t.thumbnailAssetId, getCurrentColumn: (t) => t.thumbnailAssetId,
referencedTable: i6.ReadDatabaseContainer( referencedTable: i6.ReadDatabaseContainer(
$db, $db,
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'), ).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id, getReferencedColumn: (t) => t.id,
builder: builder:
( (
joinBuilder, { joinBuilder, {
$addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer, $removeJoinBuilderFromRootComposer,
}) => i5.$$RemoteAssetEntityTableAnnotationComposer( }) => i7.$$RemoteAssetEntityTableAnnotationComposer(
$db: $db, $db: $db,
$table: i6.ReadDatabaseContainer( $table: i6.ReadDatabaseContainer(
$db, $db,
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'), ).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder, joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer:
@@ -303,7 +420,7 @@ class $$RemoteAlbumEntityTableTableManager
$$RemoteAlbumEntityTableUpdateCompanionBuilder, $$RemoteAlbumEntityTableUpdateCompanionBuilder,
(i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences), (i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences),
i1.RemoteAlbumEntityData, i1.RemoteAlbumEntityData,
i0.PrefetchHooks Function({bool thumbnailAssetId}) i0.PrefetchHooks Function({bool ownerId, bool thumbnailAssetId})
> { > {
$$RemoteAlbumEntityTableTableManager( $$RemoteAlbumEntityTableTableManager(
i0.GeneratedDatabase db, i0.GeneratedDatabase db,
@@ -328,6 +445,7 @@ class $$RemoteAlbumEntityTableTableManager
i0.Value<String> description = const i0.Value.absent(), i0.Value<String> description = const i0.Value.absent(),
i0.Value<DateTime> createdAt = const i0.Value.absent(), i0.Value<DateTime> createdAt = const i0.Value.absent(),
i0.Value<DateTime> updatedAt = 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<String?> thumbnailAssetId = const i0.Value.absent(),
i0.Value<bool> isActivityEnabled = const i0.Value.absent(), i0.Value<bool> isActivityEnabled = const i0.Value.absent(),
i0.Value<i2.AlbumAssetOrder> order = const i0.Value.absent(), i0.Value<i2.AlbumAssetOrder> order = const i0.Value.absent(),
@@ -337,6 +455,7 @@ class $$RemoteAlbumEntityTableTableManager
description: description, description: description,
createdAt: createdAt, createdAt: createdAt,
updatedAt: updatedAt, updatedAt: updatedAt,
ownerId: ownerId,
thumbnailAssetId: thumbnailAssetId, thumbnailAssetId: thumbnailAssetId,
isActivityEnabled: isActivityEnabled, isActivityEnabled: isActivityEnabled,
order: order, order: order,
@@ -348,6 +467,7 @@ class $$RemoteAlbumEntityTableTableManager
i0.Value<String> description = const i0.Value.absent(), i0.Value<String> description = const i0.Value.absent(),
i0.Value<DateTime> createdAt = const i0.Value.absent(), i0.Value<DateTime> createdAt = const i0.Value.absent(),
i0.Value<DateTime> updatedAt = 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<String?> thumbnailAssetId = const i0.Value.absent(),
i0.Value<bool> isActivityEnabled = const i0.Value.absent(), i0.Value<bool> isActivityEnabled = const i0.Value.absent(),
required i2.AlbumAssetOrder order, required i2.AlbumAssetOrder order,
@@ -357,6 +477,7 @@ class $$RemoteAlbumEntityTableTableManager
description: description, description: description,
createdAt: createdAt, createdAt: createdAt,
updatedAt: updatedAt, updatedAt: updatedAt,
ownerId: ownerId,
thumbnailAssetId: thumbnailAssetId, thumbnailAssetId: thumbnailAssetId,
isActivityEnabled: isActivityEnabled, isActivityEnabled: isActivityEnabled,
order: order, order: order,
@@ -369,7 +490,7 @@ class $$RemoteAlbumEntityTableTableManager
), ),
) )
.toList(), .toList(),
prefetchHooksCallback: ({thumbnailAssetId = false}) { prefetchHooksCallback: ({ownerId = false, thumbnailAssetId = false}) {
return i0.PrefetchHooks( return i0.PrefetchHooks(
db: db, db: db,
explicitlyWatchedTables: [], explicitlyWatchedTables: [],
@@ -389,6 +510,21 @@ class $$RemoteAlbumEntityTableTableManager
dynamic dynamic
> >
>(state) { >(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) { if (thumbnailAssetId) {
state = state =
state.withJoin( state.withJoin(
@@ -428,8 +564,12 @@ typedef $$RemoteAlbumEntityTableProcessedTableManager =
$$RemoteAlbumEntityTableUpdateCompanionBuilder, $$RemoteAlbumEntityTableUpdateCompanionBuilder,
(i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences), (i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences),
i1.RemoteAlbumEntityData, i1.RemoteAlbumEntityData,
i0.PrefetchHooks Function({bool thumbnailAssetId}) i0.PrefetchHooks Function({bool ownerId, 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 class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
with i0.TableInfo<$RemoteAlbumEntityTable, i1.RemoteAlbumEntityData> { with i0.TableInfo<$RemoteAlbumEntityTable, i1.RemoteAlbumEntityData> {
@@ -496,6 +636,20 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
requiredDuringInsert: false, requiredDuringInsert: false,
defaultValue: i4.currentDateAndTime, 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 = static const i0.VerificationMeta _thumbnailAssetIdMeta =
const i0.VerificationMeta('thumbnailAssetId'); const i0.VerificationMeta('thumbnailAssetId');
@override @override
@@ -544,6 +698,7 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
description, description,
createdAt, createdAt,
updatedAt, updatedAt,
ownerId,
thumbnailAssetId, thumbnailAssetId,
isActivityEnabled, isActivityEnabled,
order, order,
@@ -594,6 +749,14 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), 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')) { if (data.containsKey('thumbnail_asset_id')) {
context.handle( context.handle(
_thumbnailAssetIdMeta, _thumbnailAssetIdMeta,
@@ -644,6 +807,10 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
i0.DriftSqlType.dateTime, i0.DriftSqlType.dateTime,
data['${effectivePrefix}updated_at'], data['${effectivePrefix}updated_at'],
)!, )!,
ownerId: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
data['${effectivePrefix}owner_id'],
)!,
thumbnailAssetId: attachedDatabase.typeMapping.read( thumbnailAssetId: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string, i0.DriftSqlType.string,
data['${effectivePrefix}thumbnail_asset_id'], data['${effectivePrefix}thumbnail_asset_id'],
@@ -683,6 +850,7 @@ class RemoteAlbumEntityData extends i0.DataClass
final String description; final String description;
final DateTime createdAt; final DateTime createdAt;
final DateTime updatedAt; final DateTime updatedAt;
final String ownerId;
final String? thumbnailAssetId; final String? thumbnailAssetId;
final bool isActivityEnabled; final bool isActivityEnabled;
final i2.AlbumAssetOrder order; final i2.AlbumAssetOrder order;
@@ -692,6 +860,7 @@ class RemoteAlbumEntityData extends i0.DataClass
required this.description, required this.description,
required this.createdAt, required this.createdAt,
required this.updatedAt, required this.updatedAt,
required this.ownerId,
this.thumbnailAssetId, this.thumbnailAssetId,
required this.isActivityEnabled, required this.isActivityEnabled,
required this.order, required this.order,
@@ -704,6 +873,7 @@ class RemoteAlbumEntityData extends i0.DataClass
map['description'] = i0.Variable<String>(description); map['description'] = i0.Variable<String>(description);
map['created_at'] = i0.Variable<DateTime>(createdAt); map['created_at'] = i0.Variable<DateTime>(createdAt);
map['updated_at'] = i0.Variable<DateTime>(updatedAt); map['updated_at'] = i0.Variable<DateTime>(updatedAt);
map['owner_id'] = i0.Variable<String>(ownerId);
if (!nullToAbsent || thumbnailAssetId != null) { if (!nullToAbsent || thumbnailAssetId != null) {
map['thumbnail_asset_id'] = i0.Variable<String>(thumbnailAssetId); map['thumbnail_asset_id'] = i0.Variable<String>(thumbnailAssetId);
} }
@@ -727,6 +897,7 @@ class RemoteAlbumEntityData extends i0.DataClass
description: serializer.fromJson<String>(json['description']), description: serializer.fromJson<String>(json['description']),
createdAt: serializer.fromJson<DateTime>(json['createdAt']), createdAt: serializer.fromJson<DateTime>(json['createdAt']),
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']), updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
ownerId: serializer.fromJson<String>(json['ownerId']),
thumbnailAssetId: serializer.fromJson<String?>(json['thumbnailAssetId']), thumbnailAssetId: serializer.fromJson<String?>(json['thumbnailAssetId']),
isActivityEnabled: serializer.fromJson<bool>(json['isActivityEnabled']), isActivityEnabled: serializer.fromJson<bool>(json['isActivityEnabled']),
order: i1.$RemoteAlbumEntityTable.$converterorder.fromJson( order: i1.$RemoteAlbumEntityTable.$converterorder.fromJson(
@@ -743,6 +914,7 @@ class RemoteAlbumEntityData extends i0.DataClass
'description': serializer.toJson<String>(description), 'description': serializer.toJson<String>(description),
'createdAt': serializer.toJson<DateTime>(createdAt), 'createdAt': serializer.toJson<DateTime>(createdAt),
'updatedAt': serializer.toJson<DateTime>(updatedAt), 'updatedAt': serializer.toJson<DateTime>(updatedAt),
'ownerId': serializer.toJson<String>(ownerId),
'thumbnailAssetId': serializer.toJson<String?>(thumbnailAssetId), 'thumbnailAssetId': serializer.toJson<String?>(thumbnailAssetId),
'isActivityEnabled': serializer.toJson<bool>(isActivityEnabled), 'isActivityEnabled': serializer.toJson<bool>(isActivityEnabled),
'order': serializer.toJson<int>( 'order': serializer.toJson<int>(
@@ -757,6 +929,7 @@ class RemoteAlbumEntityData extends i0.DataClass
String? description, String? description,
DateTime? createdAt, DateTime? createdAt,
DateTime? updatedAt, DateTime? updatedAt,
String? ownerId,
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(), i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
bool? isActivityEnabled, bool? isActivityEnabled,
i2.AlbumAssetOrder? order, i2.AlbumAssetOrder? order,
@@ -766,6 +939,7 @@ class RemoteAlbumEntityData extends i0.DataClass
description: description ?? this.description, description: description ?? this.description,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
ownerId: ownerId ?? this.ownerId,
thumbnailAssetId: thumbnailAssetId.present thumbnailAssetId: thumbnailAssetId.present
? thumbnailAssetId.value ? thumbnailAssetId.value
: this.thumbnailAssetId, : this.thumbnailAssetId,
@@ -781,6 +955,7 @@ class RemoteAlbumEntityData extends i0.DataClass
: this.description, : this.description,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId,
thumbnailAssetId: data.thumbnailAssetId.present thumbnailAssetId: data.thumbnailAssetId.present
? data.thumbnailAssetId.value ? data.thumbnailAssetId.value
: this.thumbnailAssetId, : this.thumbnailAssetId,
@@ -799,6 +974,7 @@ class RemoteAlbumEntityData extends i0.DataClass
..write('description: $description, ') ..write('description: $description, ')
..write('createdAt: $createdAt, ') ..write('createdAt: $createdAt, ')
..write('updatedAt: $updatedAt, ') ..write('updatedAt: $updatedAt, ')
..write('ownerId: $ownerId, ')
..write('thumbnailAssetId: $thumbnailAssetId, ') ..write('thumbnailAssetId: $thumbnailAssetId, ')
..write('isActivityEnabled: $isActivityEnabled, ') ..write('isActivityEnabled: $isActivityEnabled, ')
..write('order: $order') ..write('order: $order')
@@ -813,6 +989,7 @@ class RemoteAlbumEntityData extends i0.DataClass
description, description,
createdAt, createdAt,
updatedAt, updatedAt,
ownerId,
thumbnailAssetId, thumbnailAssetId,
isActivityEnabled, isActivityEnabled,
order, order,
@@ -826,6 +1003,7 @@ class RemoteAlbumEntityData extends i0.DataClass
other.description == this.description && other.description == this.description &&
other.createdAt == this.createdAt && other.createdAt == this.createdAt &&
other.updatedAt == this.updatedAt && other.updatedAt == this.updatedAt &&
other.ownerId == this.ownerId &&
other.thumbnailAssetId == this.thumbnailAssetId && other.thumbnailAssetId == this.thumbnailAssetId &&
other.isActivityEnabled == this.isActivityEnabled && other.isActivityEnabled == this.isActivityEnabled &&
other.order == this.order); other.order == this.order);
@@ -838,6 +1016,7 @@ class RemoteAlbumEntityCompanion
final i0.Value<String> description; final i0.Value<String> description;
final i0.Value<DateTime> createdAt; final i0.Value<DateTime> createdAt;
final i0.Value<DateTime> updatedAt; final i0.Value<DateTime> updatedAt;
final i0.Value<String> ownerId;
final i0.Value<String?> thumbnailAssetId; final i0.Value<String?> thumbnailAssetId;
final i0.Value<bool> isActivityEnabled; final i0.Value<bool> isActivityEnabled;
final i0.Value<i2.AlbumAssetOrder> order; final i0.Value<i2.AlbumAssetOrder> order;
@@ -847,6 +1026,7 @@ class RemoteAlbumEntityCompanion
this.description = const i0.Value.absent(), this.description = const i0.Value.absent(),
this.createdAt = const i0.Value.absent(), this.createdAt = const i0.Value.absent(),
this.updatedAt = const i0.Value.absent(), this.updatedAt = const i0.Value.absent(),
this.ownerId = const i0.Value.absent(),
this.thumbnailAssetId = const i0.Value.absent(), this.thumbnailAssetId = const i0.Value.absent(),
this.isActivityEnabled = const i0.Value.absent(), this.isActivityEnabled = const i0.Value.absent(),
this.order = const i0.Value.absent(), this.order = const i0.Value.absent(),
@@ -857,11 +1037,13 @@ class RemoteAlbumEntityCompanion
this.description = const i0.Value.absent(), this.description = const i0.Value.absent(),
this.createdAt = const i0.Value.absent(), this.createdAt = const i0.Value.absent(),
this.updatedAt = const i0.Value.absent(), this.updatedAt = const i0.Value.absent(),
required String ownerId,
this.thumbnailAssetId = const i0.Value.absent(), this.thumbnailAssetId = const i0.Value.absent(),
this.isActivityEnabled = const i0.Value.absent(), this.isActivityEnabled = const i0.Value.absent(),
required i2.AlbumAssetOrder order, required i2.AlbumAssetOrder order,
}) : id = i0.Value(id), }) : id = i0.Value(id),
name = i0.Value(name), name = i0.Value(name),
ownerId = i0.Value(ownerId),
order = i0.Value(order); order = i0.Value(order);
static i0.Insertable<i1.RemoteAlbumEntityData> custom({ static i0.Insertable<i1.RemoteAlbumEntityData> custom({
i0.Expression<String>? id, i0.Expression<String>? id,
@@ -869,6 +1051,7 @@ class RemoteAlbumEntityCompanion
i0.Expression<String>? description, i0.Expression<String>? description,
i0.Expression<DateTime>? createdAt, i0.Expression<DateTime>? createdAt,
i0.Expression<DateTime>? updatedAt, i0.Expression<DateTime>? updatedAt,
i0.Expression<String>? ownerId,
i0.Expression<String>? thumbnailAssetId, i0.Expression<String>? thumbnailAssetId,
i0.Expression<bool>? isActivityEnabled, i0.Expression<bool>? isActivityEnabled,
i0.Expression<int>? order, i0.Expression<int>? order,
@@ -879,6 +1062,7 @@ class RemoteAlbumEntityCompanion
if (description != null) 'description': description, if (description != null) 'description': description,
if (createdAt != null) 'created_at': createdAt, if (createdAt != null) 'created_at': createdAt,
if (updatedAt != null) 'updated_at': updatedAt, if (updatedAt != null) 'updated_at': updatedAt,
if (ownerId != null) 'owner_id': ownerId,
if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId,
if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled,
if (order != null) 'order': order, if (order != null) 'order': order,
@@ -891,6 +1075,7 @@ class RemoteAlbumEntityCompanion
i0.Value<String>? description, i0.Value<String>? description,
i0.Value<DateTime>? createdAt, i0.Value<DateTime>? createdAt,
i0.Value<DateTime>? updatedAt, i0.Value<DateTime>? updatedAt,
i0.Value<String>? ownerId,
i0.Value<String?>? thumbnailAssetId, i0.Value<String?>? thumbnailAssetId,
i0.Value<bool>? isActivityEnabled, i0.Value<bool>? isActivityEnabled,
i0.Value<i2.AlbumAssetOrder>? order, i0.Value<i2.AlbumAssetOrder>? order,
@@ -901,6 +1086,7 @@ class RemoteAlbumEntityCompanion
description: description ?? this.description, description: description ?? this.description,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
ownerId: ownerId ?? this.ownerId,
thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId,
isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled,
order: order ?? this.order, order: order ?? this.order,
@@ -925,6 +1111,9 @@ class RemoteAlbumEntityCompanion
if (updatedAt.present) { if (updatedAt.present) {
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value); map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
} }
if (ownerId.present) {
map['owner_id'] = i0.Variable<String>(ownerId.value);
}
if (thumbnailAssetId.present) { if (thumbnailAssetId.present) {
map['thumbnail_asset_id'] = i0.Variable<String>(thumbnailAssetId.value); map['thumbnail_asset_id'] = i0.Variable<String>(thumbnailAssetId.value);
} }
@@ -947,6 +1136,7 @@ class RemoteAlbumEntityCompanion
..write('description: $description, ') ..write('description: $description, ')
..write('createdAt: $createdAt, ') ..write('createdAt: $createdAt, ')
..write('updatedAt: $updatedAt, ') ..write('updatedAt: $updatedAt, ')
..write('ownerId: $ownerId, ')
..write('thumbnailAssetId: $thumbnailAssetId, ') ..write('thumbnailAssetId: $thumbnailAssetId, ')
..write('isActivityEnabled: $isActivityEnabled, ') ..write('isActivityEnabled: $isActivityEnabled, ')
..write('order: $order') ..write('order: $order')
@@ -66,7 +66,7 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
type: type, type: type,
createdAt: createdAt, createdAt: createdAt,
updatedAt: updatedAt, updatedAt: updatedAt,
durationMs: durationMs, durationInSeconds: durationInSeconds,
isFavorite: isFavorite, isFavorite: isFavorite,
height: height, height: height,
width: width, width: width,
@@ -19,7 +19,7 @@ typedef $$RemoteAssetEntityTableCreateCompanionBuilder =
i0.Value<DateTime> updatedAt, i0.Value<DateTime> updatedAt,
i0.Value<int?> width, i0.Value<int?> width,
i0.Value<int?> height, i0.Value<int?> height,
i0.Value<int?> durationMs, i0.Value<int?> durationInSeconds,
required String id, required String id,
required String checksum, required String checksum,
i0.Value<bool> isFavorite, i0.Value<bool> isFavorite,
@@ -41,7 +41,7 @@ typedef $$RemoteAssetEntityTableUpdateCompanionBuilder =
i0.Value<DateTime> updatedAt, i0.Value<DateTime> updatedAt,
i0.Value<int?> width, i0.Value<int?> width,
i0.Value<int?> height, i0.Value<int?> height,
i0.Value<int?> durationMs, i0.Value<int?> durationInSeconds,
i0.Value<String> id, i0.Value<String> id,
i0.Value<String> checksum, i0.Value<String> checksum,
i0.Value<bool> isFavorite, i0.Value<bool> isFavorite,
@@ -142,8 +142,8 @@ class $$RemoteAssetEntityTableFilterComposer
builder: (column) => i0.ColumnFilters(column), builder: (column) => i0.ColumnFilters(column),
); );
i0.ColumnFilters<int> get durationMs => $composableBuilder( i0.ColumnFilters<int> get durationInSeconds => $composableBuilder(
column: $table.durationMs, column: $table.durationInSeconds,
builder: (column) => i0.ColumnFilters(column), builder: (column) => i0.ColumnFilters(column),
); );
@@ -270,8 +270,8 @@ class $$RemoteAssetEntityTableOrderingComposer
builder: (column) => i0.ColumnOrderings(column), builder: (column) => i0.ColumnOrderings(column),
); );
i0.ColumnOrderings<int> get durationMs => $composableBuilder( i0.ColumnOrderings<int> get durationInSeconds => $composableBuilder(
column: $table.durationMs, column: $table.durationInSeconds,
builder: (column) => i0.ColumnOrderings(column), builder: (column) => i0.ColumnOrderings(column),
); );
@@ -385,8 +385,8 @@ class $$RemoteAssetEntityTableAnnotationComposer
i0.GeneratedColumn<int> get height => i0.GeneratedColumn<int> get height =>
$composableBuilder(column: $table.height, builder: (column) => column); $composableBuilder(column: $table.height, builder: (column) => column);
i0.GeneratedColumn<int> get durationMs => $composableBuilder( i0.GeneratedColumn<int> get durationInSeconds => $composableBuilder(
column: $table.durationMs, column: $table.durationInSeconds,
builder: (column) => column, builder: (column) => column,
); );
@@ -499,7 +499,7 @@ class $$RemoteAssetEntityTableTableManager
i0.Value<DateTime> updatedAt = const i0.Value.absent(), i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<int?> width = const i0.Value.absent(), i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(), i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(), i0.Value<int?> durationInSeconds = const i0.Value.absent(),
i0.Value<String> id = const i0.Value.absent(), i0.Value<String> id = const i0.Value.absent(),
i0.Value<String> checksum = const i0.Value.absent(), i0.Value<String> checksum = const i0.Value.absent(),
i0.Value<bool> isFavorite = const i0.Value.absent(), i0.Value<bool> isFavorite = const i0.Value.absent(),
@@ -520,7 +520,7 @@ class $$RemoteAssetEntityTableTableManager
updatedAt: updatedAt, updatedAt: updatedAt,
width: width, width: width,
height: height, height: height,
durationMs: durationMs, durationInSeconds: durationInSeconds,
id: id, id: id,
checksum: checksum, checksum: checksum,
isFavorite: isFavorite, isFavorite: isFavorite,
@@ -542,7 +542,7 @@ class $$RemoteAssetEntityTableTableManager
i0.Value<DateTime> updatedAt = const i0.Value.absent(), i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<int?> width = const i0.Value.absent(), i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(), i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(), i0.Value<int?> durationInSeconds = const i0.Value.absent(),
required String id, required String id,
required String checksum, required String checksum,
i0.Value<bool> isFavorite = const i0.Value.absent(), i0.Value<bool> isFavorite = const i0.Value.absent(),
@@ -562,7 +562,7 @@ class $$RemoteAssetEntityTableTableManager
updatedAt: updatedAt, updatedAt: updatedAt,
width: width, width: width,
height: height, height: height,
durationMs: durationMs, durationInSeconds: durationInSeconds,
id: id, id: id,
checksum: checksum, checksum: checksum,
isFavorite: isFavorite, isFavorite: isFavorite,
@@ -724,17 +724,17 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
type: i0.DriftSqlType.int, type: i0.DriftSqlType.int,
requiredDuringInsert: false, requiredDuringInsert: false,
); );
static const i0.VerificationMeta _durationMsMeta = const i0.VerificationMeta( static const i0.VerificationMeta _durationInSecondsMeta =
'durationMs', const i0.VerificationMeta('durationInSeconds');
);
@override @override
late final i0.GeneratedColumn<int> durationMs = i0.GeneratedColumn<int>( late final i0.GeneratedColumn<int> durationInSeconds =
'duration_ms', i0.GeneratedColumn<int>(
aliasedName, 'duration_in_seconds',
true, aliasedName,
type: i0.DriftSqlType.int, true,
requiredDuringInsert: false, type: i0.DriftSqlType.int,
); requiredDuringInsert: false,
);
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
@override @override
late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>( late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>(
@@ -886,7 +886,7 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
updatedAt, updatedAt,
width, width,
height, height,
durationMs, durationInSeconds,
id, id,
checksum, checksum,
isFavorite, isFavorite,
@@ -944,10 +944,13 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
height.isAcceptableOrUnknown(data['height']!, _heightMeta), height.isAcceptableOrUnknown(data['height']!, _heightMeta),
); );
} }
if (data.containsKey('duration_ms')) { if (data.containsKey('duration_in_seconds')) {
context.handle( context.handle(
_durationMsMeta, _durationInSecondsMeta,
durationMs.isAcceptableOrUnknown(data['duration_ms']!, _durationMsMeta), durationInSeconds.isAcceptableOrUnknown(
data['duration_in_seconds']!,
_durationInSecondsMeta,
),
); );
} }
if (data.containsKey('id')) { if (data.containsKey('id')) {
@@ -1063,9 +1066,9 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
i0.DriftSqlType.int, i0.DriftSqlType.int,
data['${effectivePrefix}height'], data['${effectivePrefix}height'],
), ),
durationMs: attachedDatabase.typeMapping.read( durationInSeconds: attachedDatabase.typeMapping.read(
i0.DriftSqlType.int, i0.DriftSqlType.int,
data['${effectivePrefix}duration_ms'], data['${effectivePrefix}duration_in_seconds'],
), ),
id: attachedDatabase.typeMapping.read( id: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string, i0.DriftSqlType.string,
@@ -1145,7 +1148,7 @@ class RemoteAssetEntityData extends i0.DataClass
final DateTime updatedAt; final DateTime updatedAt;
final int? width; final int? width;
final int? height; final int? height;
final int? durationMs; final int? durationInSeconds;
final String id; final String id;
final String checksum; final String checksum;
final bool isFavorite; final bool isFavorite;
@@ -1165,7 +1168,7 @@ class RemoteAssetEntityData extends i0.DataClass
required this.updatedAt, required this.updatedAt,
this.width, this.width,
this.height, this.height,
this.durationMs, this.durationInSeconds,
required this.id, required this.id,
required this.checksum, required this.checksum,
required this.isFavorite, required this.isFavorite,
@@ -1196,8 +1199,8 @@ class RemoteAssetEntityData extends i0.DataClass
if (!nullToAbsent || height != null) { if (!nullToAbsent || height != null) {
map['height'] = i0.Variable<int>(height); map['height'] = i0.Variable<int>(height);
} }
if (!nullToAbsent || durationMs != null) { if (!nullToAbsent || durationInSeconds != null) {
map['duration_ms'] = i0.Variable<int>(durationMs); map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds);
} }
map['id'] = i0.Variable<String>(id); map['id'] = i0.Variable<String>(id);
map['checksum'] = i0.Variable<String>(checksum); map['checksum'] = i0.Variable<String>(checksum);
@@ -1244,7 +1247,7 @@ class RemoteAssetEntityData extends i0.DataClass
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']), updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
width: serializer.fromJson<int?>(json['width']), width: serializer.fromJson<int?>(json['width']),
height: serializer.fromJson<int?>(json['height']), height: serializer.fromJson<int?>(json['height']),
durationMs: serializer.fromJson<int?>(json['durationMs']), durationInSeconds: serializer.fromJson<int?>(json['durationInSeconds']),
id: serializer.fromJson<String>(json['id']), id: serializer.fromJson<String>(json['id']),
checksum: serializer.fromJson<String>(json['checksum']), checksum: serializer.fromJson<String>(json['checksum']),
isFavorite: serializer.fromJson<bool>(json['isFavorite']), isFavorite: serializer.fromJson<bool>(json['isFavorite']),
@@ -1273,7 +1276,7 @@ class RemoteAssetEntityData extends i0.DataClass
'updatedAt': serializer.toJson<DateTime>(updatedAt), 'updatedAt': serializer.toJson<DateTime>(updatedAt),
'width': serializer.toJson<int?>(width), 'width': serializer.toJson<int?>(width),
'height': serializer.toJson<int?>(height), 'height': serializer.toJson<int?>(height),
'durationMs': serializer.toJson<int?>(durationMs), 'durationInSeconds': serializer.toJson<int?>(durationInSeconds),
'id': serializer.toJson<String>(id), 'id': serializer.toJson<String>(id),
'checksum': serializer.toJson<String>(checksum), 'checksum': serializer.toJson<String>(checksum),
'isFavorite': serializer.toJson<bool>(isFavorite), 'isFavorite': serializer.toJson<bool>(isFavorite),
@@ -1298,7 +1301,7 @@ class RemoteAssetEntityData extends i0.DataClass
DateTime? updatedAt, DateTime? updatedAt,
i0.Value<int?> width = const i0.Value.absent(), i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(), i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(), i0.Value<int?> durationInSeconds = const i0.Value.absent(),
String? id, String? id,
String? checksum, String? checksum,
bool? isFavorite, bool? isFavorite,
@@ -1318,7 +1321,9 @@ class RemoteAssetEntityData extends i0.DataClass
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
width: width.present ? width.value : this.width, width: width.present ? width.value : this.width,
height: height.present ? height.value : this.height, height: height.present ? height.value : this.height,
durationMs: durationMs.present ? durationMs.value : this.durationMs, durationInSeconds: durationInSeconds.present
? durationInSeconds.value
: this.durationInSeconds,
id: id ?? this.id, id: id ?? this.id,
checksum: checksum ?? this.checksum, checksum: checksum ?? this.checksum,
isFavorite: isFavorite ?? this.isFavorite, isFavorite: isFavorite ?? this.isFavorite,
@@ -1344,9 +1349,9 @@ class RemoteAssetEntityData extends i0.DataClass
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
width: data.width.present ? data.width.value : this.width, width: data.width.present ? data.width.value : this.width,
height: data.height.present ? data.height.value : this.height, height: data.height.present ? data.height.value : this.height,
durationMs: data.durationMs.present durationInSeconds: data.durationInSeconds.present
? data.durationMs.value ? data.durationInSeconds.value
: this.durationMs, : this.durationInSeconds,
id: data.id.present ? data.id.value : this.id, id: data.id.present ? data.id.value : this.id,
checksum: data.checksum.present ? data.checksum.value : this.checksum, checksum: data.checksum.present ? data.checksum.value : this.checksum,
isFavorite: data.isFavorite.present isFavorite: data.isFavorite.present
@@ -1379,7 +1384,7 @@ class RemoteAssetEntityData extends i0.DataClass
..write('updatedAt: $updatedAt, ') ..write('updatedAt: $updatedAt, ')
..write('width: $width, ') ..write('width: $width, ')
..write('height: $height, ') ..write('height: $height, ')
..write('durationMs: $durationMs, ') ..write('durationInSeconds: $durationInSeconds, ')
..write('id: $id, ') ..write('id: $id, ')
..write('checksum: $checksum, ') ..write('checksum: $checksum, ')
..write('isFavorite: $isFavorite, ') ..write('isFavorite: $isFavorite, ')
@@ -1404,7 +1409,7 @@ class RemoteAssetEntityData extends i0.DataClass
updatedAt, updatedAt,
width, width,
height, height,
durationMs, durationInSeconds,
id, id,
checksum, checksum,
isFavorite, isFavorite,
@@ -1428,7 +1433,7 @@ class RemoteAssetEntityData extends i0.DataClass
other.updatedAt == this.updatedAt && other.updatedAt == this.updatedAt &&
other.width == this.width && other.width == this.width &&
other.height == this.height && other.height == this.height &&
other.durationMs == this.durationMs && other.durationInSeconds == this.durationInSeconds &&
other.id == this.id && other.id == this.id &&
other.checksum == this.checksum && other.checksum == this.checksum &&
other.isFavorite == this.isFavorite && other.isFavorite == this.isFavorite &&
@@ -1451,7 +1456,7 @@ class RemoteAssetEntityCompanion
final i0.Value<DateTime> updatedAt; final i0.Value<DateTime> updatedAt;
final i0.Value<int?> width; final i0.Value<int?> width;
final i0.Value<int?> height; final i0.Value<int?> height;
final i0.Value<int?> durationMs; final i0.Value<int?> durationInSeconds;
final i0.Value<String> id; final i0.Value<String> id;
final i0.Value<String> checksum; final i0.Value<String> checksum;
final i0.Value<bool> isFavorite; final i0.Value<bool> isFavorite;
@@ -1471,7 +1476,7 @@ class RemoteAssetEntityCompanion
this.updatedAt = const i0.Value.absent(), this.updatedAt = const i0.Value.absent(),
this.width = const i0.Value.absent(), this.width = const i0.Value.absent(),
this.height = const i0.Value.absent(), this.height = const i0.Value.absent(),
this.durationMs = const i0.Value.absent(), this.durationInSeconds = const i0.Value.absent(),
this.id = const i0.Value.absent(), this.id = const i0.Value.absent(),
this.checksum = const i0.Value.absent(), this.checksum = const i0.Value.absent(),
this.isFavorite = const i0.Value.absent(), this.isFavorite = const i0.Value.absent(),
@@ -1492,7 +1497,7 @@ class RemoteAssetEntityCompanion
this.updatedAt = const i0.Value.absent(), this.updatedAt = const i0.Value.absent(),
this.width = const i0.Value.absent(), this.width = const i0.Value.absent(),
this.height = const i0.Value.absent(), this.height = const i0.Value.absent(),
this.durationMs = const i0.Value.absent(), this.durationInSeconds = const i0.Value.absent(),
required String id, required String id,
required String checksum, required String checksum,
this.isFavorite = const i0.Value.absent(), this.isFavorite = const i0.Value.absent(),
@@ -1518,7 +1523,7 @@ class RemoteAssetEntityCompanion
i0.Expression<DateTime>? updatedAt, i0.Expression<DateTime>? updatedAt,
i0.Expression<int>? width, i0.Expression<int>? width,
i0.Expression<int>? height, i0.Expression<int>? height,
i0.Expression<int>? durationMs, i0.Expression<int>? durationInSeconds,
i0.Expression<String>? id, i0.Expression<String>? id,
i0.Expression<String>? checksum, i0.Expression<String>? checksum,
i0.Expression<bool>? isFavorite, i0.Expression<bool>? isFavorite,
@@ -1539,7 +1544,7 @@ class RemoteAssetEntityCompanion
if (updatedAt != null) 'updated_at': updatedAt, if (updatedAt != null) 'updated_at': updatedAt,
if (width != null) 'width': width, if (width != null) 'width': width,
if (height != null) 'height': height, if (height != null) 'height': height,
if (durationMs != null) 'duration_ms': durationMs, if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds,
if (id != null) 'id': id, if (id != null) 'id': id,
if (checksum != null) 'checksum': checksum, if (checksum != null) 'checksum': checksum,
if (isFavorite != null) 'is_favorite': isFavorite, if (isFavorite != null) 'is_favorite': isFavorite,
@@ -1562,7 +1567,7 @@ class RemoteAssetEntityCompanion
i0.Value<DateTime>? updatedAt, i0.Value<DateTime>? updatedAt,
i0.Value<int?>? width, i0.Value<int?>? width,
i0.Value<int?>? height, i0.Value<int?>? height,
i0.Value<int?>? durationMs, i0.Value<int?>? durationInSeconds,
i0.Value<String>? id, i0.Value<String>? id,
i0.Value<String>? checksum, i0.Value<String>? checksum,
i0.Value<bool>? isFavorite, i0.Value<bool>? isFavorite,
@@ -1583,7 +1588,7 @@ class RemoteAssetEntityCompanion
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
width: width ?? this.width, width: width ?? this.width,
height: height ?? this.height, height: height ?? this.height,
durationMs: durationMs ?? this.durationMs, durationInSeconds: durationInSeconds ?? this.durationInSeconds,
id: id ?? this.id, id: id ?? this.id,
checksum: checksum ?? this.checksum, checksum: checksum ?? this.checksum,
isFavorite: isFavorite ?? this.isFavorite, isFavorite: isFavorite ?? this.isFavorite,
@@ -1622,8 +1627,8 @@ class RemoteAssetEntityCompanion
if (height.present) { if (height.present) {
map['height'] = i0.Variable<int>(height.value); map['height'] = i0.Variable<int>(height.value);
} }
if (durationMs.present) { if (durationInSeconds.present) {
map['duration_ms'] = i0.Variable<int>(durationMs.value); map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds.value);
} }
if (id.present) { if (id.present) {
map['id'] = i0.Variable<String>(id.value); map['id'] = i0.Variable<String>(id.value);
@@ -1675,7 +1680,7 @@ class RemoteAssetEntityCompanion
..write('updatedAt: $updatedAt, ') ..write('updatedAt: $updatedAt, ')
..write('width: $width, ') ..write('width: $width, ')
..write('height: $height, ') ..write('height: $height, ')
..write('durationMs: $durationMs, ') ..write('durationInSeconds: $durationInSeconds, ')
..write('id: $id, ') ..write('id: $id, ')
..write('checksum: $checksum, ') ..write('checksum: $checksum, ')
..write('isFavorite: $isFavorite, ') ..write('isFavorite: $isFavorite, ')
@@ -42,7 +42,7 @@ extension TrashedLocalAssetEntityDataDomainExtension on TrashedLocalAssetEntityD
type: type, type: type,
createdAt: createdAt, createdAt: createdAt,
updatedAt: updatedAt, updatedAt: updatedAt,
durationMs: durationMs, durationInSeconds: durationInSeconds,
isFavorite: isFavorite, isFavorite: isFavorite,
height: height, height: height,
width: width, width: width,
@@ -16,7 +16,7 @@ typedef $$TrashedLocalAssetEntityTableCreateCompanionBuilder =
i0.Value<DateTime> updatedAt, i0.Value<DateTime> updatedAt,
i0.Value<int?> width, i0.Value<int?> width,
i0.Value<int?> height, i0.Value<int?> height,
i0.Value<int?> durationMs, i0.Value<int?> durationInSeconds,
required String id, required String id,
required String albumId, required String albumId,
i0.Value<String?> checksum, i0.Value<String?> checksum,
@@ -33,7 +33,7 @@ typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
i0.Value<DateTime> updatedAt, i0.Value<DateTime> updatedAt,
i0.Value<int?> width, i0.Value<int?> width,
i0.Value<int?> height, i0.Value<int?> height,
i0.Value<int?> durationMs, i0.Value<int?> durationInSeconds,
i0.Value<String> id, i0.Value<String> id,
i0.Value<String> albumId, i0.Value<String> albumId,
i0.Value<String?> checksum, i0.Value<String?> checksum,
@@ -84,8 +84,8 @@ class $$TrashedLocalAssetEntityTableFilterComposer
builder: (column) => i0.ColumnFilters(column), builder: (column) => i0.ColumnFilters(column),
); );
i0.ColumnFilters<int> get durationMs => $composableBuilder( i0.ColumnFilters<int> get durationInSeconds => $composableBuilder(
column: $table.durationMs, column: $table.durationInSeconds,
builder: (column) => i0.ColumnFilters(column), builder: (column) => i0.ColumnFilters(column),
); );
@@ -171,8 +171,8 @@ class $$TrashedLocalAssetEntityTableOrderingComposer
builder: (column) => i0.ColumnOrderings(column), builder: (column) => i0.ColumnOrderings(column),
); );
i0.ColumnOrderings<int> get durationMs => $composableBuilder( i0.ColumnOrderings<int> get durationInSeconds => $composableBuilder(
column: $table.durationMs, column: $table.durationInSeconds,
builder: (column) => i0.ColumnOrderings(column), builder: (column) => i0.ColumnOrderings(column),
); );
@@ -240,8 +240,8 @@ class $$TrashedLocalAssetEntityTableAnnotationComposer
i0.GeneratedColumn<int> get height => i0.GeneratedColumn<int> get height =>
$composableBuilder(column: $table.height, builder: (column) => column); $composableBuilder(column: $table.height, builder: (column) => column);
i0.GeneratedColumn<int> get durationMs => $composableBuilder( i0.GeneratedColumn<int> get durationInSeconds => $composableBuilder(
column: $table.durationMs, column: $table.durationInSeconds,
builder: (column) => column, builder: (column) => column,
); );
@@ -326,7 +326,7 @@ class $$TrashedLocalAssetEntityTableTableManager
i0.Value<DateTime> updatedAt = const i0.Value.absent(), i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<int?> width = const i0.Value.absent(), i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(), i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(), i0.Value<int?> durationInSeconds = const i0.Value.absent(),
i0.Value<String> id = const i0.Value.absent(), i0.Value<String> id = const i0.Value.absent(),
i0.Value<String> albumId = const i0.Value.absent(), i0.Value<String> albumId = const i0.Value.absent(),
i0.Value<String?> checksum = const i0.Value.absent(), i0.Value<String?> checksum = const i0.Value.absent(),
@@ -342,7 +342,7 @@ class $$TrashedLocalAssetEntityTableTableManager
updatedAt: updatedAt, updatedAt: updatedAt,
width: width, width: width,
height: height, height: height,
durationMs: durationMs, durationInSeconds: durationInSeconds,
id: id, id: id,
albumId: albumId, albumId: albumId,
checksum: checksum, checksum: checksum,
@@ -359,7 +359,7 @@ class $$TrashedLocalAssetEntityTableTableManager
i0.Value<DateTime> updatedAt = const i0.Value.absent(), i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<int?> width = const i0.Value.absent(), i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(), i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(), i0.Value<int?> durationInSeconds = const i0.Value.absent(),
required String id, required String id,
required String albumId, required String albumId,
i0.Value<String?> checksum = const i0.Value.absent(), i0.Value<String?> checksum = const i0.Value.absent(),
@@ -375,7 +375,7 @@ class $$TrashedLocalAssetEntityTableTableManager
updatedAt: updatedAt, updatedAt: updatedAt,
width: width, width: width,
height: height, height: height,
durationMs: durationMs, durationInSeconds: durationInSeconds,
id: id, id: id,
albumId: albumId, albumId: albumId,
checksum: checksum, checksum: checksum,
@@ -498,17 +498,17 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
type: i0.DriftSqlType.int, type: i0.DriftSqlType.int,
requiredDuringInsert: false, requiredDuringInsert: false,
); );
static const i0.VerificationMeta _durationMsMeta = const i0.VerificationMeta( static const i0.VerificationMeta _durationInSecondsMeta =
'durationMs', const i0.VerificationMeta('durationInSeconds');
);
@override @override
late final i0.GeneratedColumn<int> durationMs = i0.GeneratedColumn<int>( late final i0.GeneratedColumn<int> durationInSeconds =
'duration_ms', i0.GeneratedColumn<int>(
aliasedName, 'duration_in_seconds',
true, aliasedName,
type: i0.DriftSqlType.int, true,
requiredDuringInsert: false, type: i0.DriftSqlType.int,
); requiredDuringInsert: false,
);
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
@override @override
late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>( late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>(
@@ -599,7 +599,7 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
updatedAt, updatedAt,
width, width,
height, height,
durationMs, durationInSeconds,
id, id,
albumId, albumId,
checksum, checksum,
@@ -652,10 +652,13 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
height.isAcceptableOrUnknown(data['height']!, _heightMeta), height.isAcceptableOrUnknown(data['height']!, _heightMeta),
); );
} }
if (data.containsKey('duration_ms')) { if (data.containsKey('duration_in_seconds')) {
context.handle( context.handle(
_durationMsMeta, _durationInSecondsMeta,
durationMs.isAcceptableOrUnknown(data['duration_ms']!, _durationMsMeta), durationInSeconds.isAcceptableOrUnknown(
data['duration_in_seconds']!,
_durationInSecondsMeta,
),
); );
} }
if (data.containsKey('id')) { if (data.containsKey('id')) {
@@ -730,9 +733,9 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
i0.DriftSqlType.int, i0.DriftSqlType.int,
data['${effectivePrefix}height'], data['${effectivePrefix}height'],
), ),
durationMs: attachedDatabase.typeMapping.read( durationInSeconds: attachedDatabase.typeMapping.read(
i0.DriftSqlType.int, i0.DriftSqlType.int,
data['${effectivePrefix}duration_ms'], data['${effectivePrefix}duration_in_seconds'],
), ),
id: attachedDatabase.typeMapping.read( id: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string, i0.DriftSqlType.string,
@@ -797,7 +800,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
final DateTime updatedAt; final DateTime updatedAt;
final int? width; final int? width;
final int? height; final int? height;
final int? durationMs; final int? durationInSeconds;
final String id; final String id;
final String albumId; final String albumId;
final String? checksum; final String? checksum;
@@ -812,7 +815,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
required this.updatedAt, required this.updatedAt,
this.width, this.width,
this.height, this.height,
this.durationMs, this.durationInSeconds,
required this.id, required this.id,
required this.albumId, required this.albumId,
this.checksum, this.checksum,
@@ -838,8 +841,8 @@ class TrashedLocalAssetEntityData extends i0.DataClass
if (!nullToAbsent || height != null) { if (!nullToAbsent || height != null) {
map['height'] = i0.Variable<int>(height); map['height'] = i0.Variable<int>(height);
} }
if (!nullToAbsent || durationMs != null) { if (!nullToAbsent || durationInSeconds != null) {
map['duration_ms'] = i0.Variable<int>(durationMs); map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds);
} }
map['id'] = i0.Variable<String>(id); map['id'] = i0.Variable<String>(id);
map['album_id'] = i0.Variable<String>(albumId); map['album_id'] = i0.Variable<String>(albumId);
@@ -877,7 +880,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']), updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
width: serializer.fromJson<int?>(json['width']), width: serializer.fromJson<int?>(json['width']),
height: serializer.fromJson<int?>(json['height']), height: serializer.fromJson<int?>(json['height']),
durationMs: serializer.fromJson<int?>(json['durationMs']), durationInSeconds: serializer.fromJson<int?>(json['durationInSeconds']),
id: serializer.fromJson<String>(json['id']), id: serializer.fromJson<String>(json['id']),
albumId: serializer.fromJson<String>(json['albumId']), albumId: serializer.fromJson<String>(json['albumId']),
checksum: serializer.fromJson<String?>(json['checksum']), checksum: serializer.fromJson<String?>(json['checksum']),
@@ -902,7 +905,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
'updatedAt': serializer.toJson<DateTime>(updatedAt), 'updatedAt': serializer.toJson<DateTime>(updatedAt),
'width': serializer.toJson<int?>(width), 'width': serializer.toJson<int?>(width),
'height': serializer.toJson<int?>(height), 'height': serializer.toJson<int?>(height),
'durationMs': serializer.toJson<int?>(durationMs), 'durationInSeconds': serializer.toJson<int?>(durationInSeconds),
'id': serializer.toJson<String>(id), 'id': serializer.toJson<String>(id),
'albumId': serializer.toJson<String>(albumId), 'albumId': serializer.toJson<String>(albumId),
'checksum': serializer.toJson<String?>(checksum), 'checksum': serializer.toJson<String?>(checksum),
@@ -926,7 +929,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
DateTime? updatedAt, DateTime? updatedAt,
i0.Value<int?> width = const i0.Value.absent(), i0.Value<int?> width = const i0.Value.absent(),
i0.Value<int?> height = const i0.Value.absent(), i0.Value<int?> height = const i0.Value.absent(),
i0.Value<int?> durationMs = const i0.Value.absent(), i0.Value<int?> durationInSeconds = const i0.Value.absent(),
String? id, String? id,
String? albumId, String? albumId,
i0.Value<String?> checksum = const i0.Value.absent(), i0.Value<String?> checksum = const i0.Value.absent(),
@@ -941,7 +944,9 @@ class TrashedLocalAssetEntityData extends i0.DataClass
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
width: width.present ? width.value : this.width, width: width.present ? width.value : this.width,
height: height.present ? height.value : this.height, height: height.present ? height.value : this.height,
durationMs: durationMs.present ? durationMs.value : this.durationMs, durationInSeconds: durationInSeconds.present
? durationInSeconds.value
: this.durationInSeconds,
id: id ?? this.id, id: id ?? this.id,
albumId: albumId ?? this.albumId, albumId: albumId ?? this.albumId,
checksum: checksum.present ? checksum.value : this.checksum, checksum: checksum.present ? checksum.value : this.checksum,
@@ -960,9 +965,9 @@ class TrashedLocalAssetEntityData extends i0.DataClass
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
width: data.width.present ? data.width.value : this.width, width: data.width.present ? data.width.value : this.width,
height: data.height.present ? data.height.value : this.height, height: data.height.present ? data.height.value : this.height,
durationMs: data.durationMs.present durationInSeconds: data.durationInSeconds.present
? data.durationMs.value ? data.durationInSeconds.value
: this.durationMs, : this.durationInSeconds,
id: data.id.present ? data.id.value : this.id, id: data.id.present ? data.id.value : this.id,
albumId: data.albumId.present ? data.albumId.value : this.albumId, albumId: data.albumId.present ? data.albumId.value : this.albumId,
checksum: data.checksum.present ? data.checksum.value : this.checksum, checksum: data.checksum.present ? data.checksum.value : this.checksum,
@@ -988,7 +993,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
..write('updatedAt: $updatedAt, ') ..write('updatedAt: $updatedAt, ')
..write('width: $width, ') ..write('width: $width, ')
..write('height: $height, ') ..write('height: $height, ')
..write('durationMs: $durationMs, ') ..write('durationInSeconds: $durationInSeconds, ')
..write('id: $id, ') ..write('id: $id, ')
..write('albumId: $albumId, ') ..write('albumId: $albumId, ')
..write('checksum: $checksum, ') ..write('checksum: $checksum, ')
@@ -1008,7 +1013,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
updatedAt, updatedAt,
width, width,
height, height,
durationMs, durationInSeconds,
id, id,
albumId, albumId,
checksum, checksum,
@@ -1027,7 +1032,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
other.updatedAt == this.updatedAt && other.updatedAt == this.updatedAt &&
other.width == this.width && other.width == this.width &&
other.height == this.height && other.height == this.height &&
other.durationMs == this.durationMs && other.durationInSeconds == this.durationInSeconds &&
other.id == this.id && other.id == this.id &&
other.albumId == this.albumId && other.albumId == this.albumId &&
other.checksum == this.checksum && other.checksum == this.checksum &&
@@ -1045,7 +1050,7 @@ class TrashedLocalAssetEntityCompanion
final i0.Value<DateTime> updatedAt; final i0.Value<DateTime> updatedAt;
final i0.Value<int?> width; final i0.Value<int?> width;
final i0.Value<int?> height; final i0.Value<int?> height;
final i0.Value<int?> durationMs; final i0.Value<int?> durationInSeconds;
final i0.Value<String> id; final i0.Value<String> id;
final i0.Value<String> albumId; final i0.Value<String> albumId;
final i0.Value<String?> checksum; final i0.Value<String?> checksum;
@@ -1060,7 +1065,7 @@ class TrashedLocalAssetEntityCompanion
this.updatedAt = const i0.Value.absent(), this.updatedAt = const i0.Value.absent(),
this.width = const i0.Value.absent(), this.width = const i0.Value.absent(),
this.height = const i0.Value.absent(), this.height = const i0.Value.absent(),
this.durationMs = const i0.Value.absent(), this.durationInSeconds = const i0.Value.absent(),
this.id = const i0.Value.absent(), this.id = const i0.Value.absent(),
this.albumId = const i0.Value.absent(), this.albumId = const i0.Value.absent(),
this.checksum = const i0.Value.absent(), this.checksum = const i0.Value.absent(),
@@ -1076,7 +1081,7 @@ class TrashedLocalAssetEntityCompanion
this.updatedAt = const i0.Value.absent(), this.updatedAt = const i0.Value.absent(),
this.width = const i0.Value.absent(), this.width = const i0.Value.absent(),
this.height = const i0.Value.absent(), this.height = const i0.Value.absent(),
this.durationMs = const i0.Value.absent(), this.durationInSeconds = const i0.Value.absent(),
required String id, required String id,
required String albumId, required String albumId,
this.checksum = const i0.Value.absent(), this.checksum = const i0.Value.absent(),
@@ -1096,7 +1101,7 @@ class TrashedLocalAssetEntityCompanion
i0.Expression<DateTime>? updatedAt, i0.Expression<DateTime>? updatedAt,
i0.Expression<int>? width, i0.Expression<int>? width,
i0.Expression<int>? height, i0.Expression<int>? height,
i0.Expression<int>? durationMs, i0.Expression<int>? durationInSeconds,
i0.Expression<String>? id, i0.Expression<String>? id,
i0.Expression<String>? albumId, i0.Expression<String>? albumId,
i0.Expression<String>? checksum, i0.Expression<String>? checksum,
@@ -1112,7 +1117,7 @@ class TrashedLocalAssetEntityCompanion
if (updatedAt != null) 'updated_at': updatedAt, if (updatedAt != null) 'updated_at': updatedAt,
if (width != null) 'width': width, if (width != null) 'width': width,
if (height != null) 'height': height, if (height != null) 'height': height,
if (durationMs != null) 'duration_ms': durationMs, if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds,
if (id != null) 'id': id, if (id != null) 'id': id,
if (albumId != null) 'album_id': albumId, if (albumId != null) 'album_id': albumId,
if (checksum != null) 'checksum': checksum, if (checksum != null) 'checksum': checksum,
@@ -1130,7 +1135,7 @@ class TrashedLocalAssetEntityCompanion
i0.Value<DateTime>? updatedAt, i0.Value<DateTime>? updatedAt,
i0.Value<int?>? width, i0.Value<int?>? width,
i0.Value<int?>? height, i0.Value<int?>? height,
i0.Value<int?>? durationMs, i0.Value<int?>? durationInSeconds,
i0.Value<String>? id, i0.Value<String>? id,
i0.Value<String>? albumId, i0.Value<String>? albumId,
i0.Value<String?>? checksum, i0.Value<String?>? checksum,
@@ -1146,7 +1151,7 @@ class TrashedLocalAssetEntityCompanion
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
width: width ?? this.width, width: width ?? this.width,
height: height ?? this.height, height: height ?? this.height,
durationMs: durationMs ?? this.durationMs, durationInSeconds: durationInSeconds ?? this.durationInSeconds,
id: id ?? this.id, id: id ?? this.id,
albumId: albumId ?? this.albumId, albumId: albumId ?? this.albumId,
checksum: checksum ?? this.checksum, checksum: checksum ?? this.checksum,
@@ -1180,8 +1185,8 @@ class TrashedLocalAssetEntityCompanion
if (height.present) { if (height.present) {
map['height'] = i0.Variable<int>(height.value); map['height'] = i0.Variable<int>(height.value);
} }
if (durationMs.present) { if (durationInSeconds.present) {
map['duration_ms'] = i0.Variable<int>(durationMs.value); map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds.value);
} }
if (id.present) { if (id.present) {
map['id'] = i0.Variable<String>(id.value); map['id'] = i0.Variable<String>(id.value);
@@ -1222,7 +1227,7 @@ class TrashedLocalAssetEntityCompanion
..write('updatedAt: $updatedAt, ') ..write('updatedAt: $updatedAt, ')
..write('width: $width, ') ..write('width: $width, ')
..write('height: $height, ') ..write('height: $height, ')
..write('durationMs: $durationMs, ') ..write('durationInSeconds: $durationInSeconds, ')
..write('id: $id, ') ..write('id: $id, ')
..write('albumId: $albumId, ') ..write('albumId: $albumId, ')
..write('checksum: $checksum, ') ..write('checksum: $checksum, ')
@@ -10,7 +10,6 @@ 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.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_album_asset.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.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/memory.entity.dart'; import 'package:immich_mobile/infrastructure/entities/memory.entity.dart';
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart'; import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
@@ -19,12 +18,10 @@ 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_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album_user.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.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/remote_asset_cloud_id.entity.dart';
import 'package:immich_mobile/infrastructure/entities/stack.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/store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.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.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.drift.dart';
@@ -84,7 +81,7 @@ class Drift extends $Drift {
} }
@override @override
int get schemaVersion => 24; int get schemaVersion => 22;
@override @override
MigrationStrategy get migration => MigrationStrategy( MigrationStrategy get migration => MigrationStrategy(
@@ -225,31 +222,6 @@ class Drift extends $Drift {
await m.createTable(v22.assetEditEntity); await m.createTable(v22.assetEditEntity);
await m.createIndex(v22.idxAssetEditAssetId); 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));
},
), ),
); );
@@ -105,6 +105,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
localAlbumEntity, localAlbumEntity,
localAlbumAssetEntity, localAlbumAssetEntity,
i7.idxLocalAlbumAssetAlbumAsset, i7.idxLocalAlbumAssetAlbumAsset,
i5.idxRemoteAlbumOwnerId,
i4.idxLocalAssetChecksum, i4.idxLocalAssetChecksum,
i4.idxLocalAssetCloudId, i4.idxLocalAssetCloudId,
i3.idxStackPrimaryAssetId, i3.idxStackPrimaryAssetId,
@@ -159,6 +160,15 @@ abstract class $Drift extends i0.GeneratedDatabase {
), ),
result: [i0.TableUpdate('stack_entity', kind: i0.UpdateKind.delete)], 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( i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName( on: i0.TableUpdateQuery.onTableName(
'remote_asset_entity', 'remote_asset_entity',
File diff suppressed because it is too large Load Diff
@@ -297,7 +297,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
updatedAt: Value(asset.updatedAt), updatedAt: Value(asset.updatedAt),
width: Value(asset.width), width: Value(asset.width),
height: Value(asset.height), height: Value(asset.height),
durationMs: Value(asset.durationMs), durationInSeconds: Value(asset.durationInSeconds),
id: asset.id, id: asset.id,
orientation: Value(asset.orientation), orientation: Value(asset.orientation),
isFavorite: Value(asset.isFavorite), isFavorite: Value(asset.isFavorite),
@@ -329,7 +329,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
updatedAt: Value(asset.updatedAt), updatedAt: Value(asset.updatedAt),
width: Value(asset.width), width: Value(asset.width),
height: Value(asset.height), height: Value(asset.height),
durationMs: Value(asset.durationMs), durationInSeconds: Value(asset.durationInSeconds),
id: asset.id, id: asset.id,
checksum: const Value(null), checksum: const Value(null),
orientation: Value(asset.orientation), orientation: Value(asset.orientation),
@@ -109,40 +109,31 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
return query.map((localAlbum) => localAlbum.toDto()).get(); return query.map((localAlbum) => localAlbum.toDto()).get();
} }
Future<Map<String, List<LocalAsset>>> getAssetsFromBackupAlbums(Iterable<String> remoteIds) async { Future<Map<String, List<LocalAsset>>> getAssetsFromBackupAlbums(Iterable<String> checksums) async {
if (remoteIds.isEmpty) { if (checksums.isEmpty) {
return {}; return {};
} }
final result = <String, List<LocalAsset>>{}; final result = <String, List<LocalAsset>>{};
for (final slice in remoteIds.toSet().slices(kDriftMaxChunk)) { for (final slice in checksums.toSet().slices(kDriftMaxChunk)) {
final rows = final rows =
await (_db.select(_db.localAlbumAssetEntity).join([ await (_db.select(_db.localAlbumAssetEntity).join([
innerJoin( innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id)),
_db.localAlbumEntity,
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
useColumns: false,
),
innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)), innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)),
innerJoin(
_db.remoteAssetEntity,
_db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum),
useColumns: false,
),
])..where( ])..where(
_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) & _db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) &
_db.remoteAssetEntity.id.isIn(slice), _db.localAssetEntity.checksum.isIn(slice),
)) ))
.get(); .get();
for (final row in rows) { for (final row in rows) {
final albumId = row.readTable(_db.localAlbumAssetEntity).albumId; final albumId = row.readTable(_db.localAlbumAssetEntity).albumId;
final asset = row.readTable(_db.localAssetEntity).toDto(); final assetData = row.readTable(_db.localAssetEntity);
final asset = assetData.toDto();
(result[albumId] ??= <LocalAsset>[]).add(asset); (result[albumId] ??= <LocalAsset>[]).add(asset);
} }
} }
return result; return result;
} }
@@ -32,23 +32,17 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
useColumns: false, useColumns: false,
), ),
leftOuterJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), useColumns: false),
leftOuterJoin( leftOuterJoin(
_db.remoteAlbumUserEntity, _db.remoteAlbumUserEntity,
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
useColumns: false, 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 query
..where(_db.remoteAssetEntity.deletedAt.isNull()) ..where(_db.remoteAssetEntity.deletedAt.isNull())
..addColumns([assetCount]) ..addColumns([assetCount])
..addColumns([_db.userEntity.name, _db.userEntity.id]) ..addColumns([_db.userEntity.name])
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)]) ..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
..groupBy([_db.remoteAlbumEntity.id]); ..groupBy([_db.remoteAlbumEntity.id]);
@@ -69,7 +63,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
.readTable(_db.remoteAlbumEntity) .readTable(_db.remoteAlbumEntity)
.toDto( .toDto(
assetCount: row.read(assetCount) ?? 0, assetCount: row.read(assetCount) ?? 0,
ownerId: row.read(_db.userEntity.id)!,
ownerName: row.read(_db.userEntity.name)!, ownerName: row.read(_db.userEntity.name)!,
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
), ),
@@ -93,21 +86,19 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
useColumns: false, useColumns: false,
), ),
leftOuterJoin( leftOuterJoin(
_db.remoteAlbumUserEntity, _db.userEntity,
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
useColumns: false, useColumns: false,
), ),
innerJoin( leftOuterJoin(
_db.userEntity, _db.remoteAlbumUserEntity,
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) & _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
useColumns: false, useColumns: false,
), ),
]) ])
..where(_db.remoteAlbumEntity.id.equals(albumId) & _db.remoteAssetEntity.deletedAt.isNull()) ..where(_db.remoteAlbumEntity.id.equals(albumId) & _db.remoteAssetEntity.deletedAt.isNull())
..addColumns([assetCount]) ..addColumns([assetCount])
..addColumns([_db.userEntity.name, _db.userEntity.id]) ..addColumns([_db.userEntity.name])
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)]) ..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
..groupBy([_db.remoteAlbumEntity.id]); ..groupBy([_db.remoteAlbumEntity.id]);
@@ -117,7 +108,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
.readTable(_db.remoteAlbumEntity) .readTable(_db.remoteAlbumEntity)
.toDto( .toDto(
assetCount: row.read(assetCount) ?? 0, assetCount: row.read(assetCount) ?? 0,
ownerId: row.read(_db.userEntity.id)!,
ownerName: row.read(_db.userEntity.name)!, ownerName: row.read(_db.userEntity.name)!,
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
), ),
@@ -126,29 +116,12 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
} }
Future<RemoteAlbum?> getByName(String albumName, String ownerId) { Future<RemoteAlbum?> getByName(String albumName, String ownerId) {
final query = _db.remoteAlbumEntity.select().join([ final query = _db.remoteAlbumEntity.select()
innerJoin( ..where((row) => row.name.equals(albumName) & row.ownerId.equals(ownerId))
_db.remoteAlbumUserEntity, ..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
_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); ..limit(1);
return query return query.map((row) => row.toDto(ownerName: '', isShared: false)).getSingleOrNull();
.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 { Future<void> create(RemoteAlbum album, List<String> assetIds) async {
@@ -156,6 +129,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
final entity = RemoteAlbumEntityCompanion( final entity = RemoteAlbumEntityCompanion(
id: Value(album.id), id: Value(album.id),
name: Value(album.name), name: Value(album.name),
ownerId: Value(album.ownerId),
createdAt: Value(album.createdAt), createdAt: Value(album.createdAt),
updatedAt: Value(album.updatedAt), updatedAt: Value(album.updatedAt),
description: Value(album.description), description: Value(album.description),
@@ -166,14 +140,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
await _db.remoteAlbumEntity.insertOne(entity); 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) { if (assetIds.isNotEmpty) {
final albumAssets = assetIds.map( final albumAssets = assetIds.map(
(assetId) => RemoteAlbumAssetEntityCompanion(albumId: Value(album.id), assetId: Value(assetId)), (assetId) => RemoteAlbumAssetEntityCompanion(albumId: Value(album.id), assetId: Value(assetId)),
@@ -191,6 +157,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
RemoteAlbumEntityCompanion( RemoteAlbumEntityCompanion(
id: Value(album.id), id: Value(album.id),
name: Value(album.name), name: Value(album.name),
ownerId: Value(album.ownerId),
createdAt: Value(album.createdAt), createdAt: Value(album.createdAt),
updatedAt: Value(album.updatedAt), updatedAt: Value(album.updatedAt),
description: Value(album.description), description: Value(album.description),
@@ -230,7 +197,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
Future<List<UserDto>> getSharedUsers(String albumId) async { Future<List<UserDto>> getSharedUsers(String albumId) async {
final albumUserRows = await (_db.select( final albumUserRows = await (_db.select(
_db.remoteAlbumUserEntity, _db.remoteAlbumUserEntity,
)..where((row) => row.albumId.equals(albumId) & row.role.isNotValue(AlbumUserRole.owner.index))).get(); )..where((row) => row.albumId.equals(albumId))).get();
if (albumUserRows.isEmpty) { if (albumUserRows.isEmpty) {
return []; return [];
@@ -328,21 +295,19 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
useColumns: false, useColumns: false,
), ),
leftOuterJoin(
_db.userEntity,
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
useColumns: false,
),
leftOuterJoin( leftOuterJoin(
_db.remoteAlbumUserEntity, _db.remoteAlbumUserEntity,
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
useColumns: false, 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)) ..where(_db.remoteAlbumEntity.id.equals(albumId))
..addColumns([_db.userEntity.name, _db.userEntity.id]) ..addColumns([_db.userEntity.name])
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)]) ..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
..groupBy([_db.remoteAlbumEntity.id]); ..groupBy([_db.remoteAlbumEntity.id]);
@@ -350,7 +315,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
final album = row final album = row
.readTable(_db.remoteAlbumEntity) .readTable(_db.remoteAlbumEntity)
.toDto( .toDto(
ownerId: row.read(_db.userEntity.id)!,
ownerName: row.read(_db.userEntity.name)!, ownerName: row.read(_db.userEntity.name)!,
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
); );
@@ -391,37 +355,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
return _db.managers.remoteAlbumEntity.count(); 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 { Future<List<String>> getLinkedAssetIds(String userId, String localAlbumId, String remoteAlbumId) async {
// Find remote asset ids that: // Find remote asset ids that:
// 1. Belong to the provided local album (via local_album_asset_entity) // 1. Belong to the provided local album (via local_album_asset_entity)
@@ -484,22 +417,20 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
useColumns: false, useColumns: false,
), ),
leftOuterJoin( leftOuterJoin(
_db.remoteAlbumUserEntity, _db.userEntity,
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
useColumns: false, useColumns: false,
), ),
innerJoin( leftOuterJoin(
_db.userEntity, _db.remoteAlbumUserEntity,
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) & _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
useColumns: false, useColumns: false,
), ),
]) ])
..where(_db.remoteAlbumEntity.id.isIn(albumIds) & _db.remoteAssetEntity.deletedAt.isNull()) ..where(_db.remoteAlbumEntity.id.isIn(albumIds) & _db.remoteAssetEntity.deletedAt.isNull())
..addColumns([assetCount]) ..addColumns([assetCount])
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)]) ..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
..addColumns([_db.userEntity.name, _db.userEntity.id]) ..addColumns([_db.userEntity.name])
..groupBy([_db.remoteAlbumEntity.id]); ..groupBy([_db.remoteAlbumEntity.id]);
return query return query
@@ -507,7 +438,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
(row) => row (row) => row
.readTable(_db.remoteAlbumEntity) .readTable(_db.remoteAlbumEntity)
.toDto( .toDto(
ownerId: row.read(_db.userEntity.id)!,
ownerName: row.read(_db.userEntity.name) ?? '', ownerName: row.read(_db.userEntity.name) ?? '',
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
assetCount: row.read(assetCount) ?? 0, assetCount: row.read(assetCount) ?? 0,
@@ -518,7 +448,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
} }
extension on RemoteAlbumEntityData { extension on RemoteAlbumEntityData {
RemoteAlbum toDto({int assetCount = 0, required String ownerName, required String ownerId, required bool isShared}) { RemoteAlbum toDto({int assetCount = 0, required String ownerName, required bool isShared}) {
return RemoteAlbum( return RemoteAlbum(
id: id, id: id,
name: name, name: name,
@@ -46,25 +46,16 @@ class SyncApiRepository {
types: [ types: [
SyncRequestType.authUsersV1, SyncRequestType.authUsersV1,
SyncRequestType.usersV1, SyncRequestType.usersV1,
serverVersion >= const SemVer(major: 3, minor: 0, patch: 0) SyncRequestType.assetsV1,
? SyncRequestType.assetsV2
: SyncRequestType.assetsV1,
SyncRequestType.assetExifsV1, SyncRequestType.assetExifsV1,
if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetEditsV1, if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetEditsV1,
SyncRequestType.assetMetadataV1, SyncRequestType.assetMetadataV1,
SyncRequestType.partnersV1, SyncRequestType.partnersV1,
serverVersion >= const SemVer(major: 3, minor: 0, patch: 0) SyncRequestType.partnerAssetsV1,
? SyncRequestType.partnerAssetsV2
: SyncRequestType.partnerAssetsV1,
SyncRequestType.partnerAssetExifsV1, SyncRequestType.partnerAssetExifsV1,
if (serverVersion < const SemVer(major: 3, minor: 0, patch: 0)) SyncRequestType.albumsV1,
SyncRequestType.albumsV1
else
SyncRequestType.albumsV2,
SyncRequestType.albumUsersV1, SyncRequestType.albumUsersV1,
serverVersion >= const SemVer(major: 3, minor: 0, patch: 0) SyncRequestType.albumAssetsV1,
? SyncRequestType.albumAssetsV2
: SyncRequestType.albumAssetsV1,
SyncRequestType.albumAssetExifsV1, SyncRequestType.albumAssetExifsV1,
SyncRequestType.albumToAssetsV1, SyncRequestType.albumToAssetsV1,
SyncRequestType.memoriesV1, SyncRequestType.memoriesV1,
@@ -73,9 +64,8 @@ class SyncApiRepository {
SyncRequestType.partnerStacksV1, SyncRequestType.partnerStacksV1,
SyncRequestType.userMetadataV1, SyncRequestType.userMetadataV1,
SyncRequestType.peopleV1, SyncRequestType.peopleV1,
serverVersion >= const SemVer(major: 2, minor: 6, patch: 0) if (serverVersion < const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV1,
? SyncRequestType.assetFacesV2 if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV2,
: SyncRequestType.assetFacesV1,
], ],
reset: shouldReset, reset: shouldReset,
).toJson(), ).toJson(),
@@ -160,7 +150,6 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
SyncEntityType.partnerV1: SyncPartnerV1.fromJson, SyncEntityType.partnerV1: SyncPartnerV1.fromJson,
SyncEntityType.partnerDeleteV1: SyncPartnerDeleteV1.fromJson, SyncEntityType.partnerDeleteV1: SyncPartnerDeleteV1.fromJson,
SyncEntityType.assetV1: SyncAssetV1.fromJson, SyncEntityType.assetV1: SyncAssetV1.fromJson,
SyncEntityType.assetV2: SyncAssetV2.fromJson,
SyncEntityType.assetDeleteV1: SyncAssetDeleteV1.fromJson, SyncEntityType.assetDeleteV1: SyncAssetDeleteV1.fromJson,
SyncEntityType.assetExifV1: SyncAssetExifV1.fromJson, SyncEntityType.assetExifV1: SyncAssetExifV1.fromJson,
SyncEntityType.assetEditV1: SyncAssetEditV1.fromJson, SyncEntityType.assetEditV1: SyncAssetEditV1.fromJson,
@@ -168,24 +157,18 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
SyncEntityType.assetMetadataV1: SyncAssetMetadataV1.fromJson, SyncEntityType.assetMetadataV1: SyncAssetMetadataV1.fromJson,
SyncEntityType.assetMetadataDeleteV1: SyncAssetMetadataDeleteV1.fromJson, SyncEntityType.assetMetadataDeleteV1: SyncAssetMetadataDeleteV1.fromJson,
SyncEntityType.partnerAssetV1: SyncAssetV1.fromJson, SyncEntityType.partnerAssetV1: SyncAssetV1.fromJson,
SyncEntityType.partnerAssetV2: SyncAssetV2.fromJson,
SyncEntityType.partnerAssetBackfillV1: SyncAssetV1.fromJson, SyncEntityType.partnerAssetBackfillV1: SyncAssetV1.fromJson,
SyncEntityType.partnerAssetBackfillV2: SyncAssetV2.fromJson,
SyncEntityType.partnerAssetDeleteV1: SyncAssetDeleteV1.fromJson, SyncEntityType.partnerAssetDeleteV1: SyncAssetDeleteV1.fromJson,
SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson, SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson,
SyncEntityType.partnerAssetExifBackfillV1: SyncAssetExifV1.fromJson, SyncEntityType.partnerAssetExifBackfillV1: SyncAssetExifV1.fromJson,
SyncEntityType.albumV1: SyncAlbumV1.fromJson, SyncEntityType.albumV1: SyncAlbumV1.fromJson,
SyncEntityType.albumV2: SyncAlbumV2.fromJson,
SyncEntityType.albumDeleteV1: SyncAlbumDeleteV1.fromJson, SyncEntityType.albumDeleteV1: SyncAlbumDeleteV1.fromJson,
SyncEntityType.albumUserV1: SyncAlbumUserV1.fromJson, SyncEntityType.albumUserV1: SyncAlbumUserV1.fromJson,
SyncEntityType.albumUserBackfillV1: SyncAlbumUserV1.fromJson, SyncEntityType.albumUserBackfillV1: SyncAlbumUserV1.fromJson,
SyncEntityType.albumUserDeleteV1: SyncAlbumUserDeleteV1.fromJson, SyncEntityType.albumUserDeleteV1: SyncAlbumUserDeleteV1.fromJson,
SyncEntityType.albumAssetCreateV1: SyncAssetV1.fromJson, SyncEntityType.albumAssetCreateV1: SyncAssetV1.fromJson,
SyncEntityType.albumAssetCreateV2: SyncAssetV2.fromJson,
SyncEntityType.albumAssetUpdateV1: SyncAssetV1.fromJson, SyncEntityType.albumAssetUpdateV1: SyncAssetV1.fromJson,
SyncEntityType.albumAssetUpdateV2: SyncAssetV2.fromJson,
SyncEntityType.albumAssetBackfillV1: SyncAssetV1.fromJson, SyncEntityType.albumAssetBackfillV1: SyncAssetV1.fromJson,
SyncEntityType.albumAssetBackfillV2: SyncAssetV2.fromJson,
SyncEntityType.albumAssetExifCreateV1: SyncAssetExifV1.fromJson, SyncEntityType.albumAssetExifCreateV1: SyncAssetExifV1.fromJson,
SyncEntityType.albumAssetExifUpdateV1: SyncAssetExifV1.fromJson, SyncEntityType.albumAssetExifUpdateV1: SyncAssetExifV1.fromJson,
SyncEntityType.albumAssetExifBackfillV1: SyncAssetExifV1.fromJson, SyncEntityType.albumAssetExifBackfillV1: SyncAssetExifV1.fromJson,
@@ -9,7 +9,6 @@ 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/memory.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/models/user_metadata.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_edit.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart';
@@ -30,7 +29,7 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart' as api show AssetVisibility, AlbumUserRole, UserMetadataKey, AssetEditAction; import 'package:openapi/api.dart' as api show AssetVisibility, AlbumUserRole, UserMetadataKey, AssetEditAction;
import 'package:openapi/api.dart' hide UserMetadataKey, AssetEditAction, AssetVisibility, AlbumUserRole; import 'package:openapi/api.dart' hide AlbumUserRole, UserMetadataKey, AssetEditAction, AssetVisibility;
class SyncStreamRepository extends DriftDatabaseRepository { class SyncStreamRepository extends DriftDatabaseRepository {
final Logger _logger = Logger('DriftSyncStreamRepository'); final Logger _logger = Logger('DriftSyncStreamRepository');
@@ -191,7 +190,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
type: Value(asset.type.toAssetType()), type: Value(asset.type.toAssetType()),
createdAt: Value.absentIfNull(asset.fileCreatedAt), createdAt: Value.absentIfNull(asset.fileCreatedAt),
updatedAt: Value.absentIfNull(asset.fileModifiedAt), updatedAt: Value.absentIfNull(asset.fileModifiedAt),
durationMs: Value(asset.duration?.toDuration()?.inMilliseconds ?? 0), durationInSeconds: Value(asset.duration?.toDuration()?.inSeconds ?? 0),
checksum: Value(asset.checksum), checksum: Value(asset.checksum),
isFavorite: Value(asset.isFavorite), isFavorite: Value(asset.isFavorite),
ownerId: Value(asset.ownerId), ownerId: Value(asset.ownerId),
@@ -220,44 +219,6 @@ class SyncStreamRepository extends DriftDatabaseRepository {
} }
} }
Future<void> updateAssetsV2(Iterable<SyncAssetV2> data, {String debugLabel = 'user'}) async {
try {
await _db.batch((batch) {
for (final asset in data) {
final companion = RemoteAssetEntityCompanion(
name: Value(asset.originalFileName),
type: Value(asset.type.toAssetType()),
createdAt: Value.absentIfNull(asset.fileCreatedAt),
updatedAt: Value.absentIfNull(asset.fileModifiedAt),
durationMs: Value(asset.duration),
checksum: Value(asset.checksum),
isFavorite: Value(asset.isFavorite),
ownerId: Value(asset.ownerId),
localDateTime: Value(asset.localDateTime),
thumbHash: Value(asset.thumbhash),
deletedAt: Value(asset.deletedAt),
visibility: Value(asset.visibility.toAssetVisibility()),
livePhotoVideoId: Value(asset.livePhotoVideoId),
stackId: Value(asset.stackId),
libraryId: Value(asset.libraryId),
width: Value(asset.width),
height: Value(asset.height),
isEdited: Value(asset.isEdited),
);
batch.insert(
_db.remoteAssetEntity,
companion.copyWith(id: Value(asset.id)),
onConflict: DoUpdate((_) => companion),
);
}
});
} catch (error, stack) {
_logger.severe('Error: updateAssetsV2 - $debugLabel', error, stack);
rethrow;
}
}
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data, {String debugLabel = 'user'}) async { Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data, {String debugLabel = 'user'}) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
@@ -435,47 +396,6 @@ class SyncStreamRepository extends DriftDatabaseRepository {
} }
Future<void> updateAlbumsV1(Iterable<SyncAlbumV1> data) async { 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 { try {
await _db.batch((batch) { await _db.batch((batch) {
for (final album in data) { for (final album in data) {
@@ -485,6 +405,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
isActivityEnabled: Value(album.isActivityEnabled), isActivityEnabled: Value(album.isActivityEnabled),
order: Value(album.order.toAlbumAssetOrder()), order: Value(album.order.toAlbumAssetOrder()),
thumbnailAssetId: Value(album.thumbnailAssetId), thumbnailAssetId: Value(album.thumbnailAssetId),
ownerId: Value(album.ownerId),
createdAt: Value(album.createdAt), createdAt: Value(album.createdAt),
updatedAt: Value(album.updatedAt), updatedAt: Value(album.updatedAt),
); );
@@ -497,7 +418,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
} }
}); });
} catch (error, stack) { } catch (error, stack) {
_logger.severe('Error: updateAlbumsV2', error, stack); _logger.severe('Error: updateAlbumsV1', error, stack);
rethrow; rethrow;
} }
} }
@@ -899,7 +820,6 @@ extension on api.AlbumUserRole {
AlbumUserRole toAlbumUserRole() => switch (this) { AlbumUserRole toAlbumUserRole() => switch (this) {
api.AlbumUserRole.editor => AlbumUserRole.editor, api.AlbumUserRole.editor => AlbumUserRole.editor,
api.AlbumUserRole.viewer => AlbumUserRole.viewer, api.AlbumUserRole.viewer => AlbumUserRole.viewer,
api.AlbumUserRole.owner => AlbumUserRole.owner,
_ => throw Exception('Unknown AlbumUserRole value: $this'), _ => throw Exception('Unknown AlbumUserRole value: $this'),
}; };
} }
@@ -923,6 +843,18 @@ 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 { extension on UserAvatarColor {
AvatarColor? toAvatarColor() => AvatarColor.values.firstWhereOrNull((c) => c.name == value); AvatarColor? toAvatarColor() => AvatarColor.values.firstWhereOrNull((c) => c.name == value);
} }
@@ -83,7 +83,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
width: row.width, width: row.width,
height: row.height, height: row.height,
isFavorite: row.isFavorite, isFavorite: row.isFavorite,
durationMs: row.durationMs, durationInSeconds: row.durationInSeconds,
livePhotoVideoId: row.livePhotoVideoId, livePhotoVideoId: row.livePhotoVideoId,
stackId: row.stackId, stackId: row.stackId,
isEdited: row.isEdited, isEdited: row.isEdited,
@@ -99,7 +99,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
width: row.width, width: row.width,
height: row.height, height: row.height,
isFavorite: row.isFavorite, isFavorite: row.isFavorite,
durationMs: row.durationMs, durationInSeconds: row.durationInSeconds,
orientation: row.orientation, orientation: row.orientation,
playbackStyle: AssetPlaybackStyle.values[row.playbackStyle], playbackStyle: AssetPlaybackStyle.values[row.playbackStyle],
cloudId: row.iCloudId, cloudId: row.iCloudId,
@@ -82,7 +82,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
updatedAt: Value(item.asset.updatedAt), updatedAt: Value(item.asset.updatedAt),
width: Value(item.asset.width), width: Value(item.asset.width),
height: Value(item.asset.height), height: Value(item.asset.height),
durationMs: Value(item.asset.durationMs), durationInSeconds: Value(item.asset.durationInSeconds),
isFavorite: Value(item.asset.isFavorite), isFavorite: Value(item.asset.isFavorite),
orientation: Value(item.asset.orientation), orientation: Value(item.asset.orientation),
playbackStyle: Value(item.asset.playbackStyle), playbackStyle: Value(item.asset.playbackStyle),
@@ -145,7 +145,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
type: Value(asset.type), type: Value(asset.type),
width: Value(asset.width), width: Value(asset.width),
height: Value(asset.height), height: Value(asset.height),
durationMs: Value(asset.durationMs), durationInSeconds: Value(asset.durationInSeconds),
isFavorite: Value(asset.isFavorite), isFavorite: Value(asset.isFavorite),
orientation: Value(asset.orientation), orientation: Value(asset.orientation),
playbackStyle: Value(asset.playbackStyle), playbackStyle: Value(asset.playbackStyle),
@@ -193,7 +193,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
updatedAt: Value(e.updatedAt), updatedAt: Value(e.updatedAt),
width: Value(e.width), width: Value(e.width),
height: Value(e.height), height: Value(e.height),
durationMs: Value(e.durationMs), durationInSeconds: Value(e.durationInSeconds),
checksum: Value(e.checksum), checksum: Value(e.checksum),
isFavorite: Value(e.isFavorite), isFavorite: Value(e.isFavorite),
orientation: Value(e.orientation), orientation: Value(e.orientation),
@@ -244,7 +244,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
updatedAt: Value(e.asset.updatedAt), updatedAt: Value(e.asset.updatedAt),
width: Value(e.asset.width), width: Value(e.asset.width),
height: Value(e.asset.height), height: Value(e.asset.height),
durationMs: Value(e.asset.durationMs), durationInSeconds: Value(e.asset.durationInSeconds),
checksum: Value(e.asset.checksum), checksum: Value(e.asset.checksum),
isFavorite: Value(e.asset.isFavorite), isFavorite: Value(e.asset.isFavorite),
orientation: Value(e.asset.orientation), orientation: Value(e.asset.orientation),
@@ -8,5 +8,5 @@ mixin AssetEntityMixin on Table {
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
IntColumn get width => integer().nullable()(); IntColumn get width => integer().nullable()();
IntColumn get height => integer().nullable()(); IntColumn get height => integer().nullable()();
IntColumn get durationMs => integer().nullable()(); IntColumn get durationInSeconds => integer().nullable()();
} }
+5 -5
View File
@@ -97,7 +97,7 @@ class PlatformAsset {
this.updatedAt, this.updatedAt,
this.width, this.width,
this.height, this.height,
required this.durationMs, required this.durationInSeconds,
required this.orientation, required this.orientation,
required this.isFavorite, required this.isFavorite,
this.adjustmentTime, this.adjustmentTime,
@@ -120,7 +120,7 @@ class PlatformAsset {
int? height; int? height;
int durationMs; int durationInSeconds;
int orientation; int orientation;
@@ -143,7 +143,7 @@ class PlatformAsset {
updatedAt, updatedAt,
width, width,
height, height,
durationMs, durationInSeconds,
orientation, orientation,
isFavorite, isFavorite,
adjustmentTime, adjustmentTime,
@@ -167,7 +167,7 @@ class PlatformAsset {
updatedAt: result[4] as int?, updatedAt: result[4] as int?,
width: result[5] as int?, width: result[5] as int?,
height: result[6] as int?, height: result[6] as int?,
durationMs: result[7]! as int, durationInSeconds: result[7]! as int,
orientation: result[8]! as int, orientation: result[8]! as int,
isFavorite: result[9]! as bool, isFavorite: result[9]! as bool,
adjustmentTime: result[10] as int?, adjustmentTime: result[10] as int?,
@@ -193,7 +193,7 @@ class PlatformAsset {
_deepEquals(updatedAt, other.updatedAt) && _deepEquals(updatedAt, other.updatedAt) &&
_deepEquals(width, other.width) && _deepEquals(width, other.width) &&
_deepEquals(height, other.height) && _deepEquals(height, other.height) &&
_deepEquals(durationMs, other.durationMs) && _deepEquals(durationInSeconds, other.durationInSeconds) &&
_deepEquals(orientation, other.orientation) && _deepEquals(orientation, other.orientation) &&
_deepEquals(isFavorite, other.isFavorite) && _deepEquals(isFavorite, other.isFavorite) &&
_deepEquals(adjustmentTime, other.adjustmentTime) && _deepEquals(adjustmentTime, other.adjustmentTime) &&
@@ -112,7 +112,10 @@ class _AssetPropertiesSectionState extends ConsumerState<_AssetPropertiesSection
_PropertyItem(label: 'Updated At', value: asset.updatedAt.toString()), _PropertyItem(label: 'Updated At', value: asset.updatedAt.toString()),
_PropertyItem(label: 'Width', value: asset.width?.toString()), _PropertyItem(label: 'Width', value: asset.width?.toString()),
_PropertyItem(label: 'Height', value: asset.height?.toString()), _PropertyItem(label: 'Height', value: asset.height?.toString()),
_PropertyItem(label: 'Duration', value: asset.durationMs != null ? '${asset.durationMs} ms' : null), _PropertyItem(
label: 'Duration',
value: asset.durationInSeconds != null ? '${asset.durationInSeconds} seconds' : null,
),
_PropertyItem(label: 'Is Favorite', value: asset.isFavorite.toString()), _PropertyItem(label: 'Is Favorite', value: asset.isFavorite.toString()),
_PropertyItem(label: 'Live Photo Video ID', value: asset.livePhotoVideoId), _PropertyItem(label: 'Live Photo Video ID', value: asset.livePhotoVideoId),
_PropertyItem(label: 'Is Edited', value: asset.isEdited.toString()), _PropertyItem(label: 'Is Edited', value: asset.isEdited.toString()),
@@ -14,7 +14,9 @@ import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart';
class _SharePreparingDialog extends StatelessWidget { class _SharePreparingDialog extends StatelessWidget {
const _SharePreparingDialog(); final ValueNotifier<double?> progress;
const _SharePreparingDialog({required this.progress});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -22,8 +24,24 @@ class _SharePreparingDialog extends StatelessWidget {
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const CircularProgressIndicator(), Container(margin: const EdgeInsets.only(bottom: 12), child: const Text('share_dialog_preparing').tr()),
Container(margin: const EdgeInsets.only(top: 12), child: const Text('share_dialog_preparing').tr()), SizedBox(
width: 240,
child: ValueListenableBuilder<double?>(
valueListenable: progress,
builder: (context, value, _) {
final percent = value == null ? null : (value * 100).clamp(0, 100);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
LinearProgressIndicator(value: value, minHeight: 8.0),
if (percent != null)
Container(margin: const EdgeInsets.only(top: 8), child: Text('${percent.toStringAsFixed(0)}%')),
],
);
},
),
),
], ],
), ),
); );
@@ -43,32 +61,39 @@ class ShareActionButton extends ConsumerWidget {
} }
final cancelCompleter = Completer<void>(); final cancelCompleter = Completer<void>();
const preparingDialog = _SharePreparingDialog(); final progress = ValueNotifier<double?>(null);
final preparingDialog = _SharePreparingDialog(progress: progress);
await showDialog( await showDialog(
context: context, context: context,
builder: (BuildContext buildContext) { builder: (BuildContext buildContext) {
ref.read(actionProvider.notifier).shareAssets(source, context, cancelCompleter: cancelCompleter).then(( ref
ActionResult result, .read(actionProvider.notifier)
) { .shareAssets(
if (cancelCompleter.isCompleted || !context.mounted) { source,
return; context,
} cancelCompleter: cancelCompleter,
onAssetDownloadProgress: (value) => progress.value = value,
)
.then((ActionResult result) {
if (cancelCompleter.isCompleted || !context.mounted) {
return;
}
ref.read(multiSelectProvider.notifier).reset(); ref.read(multiSelectProvider.notifier).reset();
if (!result.success) { if (!result.success) {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: 'scaffold_body_error_occurred'.t(context: context), msg: 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
toastType: ToastType.error, toastType: ToastType.error,
); );
} }
buildContext.pop(); buildContext.pop();
}); });
// show a loading spinner with a "Preparing" message // Show download progress with a "Preparing" message
return preparingDialog; return preparingDialog;
}, },
barrierDismissible: false, barrierDismissible: false,
@@ -77,6 +102,7 @@ class ShareActionButton extends ConsumerWidget {
if (!cancelCompleter.isCompleted) { if (!cancelCompleter.isCompleted) {
cancelCompleter.complete(); cancelCompleter.complete();
} }
progress.dispose();
}); });
} }
@@ -7,7 +7,6 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/scroll_extensions.dart'; import 'package:immich_mobile/extensions/scroll_extensions.dart';
@@ -364,8 +363,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
} }
BaseAsset displayAsset = asset; BaseAsset displayAsset = asset;
final showAssetStack = ref.watch(timelineServiceProvider.select((s) => s.origin != TimelineOrigin.trash)); final stackChildren = ref.watch(stackChildrenNotifier(asset)).valueOrNull;
final stackChildren = showAssetStack ? ref.watch(stackChildrenNotifier(asset)).valueOrNull : null;
if (stackChildren != null && stackChildren.isNotEmpty) { if (stackChildren != null && stackChildren.isNotEmpty) {
displayAsset = stackChildren.elementAt(stackIndex); displayAsset = stackChildren.elementAt(stackIndex);
} }
@@ -1,10 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
class AssetStackRow extends ConsumerWidget { class AssetStackRow extends ConsumerWidget {
final List<RemoteAsset> stack; final List<RemoteAsset> stack;
@@ -17,11 +15,6 @@ class AssetStackRow extends ConsumerWidget {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
final hideAssetStack = ref.read(timelineServiceProvider).origin == TimelineOrigin.trash;
if (hideAssetStack) {
return const SizedBox.shrink();
}
final showingControls = ref.watch(assetViewerProvider.select((s) => s.showingControls)); final showingControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
double opacity = ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0); double opacity = ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0);
@@ -148,7 +148,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
if (ref.read(assetViewerProvider).showingDetails) return; if (ref.read(assetViewerProvider).showingDetails) return;
final autoPlayVideo = AppSetting.get(Setting.autoPlayVideo); final autoPlayVideo = AppSetting.get(Setting.autoPlayVideo);
if (autoPlayVideo || widget.asset.isMotionPhoto) await _notifier.play(); if (autoPlayVideo) await _notifier.play();
} }
void _onPlaybackEnded() { void _onPlaybackEnded() {
@@ -2,21 +2,17 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/map/map.state.dart'; import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart';
class MapBottomSheet extends StatelessWidget { class MapBottomSheet extends StatelessWidget {
final Key? sheetKey; const MapBottomSheet({super.key});
const MapBottomSheet({super.key, this.sheetKey});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BaseBottomSheet( return BaseBottomSheet(
key: sheetKey,
initialChildSize: 0.25, initialChildSize: 0.25,
maxChildSize: 0.75, maxChildSize: 0.75,
shouldCloseOnMinExtent: false, shouldCloseOnMinExtent: false,
@@ -53,7 +49,7 @@ class _ScopedMapTimeline extends StatelessWidget {
return timelineService; return timelineService;
}), }),
], ],
child: const Timeline(appBar: null, bottomSheet: GeneralBottomSheet(minChildSize: 0.23), withScrubber: false), child: const Timeline(appBar: null, bottomSheet: null, withScrubber: false),
); );
} }
} }
@@ -21,7 +21,6 @@ class ThumbnailTile extends ConsumerStatefulWidget {
this.showStorageIndicator = false, this.showStorageIndicator = false,
this.lockSelection = false, this.lockSelection = false,
this.heroOffset, this.heroOffset,
this.showStackIndicator = false,
super.key, super.key,
}); });
@@ -31,7 +30,6 @@ class ThumbnailTile extends ConsumerStatefulWidget {
final bool showStorageIndicator; final bool showStorageIndicator;
final bool lockSelection; final bool lockSelection;
final int? heroOffset; final int? heroOffset;
final bool showStackIndicator;
@override @override
ConsumerState<ThumbnailTile> createState() => _ThumbnailTileState(); ConsumerState<ThumbnailTile> createState() => _ThumbnailTileState();
@@ -131,7 +129,7 @@ class _ThumbnailTileState extends ConsumerState<ThumbnailTile> {
} }
animation.addStatusListener(animationStatusListener); animation.addStatusListener(animationStatusListener);
return direction == HeroFlightDirection.push ? from.widget : to.widget; return to.widget;
}, },
), ),
), ),
@@ -141,14 +139,7 @@ class _ThumbnailTileState extends ConsumerState<ThumbnailTile> {
duration: Durations.short4, duration: Durations.short4,
child: Align( child: Align(
alignment: Alignment.topRight, alignment: Alignment.topRight,
child: Column( child: _AssetTypeIcons(asset: asset),
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
_AssetTypeIcons(asset: asset),
if (widget.showStackIndicator) _StackIndicator(asset: asset),
],
),
), ),
), ),
if (storageIndicator && asset != null) if (storageIndicator && asset != null)
@@ -295,8 +286,8 @@ class _AssetTypeIcons extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final remoteAsset = asset is RemoteAsset ? asset as RemoteAsset : null; final hasStack = asset is RemoteAsset && (asset as RemoteAsset).stackId != null;
final isLivePhoto = remoteAsset?.livePhotoVideoId != null; final isLivePhoto = asset is RemoteAsset && asset.livePhotoVideoId != null;
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -304,6 +295,11 @@ class _AssetTypeIcons extends StatelessWidget {
children: [ children: [
if (asset.isVideo) if (asset.isVideo)
Padding(padding: const EdgeInsets.only(right: 10.0, top: 6.0), child: _VideoIndicator(asset.duration)), Padding(padding: const EdgeInsets.only(right: 10.0, top: 6.0), child: _VideoIndicator(asset.duration)),
if (hasStack)
const Padding(
padding: EdgeInsets.only(right: 10.0, top: 6.0),
child: _TileOverlayIcon(Icons.burst_mode_rounded),
),
if (isLivePhoto) if (isLivePhoto)
const Padding( const Padding(
padding: EdgeInsets.only(right: 10.0, top: 6.0), padding: EdgeInsets.only(right: 10.0, top: 6.0),
@@ -316,24 +312,6 @@ class _AssetTypeIcons extends StatelessWidget {
} }
} }
class _StackIndicator extends StatelessWidget {
final BaseAsset asset;
const _StackIndicator({required this.asset});
@override
Widget build(BuildContext context) {
if (asset is! RemoteAsset || (asset as RemoteAsset).stackId == null) {
return const SizedBox.shrink();
}
return const Padding(
padding: EdgeInsets.only(right: 10.0, top: 6.0),
child: _TileOverlayIcon(Icons.burst_mode_rounded),
);
}
}
class _UploadProgressOverlay extends StatelessWidget { class _UploadProgressOverlay extends StatelessWidget {
final double progress; final double progress;

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