Compare commits
22 Commits
chore/pnpm
...
feat/inlin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
023d2195f9 | ||
|
|
c482bdfae7 | ||
|
|
0dadfc52dd | ||
|
|
df4a27e8a7 | ||
|
|
1f9813a28e | ||
|
|
bbfff45058 | ||
|
|
87dd09d103 | ||
|
|
dd94ad17aa | ||
|
|
a87c2e82cd | ||
|
|
a11ab4c3f7 | ||
|
|
ebf2f9fd7b | ||
|
|
683af67344 | ||
|
|
d149d6fa3f | ||
|
|
8c5269c002 | ||
|
|
cf91d9bdfc | ||
|
|
5579554532 | ||
|
|
7e35e6985e | ||
|
|
56756baea2 | ||
|
|
d5923241b5 | ||
|
|
cc471806fe | ||
|
|
4ce9bce414 | ||
|
|
2f5d75ce21 |
@@ -74,7 +74,7 @@ install_dependencies() {
|
||||
(
|
||||
cd "${IMMICH_WORKSPACE}" || exit 1
|
||||
export CI=1 FROZEN=1 OFFLINE=1
|
||||
run_cmd make setup-web-dev setup-server-dev
|
||||
run_cmd make setup-dev
|
||||
)
|
||||
log ""
|
||||
}
|
||||
|
||||
@@ -10,9 +10,8 @@ cd "${IMMICH_WORKSPACE}/server" || (
|
||||
exit 1
|
||||
)
|
||||
|
||||
CI=1 pnpm install
|
||||
while true; do
|
||||
run_cmd pnpm exec nest start --debug "0.0.0.0:9230" --watch
|
||||
run_cmd node ./node_modules/.bin/nest start --debug "0.0.0.0:9230" --watch
|
||||
log "Nest API Server crashed with exit code $?. Respawning in 3s ..."
|
||||
sleep 3
|
||||
done
|
||||
|
||||
@@ -16,7 +16,7 @@ until curl --output /dev/null --silent --head --fail "http://127.0.0.1:${IMMICH_
|
||||
done
|
||||
|
||||
while true; do
|
||||
run_cmd pnpm exec vite dev --host 0.0.0.0 --port "${DEV_PORT}"
|
||||
run_cmd node ./node_modules/.bin/vite dev --host 0.0.0.0 --port "${DEV_PORT}"
|
||||
log "Web crashed with exit code $?. Respawning in 3s ..."
|
||||
sleep 3
|
||||
done
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
.vscode/
|
||||
.github/
|
||||
.git/
|
||||
.env*
|
||||
*.log
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
**/Dockerfile
|
||||
**/node_modules/
|
||||
**/.pnpm-store/
|
||||
**/dist/
|
||||
**/coverage/
|
||||
**/build/
|
||||
|
||||
design/
|
||||
docker/
|
||||
Dockerfile
|
||||
!docker/scripts
|
||||
|
||||
docs/
|
||||
!docs/package.json
|
||||
!docs/package-lock.json
|
||||
|
||||
e2e/
|
||||
!e2e/package.json
|
||||
!e2e/package-lock.json
|
||||
|
||||
fastlane/
|
||||
machine-learning/
|
||||
misc/
|
||||
mobile/
|
||||
|
||||
open-api/typescript-sdk/build/
|
||||
!open-api/typescript-sdk/package.json
|
||||
!open-api/typescript-sdk/package-lock.json
|
||||
cli/coverage/
|
||||
cli/dist/
|
||||
cli/node_modules/
|
||||
cli/Dockerfile
|
||||
|
||||
open-api/typescript-sdk/build/
|
||||
open-api/typescript-sdk/node_modules/
|
||||
|
||||
server/coverage/
|
||||
server/node_modules/
|
||||
server/upload/
|
||||
server/src/queries
|
||||
server/dist/
|
||||
server/www/
|
||||
server/Dockerfile
|
||||
|
||||
web/node_modules/
|
||||
web/coverage/
|
||||
web/.svelte-kit
|
||||
web/build/
|
||||
web/.env
|
||||
web/Dockerfile
|
||||
|
||||
28
.github/package-lock.json
generated
vendored
Normal file
28
.github/package-lock.json
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": ".github",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"prettier": "^3.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
.github/workflows/cli.yml
vendored
27
.github/workflows/cli.yml
vendored
@@ -33,24 +33,21 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
# Setup .npmrc file to publish to npm
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
node-version-file: './cli/.nvmrc'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Setup typescript-sdk
|
||||
run: pnpm install && pnpm run build
|
||||
working-directory: ./open-api/typescript-sdk
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm build
|
||||
- run: pnpm publish
|
||||
- name: Prepare SDK
|
||||
run: npm ci --prefix ../open-api/typescript-sdk/
|
||||
- name: Build SDK
|
||||
run: npm run build --prefix ../open-api/typescript-sdk/
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm publish
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
uses: github/codeql-action/autobuild@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -76,6 +76,6 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
|
||||
with:
|
||||
category: '/language:${{matrix.language}}'
|
||||
|
||||
17
.github/workflows/docs-build.yml
vendored
17
.github/workflows/docs-build.yml
vendored
@@ -53,24 +53,21 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './cli/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
node-version-file: './docs/.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Run install
|
||||
run: pnpm install
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
|
||||
- name: Check formatting
|
||||
run: pnpm format
|
||||
run: npm run format
|
||||
|
||||
- name: Run build
|
||||
run: pnpm build
|
||||
run: npm run build
|
||||
|
||||
- name: Upload build output
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
|
||||
2
.github/workflows/fix-format.yml
vendored
2
.github/workflows/fix-format.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Fix formatting
|
||||
run: make install-all && make format-all
|
||||
|
||||
13
.github/workflows/sdk.yml
vendored
13
.github/workflows/sdk.yml
vendored
@@ -20,21 +20,18 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
# Setup .npmrc file to publish to npm
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './open-api/typescript-sdk/.nvmrc'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
- name: Install deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
run: npm run build
|
||||
- name: Publish
|
||||
run: pnpm publish
|
||||
run: npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
26
.github/workflows/static_analysis.yml
vendored
26
.github/workflows/static_analysis.yml
vendored
@@ -42,6 +42,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./mobile
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
@@ -56,27 +59,23 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
run: dart pub get
|
||||
working-directory: ./mobile
|
||||
|
||||
- name: Install DCM
|
||||
run: |
|
||||
sudo apt-get update
|
||||
wget -qO- https://dcm.dev/pgp-key.public | sudo gpg --dearmor -o /usr/share/keyrings/dcm.gpg
|
||||
echo 'deb [signed-by=/usr/share/keyrings/dcm.gpg arch=amd64] https://dcm.dev/debian stable main' | sudo tee /etc/apt/sources.list.d/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install dcm
|
||||
# TODO: Move to upstream after https://github.com/CQLabs/setup-dcm/pull/235 merges
|
||||
uses: bo0tzz/setup-dcm@b4952ab813659c03513b57bd78bfe3f634171f8a
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: auto
|
||||
working-directory: ./mobile
|
||||
|
||||
- name: Generate translation file
|
||||
run: make translation
|
||||
working-directory: ./mobile
|
||||
|
||||
- name: Run Build Runner
|
||||
run: make build
|
||||
working-directory: ./mobile
|
||||
|
||||
- name: Generate platform API
|
||||
run: make pigeon
|
||||
working-directory: ./mobile
|
||||
|
||||
- name: Find file changes
|
||||
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
||||
@@ -98,19 +97,16 @@ jobs:
|
||||
|
||||
- name: Run dart analyze
|
||||
run: dart analyze --fatal-infos
|
||||
working-directory: ./mobile
|
||||
|
||||
- name: Run dart format
|
||||
run: dart format lib/ --set-exit-if-changed
|
||||
working-directory: ./mobile
|
||||
|
||||
- name: Run dart custom_lint
|
||||
run: dart run custom_lint
|
||||
working-directory: ./mobile
|
||||
|
||||
# TODO: Use https://github.com/CQLabs/dcm-action
|
||||
- name: Run DCM
|
||||
run: dcm analyze lib --fatal-style --fatal-warnings
|
||||
working-directory: ./mobile
|
||||
|
||||
zizmor:
|
||||
name: zizmor
|
||||
@@ -134,7 +130,7 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
category: zizmor
|
||||
|
||||
214
.github/workflows/test.yml
vendored
214
.github/workflows/test.yml
vendored
@@ -80,33 +80,30 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Run package manager install
|
||||
run: pnpm install
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
|
||||
- name: Run linter
|
||||
run: pnpm lint
|
||||
run: npm run lint
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run formatter
|
||||
run: pnpm format
|
||||
run: npm run format
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run tsc
|
||||
run: pnpm check
|
||||
run: npm run check
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run small tests & coverage
|
||||
run: pnpm test
|
||||
run: npm test
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
cli-unit-tests:
|
||||
@@ -126,37 +123,34 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
node-version-file: './cli/.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Setup typescript-sdk
|
||||
run: pnpm install && pnpm run build
|
||||
run: npm ci && npm run build
|
||||
working-directory: ./open-api/typescript-sdk
|
||||
|
||||
- name: Install deps
|
||||
run: pnpm install
|
||||
run: npm ci
|
||||
|
||||
- name: Run linter
|
||||
run: pnpm lint
|
||||
run: npm run lint
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run formatter
|
||||
run: pnpm format
|
||||
run: npm run format
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run tsc
|
||||
run: pnpm check
|
||||
run: npm run check
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run unit tests & coverage
|
||||
run: pnpm test
|
||||
run: npm run test
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
cli-unit-tests-win:
|
||||
@@ -176,30 +170,27 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
node-version-file: './cli/.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Setup typescript-sdk
|
||||
run: pnpm install --frozen-lockfile && pnpm build
|
||||
run: npm ci && npm run build
|
||||
working-directory: ./open-api/typescript-sdk
|
||||
|
||||
- name: Install deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
|
||||
# Skip linter & formatter in Windows test.
|
||||
- name: Run tsc
|
||||
run: pnpm check
|
||||
run: npm run check
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run unit tests & coverage
|
||||
run: pnpm test
|
||||
run: npm run test
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
web-lint:
|
||||
@@ -219,33 +210,30 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
node-version-file: './web/.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Run setup typescript-sdk
|
||||
run: pnpm install --frozen-lockfile && pnpm build
|
||||
run: npm ci && npm run build
|
||||
working-directory: ./open-api/typescript-sdk
|
||||
|
||||
- name: Run npm install
|
||||
run: pnpm rebuild && pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
|
||||
- name: Run linter
|
||||
run: pnpm lint:p
|
||||
run: npm run lint:p
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run formatter
|
||||
run: pnpm format
|
||||
run: npm run format
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run svelte checks
|
||||
run: pnpm check:svelte
|
||||
run: npm run check:svelte
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
web-unit-tests:
|
||||
@@ -265,29 +253,26 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
node-version-file: './web/.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Run setup typescript-sdk
|
||||
run: pnpm install --frozen-lockfile && pnpm build
|
||||
run: npm ci && npm run build
|
||||
working-directory: ./open-api/typescript-sdk
|
||||
|
||||
- name: Run npm install
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
|
||||
- name: Run tsc
|
||||
run: pnpm check:typescript
|
||||
run: npm run check:typescript
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run unit tests & coverage
|
||||
run: pnpm test
|
||||
run: npm run test
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
i18n-tests:
|
||||
@@ -303,21 +288,18 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
node-version-file: './web/.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm --filter=immich-web install --frozen-lockfile
|
||||
run: npm --prefix=web ci
|
||||
|
||||
- name: Format
|
||||
run: pnpm --filter=immich-web format:i18n
|
||||
run: npm --prefix=web run format:i18n
|
||||
|
||||
- name: Find file changes
|
||||
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
||||
@@ -352,35 +334,32 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
node-version-file: './e2e/.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Run setup typescript-sdk
|
||||
run: pnpm install --frozen-lockfile && pnpm build
|
||||
run: npm ci && npm run build
|
||||
working-directory: ./open-api/typescript-sdk
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run linter
|
||||
run: pnpm lint
|
||||
run: npm run lint
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run formatter
|
||||
run: pnpm format
|
||||
run: npm run format
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run tsc
|
||||
run: pnpm check
|
||||
run: npm run check
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
server-medium-tests:
|
||||
@@ -400,21 +379,18 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Run npm install
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
|
||||
- name: Run medium tests
|
||||
run: pnpm test:medium
|
||||
run: npm run test:medium
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
e2e-tests-server-cli:
|
||||
@@ -438,33 +414,25 @@ jobs:
|
||||
persist-credentials: false
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
node-version-file: './e2e/.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Run setup typescript-sdk
|
||||
run: pnpm install --frozen-lockfile && pnpm build
|
||||
run: npm ci && npm run build
|
||||
working-directory: ./open-api/typescript-sdk
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run setup web
|
||||
run: pnpm install --frozen-lockfile && pnpm exec svelte-kit sync
|
||||
working-directory: ./web
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run setup cli
|
||||
run: pnpm install --frozen-lockfile && pnpm build
|
||||
run: npm ci && npm run build
|
||||
working-directory: ./cli
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Docker build
|
||||
@@ -472,7 +440,7 @@ jobs:
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run e2e tests (api & cli)
|
||||
run: pnpm test
|
||||
run: npm run test
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
e2e-tests-web:
|
||||
@@ -496,23 +464,20 @@ jobs:
|
||||
persist-credentials: false
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
node-version-file: './e2e/.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Run setup typescript-sdk
|
||||
run: pnpm install --frozen-lockfile && pnpm build
|
||||
run: npm ci && npm run build
|
||||
working-directory: ./open-api/typescript-sdk
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
@@ -619,21 +584,18 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
node-version-file: './.github/.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Run npm install
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
|
||||
- name: Run formatter
|
||||
run: pnpm format
|
||||
run: npm run format
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
shellcheck:
|
||||
@@ -665,21 +627,18 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Install server dependencies
|
||||
run: pnpm --filter immich install --frozen-lockfile
|
||||
run: npm --prefix=server ci
|
||||
|
||||
- name: Build the app
|
||||
run: pnpm --filter immich build
|
||||
run: npm --prefix=server run build
|
||||
|
||||
- name: Run API generation
|
||||
run: make open-api
|
||||
@@ -731,31 +690,28 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
- name: Install server dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
|
||||
- name: Build the app
|
||||
run: pnpm build
|
||||
run: npm run build
|
||||
|
||||
- name: Run existing migrations
|
||||
run: pnpm migrations:run
|
||||
run: npm run migrations:run
|
||||
|
||||
- name: Test npm run schema:reset command works
|
||||
run: pnpm schema:reset
|
||||
run: npm run schema:reset
|
||||
|
||||
- name: Generate new migrations
|
||||
continue-on-error: true
|
||||
run: pnpm migrations:generate src/TestMigration
|
||||
run: npm run migrations:generate src/TestMigration
|
||||
|
||||
- name: Find file changes
|
||||
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
||||
@@ -774,7 +730,7 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Run SQL generation
|
||||
run: pnpm sync:sql
|
||||
run: npm run sync:sql
|
||||
env:
|
||||
DB_URL: postgres://postgres:postgres@localhost:5432/immich
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,4 +24,3 @@ mobile/android/fastlane/report.xml
|
||||
mobile/ios/fastlane/report.xml
|
||||
|
||||
vite.config.js.timestamp-*
|
||||
.pnpm-store
|
||||
|
||||
71
Makefile
71
Makefile
@@ -40,7 +40,7 @@ open-api-typescript:
|
||||
cd ./open-api && bash ./bin/generate-open-api.sh typescript
|
||||
|
||||
sql:
|
||||
pnpm --filter immich run sync:sql
|
||||
npm --prefix server run sync:sql
|
||||
|
||||
attach-server:
|
||||
docker exec -it docker_immich-server_1 sh
|
||||
@@ -50,40 +50,31 @@ renovate:
|
||||
|
||||
MODULES = e2e server web cli sdk docs .github
|
||||
|
||||
# directory to package name mapping function
|
||||
# cli = @immich/cli
|
||||
# docs = documentation
|
||||
# e2e = immich-e2e
|
||||
# open-api/typescript-sdk = @immich/sdk
|
||||
# server = immich
|
||||
# web = immich-web
|
||||
map-package = $(subst sdk,@immich/sdk,$(subst cli,@immich/cli,$(subst docs,documentation,$(subst e2e,immich-e2e,$(subst server,immich,$(subst web,immich-web,$1))))))
|
||||
|
||||
audit-%:
|
||||
pnpm --filter $(call map-package,$*) audit fix
|
||||
npm --prefix $(subst sdk,open-api/typescript-sdk,$*) audit fix
|
||||
install-%:
|
||||
pnpm --filter $(call map-package,$*) install $(if $(FROZEN),--frozen-lockfile) $(if $(OFFLINE),--offline)
|
||||
npm --prefix $(subst sdk,open-api/typescript-sdk,$*) i
|
||||
ci-%:
|
||||
npm --prefix $(subst sdk,open-api/typescript-sdk,$*) ci
|
||||
build-cli: build-sdk
|
||||
build-web: build-sdk
|
||||
build-%: install-%
|
||||
pnpm --filter $(call map-package,$*) run build
|
||||
npm --prefix $(subst sdk,open-api/typescript-sdk,$*) run build
|
||||
format-%:
|
||||
pnpm --filter $(call map-package,$*) run format:fix
|
||||
npm --prefix $* run format:fix
|
||||
lint-%:
|
||||
pnpm --filter $(call map-package,$*) run lint:fix
|
||||
lint-web:
|
||||
pnpm --filter $(call map-package,$*) run lint:p
|
||||
npm --prefix $* run lint:fix
|
||||
check-%:
|
||||
pnpm --filter $(call map-package,$*) run check
|
||||
npm --prefix $* run check
|
||||
check-web:
|
||||
pnpm --filter immich-web run check:typescript
|
||||
pnpm --filter immich-web run check:svelte
|
||||
npm --prefix web run check:typescript
|
||||
npm --prefix web run check:svelte
|
||||
test-%:
|
||||
pnpm --filter $(call map-package,$*) run test
|
||||
npm --prefix $* run test
|
||||
test-e2e:
|
||||
docker compose -f ./e2e/docker-compose.yml build
|
||||
pnpm --filter immich-e2e run test
|
||||
pnpm --filter immich-e2e run test:web
|
||||
npm --prefix e2e run test
|
||||
npm --prefix e2e run test:web
|
||||
test-medium:
|
||||
docker run \
|
||||
--rm \
|
||||
@@ -93,28 +84,19 @@ test-medium:
|
||||
-v ./server/tsconfig.json:/usr/src/app/tsconfig.json \
|
||||
-e NODE_ENV=development \
|
||||
immich-server:latest \
|
||||
-c "pnpm test:medium -- --run"
|
||||
-c "npm ci && npm run test:medium -- --run"
|
||||
test-medium-dev:
|
||||
docker exec -it immich_server /bin/sh -c "pnpm run test:medium"
|
||||
docker exec -it immich_server /bin/sh -c "npm run test:medium"
|
||||
|
||||
install-all:
|
||||
pnpm -r --filter '!documentation' install
|
||||
|
||||
build-all: $(foreach M,$(filter-out e2e docs .github,$(MODULES)),build-$M) ;
|
||||
|
||||
check-all:
|
||||
pnpm -r --filter '!documentation' run "/^(check|check\:svelte|check\:typescript)$/"
|
||||
lint-all:
|
||||
pnpm -r --filter '!documentation' run lint:fix
|
||||
format-all:
|
||||
pnpm -r --filter '!documentation' run format:fix
|
||||
audit-all:
|
||||
pnpm -r --filter '!documentation' audit fix
|
||||
hygiene-all: audit-all
|
||||
pnpm -r --filter '!documentation' run "/(format:fix|check|check:svelte|check:typescript|sql)/"
|
||||
|
||||
test-all:
|
||||
pnpm -r --filter '!documentation' run "/^test/"
|
||||
build-all: $(foreach M,$(filter-out e2e .github,$(MODULES)),build-$M) ;
|
||||
install-all: $(foreach M,$(MODULES),install-$M) ;
|
||||
ci-all: $(foreach M,$(filter-out .github,$(MODULES)),ci-$M) ;
|
||||
check-all: $(foreach M,$(filter-out sdk cli docs .github,$(MODULES)),check-$M) ;
|
||||
lint-all: $(foreach M,$(filter-out sdk docs .github,$(MODULES)),lint-$M) ;
|
||||
format-all: $(foreach M,$(filter-out sdk,$(MODULES)),format-$M) ;
|
||||
audit-all: $(foreach M,$(MODULES),audit-$M) ;
|
||||
hygiene-all: lint-all format-all check-all sql audit-all;
|
||||
test-all: $(foreach M,$(filter-out sdk docs .github,$(MODULES)),test-$M) ;
|
||||
|
||||
clean:
|
||||
find . -name "node_modules" -type d -prune -exec rm -rf {} +
|
||||
@@ -124,5 +106,4 @@ clean:
|
||||
command -v docker >/dev/null 2>&1 && docker compose -f ./docker/docker-compose.dev.yml rm -v -f || true
|
||||
command -v docker >/dev/null 2>&1 && docker compose -f ./e2e/docker-compose.yml rm -v -f || true
|
||||
|
||||
setup-server-dev: install-server
|
||||
setup-web-dev: install-sdk build-sdk install-web
|
||||
setup-dev: install-server install-sdk build-sdk install-web
|
||||
@@ -6,10 +6,8 @@ Please see the [Immich CLI documentation](https://immich.app/docs/features/comma
|
||||
|
||||
Before building the CLI, you must build the immich server and the open-api client. To build the server run the following in the server folder:
|
||||
|
||||
# if you don't have node installed
|
||||
$ npm install -g pnpm
|
||||
$ pnpm install
|
||||
$ pnpm build
|
||||
$ npm install
|
||||
$ npm run build
|
||||
|
||||
Then, to build the open-api client run the following in the open-api folder:
|
||||
|
||||
@@ -17,10 +15,8 @@ Then, to build the open-api client run the following in the open-api folder:
|
||||
|
||||
To run the Immich CLI from source, run the following in the cli folder:
|
||||
|
||||
# if you don't have node installed
|
||||
$ npm install -g pnpm
|
||||
$ pnpm install
|
||||
$ pnpm build
|
||||
$ npm install
|
||||
$ npm run build
|
||||
$ ts-node .
|
||||
|
||||
You'll need ts-node, the easiest way to install it is to use npm:
|
||||
|
||||
4617
cli/package-lock.json
generated
Normal file
4617
cli/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ name: immich-dev
|
||||
services:
|
||||
immich-server:
|
||||
container_name: immich_server
|
||||
command: ['/usr/src/app/server/bin/immich-dev']
|
||||
command: ['/usr/src/app/bin/immich-dev']
|
||||
image: immich-server-dev:latest
|
||||
# extends:
|
||||
# file: hwaccel.transcoding.yml
|
||||
@@ -27,18 +27,14 @@ services:
|
||||
target: dev
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ../Makefile:/usr/src/app/Makefile
|
||||
- ../package.json:/usr/src/app/package.json
|
||||
- ../pnpm-lock.yaml:/usr/src/app/pnpm-lock.yaml
|
||||
- ../pnpm-workspace.yaml:/usr/src/app/pnpm-workspace.yaml
|
||||
- ../server:/usr/src/app/server
|
||||
- ../open-api:/usr/src/app/open-api
|
||||
- ${UPLOAD_LOCATION}/photos:/usr/src/app/server/upload
|
||||
- ${UPLOAD_LOCATION}/photos/upload:/usr/src/app/server/upload/upload
|
||||
- ../server:/usr/src/app
|
||||
- ../open-api:/usr/src/open-api
|
||||
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
|
||||
- ${UPLOAD_LOCATION}/photos/upload:/usr/src/app/upload/upload
|
||||
- /usr/src/app/node_modules
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
env_file:
|
||||
- .env
|
||||
# user: ${UID:-1000}:${GID:-1000}
|
||||
environment:
|
||||
IMMICH_REPOSITORY: immich-app/immich
|
||||
IMMICH_REPOSITORY_URL: https://github.com/immich-app/immich
|
||||
@@ -72,25 +68,20 @@ services:
|
||||
image: immich-web-dev:latest
|
||||
# Needed for rootless docker setup, see https://github.com/moby/moby/issues/45919
|
||||
# user: 0:0
|
||||
# user: ${UID:-1000}:${GID:-1000}
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: web/Dockerfile
|
||||
command: ['/usr/src/app/web/bin/immich-web']
|
||||
context: ../web
|
||||
command: ['/usr/src/app/bin/immich-web']
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 3000:3000
|
||||
- 24678:24678
|
||||
volumes:
|
||||
- ../Makefile:/usr/src/app/Makefile
|
||||
- ../package.json:/usr/src/app/package.json
|
||||
- ../pnpm-lock.yaml:/usr/src/app/pnpm-lock.yaml
|
||||
- ../pnpm-workspace.yaml:/usr/src/app/pnpm-workspace.yaml
|
||||
- ../web:/usr/src/app/web
|
||||
- ../i18n:/usr/src/app/i18n
|
||||
- ../open-api:/usr/src/app/open-api
|
||||
- ../web:/usr/src/app
|
||||
- ../i18n:/usr/src/i18n
|
||||
- ../open-api/:/usr/src/open-api/
|
||||
# - ../../ui:/usr/ui
|
||||
- /usr/src/app/node_modules
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 1048576
|
||||
|
||||
@@ -1,7 +1,2 @@
|
||||
build/
|
||||
.docusaurus/
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
@@ -5,7 +5,7 @@ This website is built using [Docusaurus](https://docusaurus.io/), a modern stati
|
||||
### Installation
|
||||
|
||||
```
|
||||
$ pnpm install
|
||||
$ npm install
|
||||
```
|
||||
|
||||
### Local Development
|
||||
|
||||
@@ -62,6 +62,7 @@ Once you have a new OAuth client application configured, Immich can be configure
|
||||
| Scope | string | openid email profile | Full list of scopes to send with the request (space delimited) |
|
||||
| Signing Algorithm | string | RS256 | The algorithm used to sign the id token (examples: RS256, HS256) |
|
||||
| Storage Label Claim | string | preferred_username | Claim mapping for the user's storage label**¹** |
|
||||
| Role Claim | string | immich_role | Claim mapping for the user's role. (should return "user" or "admin")**¹** |
|
||||
| Storage Quota Claim | string | immich_quota | Claim mapping for the user's storage**¹** |
|
||||
| Default Storage Quota (GiB) | number | 0 | Default quota for user without storage quota claim (Enter 0 for unlimited quota) |
|
||||
| Button Text | string | Login with OAuth | Text for the OAuth button on the web |
|
||||
|
||||
@@ -200,7 +200,7 @@ When the Dev Container starts, it automatically:
|
||||
|
||||
1. **Runs post-create script** (`container-server-post-create.sh`):
|
||||
- Adjusts file permissions for the `node` user
|
||||
- Installs dependencies: `pnpm install` in all packages
|
||||
- Installs dependencies: `npm install` in all packages
|
||||
- Builds TypeScript SDK: `npm run build` in `open-api/typescript-sdk`
|
||||
|
||||
2. **Starts development servers** via VS Code tasks:
|
||||
|
||||
@@ -56,7 +56,7 @@ If you only want to do web development connected to an existing, remote backend,
|
||||
|
||||
1. Build the Immich SDK - `cd open-api/typescript-sdk && npm i && npm run build && cd -`
|
||||
2. Enter the web directory - `cd web/`
|
||||
3. Install web dependencies - `pnpm i`
|
||||
3. Install web dependencies - `npm i`
|
||||
4. Start the web development server
|
||||
|
||||
```bash
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
### Unit tests
|
||||
|
||||
Unit are run by calling `npm run test` from the `server/` directory.
|
||||
You need to run `pnpm install` (in `server/`) before _once_.
|
||||
You need to run `npm install` (in `server/`) before _once_.
|
||||
|
||||
### End to end tests
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ For the full list, refer to the [Immich source code](https://github.com/immich-a
|
||||
| `HEIC` | `.heic` | :white_check_mark: | |
|
||||
| `HEIF` | `.heif` | :white_check_mark: | |
|
||||
| `JPEG 2000` | `.jp2` | :white_check_mark: | |
|
||||
| `JPEG` | `.webp` `.jpg` `.jpe` `.insp` | :white_check_mark: | |
|
||||
| `JPEG` | `.jpeg` `.jpg` `.jpe` `.insp` | :white_check_mark: | |
|
||||
| `JPEG XL` | `.jxl` | :white_check_mark: | |
|
||||
| `PNG` | `.png` | :white_check_mark: | |
|
||||
| `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop |
|
||||
|
||||
20545
docs/package-lock.json
generated
Normal file
20545
docs/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -36,7 +36,7 @@ services:
|
||||
- 2285:2285
|
||||
|
||||
redis:
|
||||
image: redis:6.2-alpine@sha256:3211c33a618c457e5d241922c975dbc4f446d0bdb2dc75694f5573ef8e2d01fa
|
||||
image: redis:6.2-alpine@sha256:03fd052257735b41cd19f3d8ae9782926bf9b704fb6a9dc5e29f9ccfbe8827f0
|
||||
|
||||
database:
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:3aef84a0a4fabbda17ef115c3019ba0c914ec73e9f6e59203674322d858b8eea
|
||||
|
||||
7409
e2e/package-lock.json
generated
Normal file
7409
e2e/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -227,6 +227,21 @@ describe(`/oauth`, () => {
|
||||
expect(user.storageLabel).toBe('user-username');
|
||||
});
|
||||
|
||||
it('should set the admin status from a role claim', async () => {
|
||||
const callbackParams = await loginWithOAuth(OAuthUser.WITH_ROLE);
|
||||
const { status, body } = await request(app).post('/oauth/callback').send(callbackParams);
|
||||
expect(status).toBe(201);
|
||||
expect(body).toMatchObject({
|
||||
accessToken: expect.any(String),
|
||||
userId: expect.any(String),
|
||||
userEmail: 'oauth-with-role@immich.app',
|
||||
isAdmin: true,
|
||||
});
|
||||
|
||||
const user = await getMyUser({ headers: asBearerAuth(body.accessToken) });
|
||||
expect(user.isAdmin).toBe(true);
|
||||
});
|
||||
|
||||
it('should work with RS256 signed tokens', async () => {
|
||||
await setupOAuth(admin.accessToken, {
|
||||
enabled: true,
|
||||
|
||||
@@ -12,6 +12,7 @@ export enum OAuthUser {
|
||||
NO_NAME = 'no-name',
|
||||
WITH_QUOTA = 'with-quota',
|
||||
WITH_USERNAME = 'with-username',
|
||||
WITH_ROLE = 'with-role',
|
||||
}
|
||||
|
||||
const claims = [
|
||||
@@ -34,6 +35,12 @@ const claims = [
|
||||
preferred_username: 'user-quota',
|
||||
immich_quota: 25,
|
||||
},
|
||||
{
|
||||
sub: OAuthUser.WITH_ROLE,
|
||||
email: 'oauth-with-role@immich.app',
|
||||
email_verified: true,
|
||||
immich_role: 'admin',
|
||||
},
|
||||
];
|
||||
|
||||
const withDefaultClaims = (sub: string) => ({
|
||||
@@ -64,7 +71,15 @@ const setup = async () => {
|
||||
claims: {
|
||||
openid: ['sub'],
|
||||
email: ['email', 'email_verified'],
|
||||
profile: ['name', 'given_name', 'family_name', 'preferred_username', 'immich_quota', 'immich_username'],
|
||||
profile: [
|
||||
'name',
|
||||
'given_name',
|
||||
'family_name',
|
||||
'preferred_username',
|
||||
'immich_quota',
|
||||
'immich_username',
|
||||
'immich_role',
|
||||
],
|
||||
},
|
||||
features: {
|
||||
jwtUserinfo: {
|
||||
|
||||
@@ -79,7 +79,7 @@ export const tempDir = tmpdir();
|
||||
export const asBearerAuth = (accessToken: string) => ({ Authorization: `Bearer ${accessToken}` });
|
||||
export const asKeyAuth = (key: string) => ({ 'x-api-key': key });
|
||||
export const immichCli = (args: string[]) =>
|
||||
executeCommand('pnpm', ['exec', 'immich', '-d', `/${tempDir}/immich/`, ...args], { cwd: '../cli' }).promise;
|
||||
executeCommand('node', ['node_modules/.bin/immich', '-d', `/${tempDir}/immich/`, ...args]).promise;
|
||||
export const immichAdmin = (args: string[]) =>
|
||||
executeCommand('docker', ['exec', '-i', 'immich-e2e-server', '/bin/bash', '-c', `immich-admin ${args.join(' ')}`]);
|
||||
export const specialCharStrings = ["'", '"', ',', '{', '}', '*'];
|
||||
|
||||
@@ -196,6 +196,8 @@
|
||||
"oauth_mobile_redirect_uri": "Mobile redirect URI",
|
||||
"oauth_mobile_redirect_uri_override": "Mobile redirect URI override",
|
||||
"oauth_mobile_redirect_uri_override_description": "Enable when OAuth provider does not allow a mobile URI, like ''{callback}''",
|
||||
"oauth_role_claim": "Role Claim",
|
||||
"oauth_role_claim_description": "Automatically grant admin access based on the presence of this claim. The claim may have either 'user' or 'admin'.",
|
||||
"oauth_settings": "OAuth",
|
||||
"oauth_settings_description": "Manage OAuth login settings",
|
||||
"oauth_settings_more_details": "For more details about this feature, refer to the <link>docs</link>.",
|
||||
|
||||
1332
mobile/drift_schemas/main/drift_schema_v1.json
generated
1332
mobile/drift_schemas/main/drift_schema_v1.json
generated
File diff suppressed because one or more lines are too long
@@ -18,7 +18,7 @@ const String kSecuredPinCode = "secured_pin_code";
|
||||
|
||||
// Timeline constants
|
||||
const int kTimelineNoneSegmentSize = 120;
|
||||
const int kTimelineAssetLoadBatchSize = 256;
|
||||
const int kTimelineAssetLoadBatchSize = 1024;
|
||||
const int kTimelineAssetLoadOppositeSize = 64;
|
||||
|
||||
// Widget keys
|
||||
|
||||
84
mobile/lib/domain/models/stack.model.dart
Normal file
84
mobile/lib/domain/models/stack.model.dart
Normal file
@@ -0,0 +1,84 @@
|
||||
import 'dart:convert';
|
||||
|
||||
// Model for a stack stored in the server
|
||||
class Stack {
|
||||
final String id;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final String ownerId;
|
||||
final String primaryAssetId;
|
||||
|
||||
const Stack({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.ownerId,
|
||||
required this.primaryAssetId,
|
||||
});
|
||||
|
||||
Stack copyWith({
|
||||
String? id,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
String? ownerId,
|
||||
String? primaryAssetId,
|
||||
}) {
|
||||
return Stack(
|
||||
id: id ?? this.id,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
ownerId: ownerId ?? this.ownerId,
|
||||
primaryAssetId: primaryAssetId ?? this.primaryAssetId,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return <String, dynamic>{
|
||||
'id': id,
|
||||
'createdAt': createdAt.millisecondsSinceEpoch,
|
||||
'updatedAt': updatedAt.millisecondsSinceEpoch,
|
||||
'ownerId': ownerId,
|
||||
'primaryAssetId': primaryAssetId,
|
||||
};
|
||||
}
|
||||
|
||||
factory Stack.fromMap(Map<String, dynamic> map) {
|
||||
return Stack(
|
||||
id: map['id'] as String,
|
||||
createdAt: DateTime.fromMillisecondsSinceEpoch(map['createdAt'] as int),
|
||||
updatedAt: DateTime.fromMillisecondsSinceEpoch(map['updatedAt'] as int),
|
||||
ownerId: map['ownerId'] as String,
|
||||
primaryAssetId: map['primaryAssetId'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Stack.fromJson(String source) =>
|
||||
Stack.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Stack(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, ownerId: $ownerId, primaryAssetId: $primaryAssetId)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(covariant Stack other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other.id == id &&
|
||||
other.createdAt == createdAt &&
|
||||
other.updatedAt == updatedAt &&
|
||||
other.ownerId == ownerId &&
|
||||
other.primaryAssetId == primaryAssetId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode ^
|
||||
createdAt.hashCode ^
|
||||
updatedAt.hashCode ^
|
||||
ownerId.hashCode ^
|
||||
primaryAssetId.hashCode;
|
||||
}
|
||||
}
|
||||
@@ -154,6 +154,25 @@ class SyncStreamService {
|
||||
return _syncStreamRepository.updateMemoryAssetsV1(data.cast());
|
||||
case SyncEntityType.memoryToAssetDeleteV1:
|
||||
return _syncStreamRepository.deleteMemoryAssetsV1(data.cast());
|
||||
case SyncEntityType.stackV1:
|
||||
return _syncStreamRepository.updateStacksV1(data.cast());
|
||||
case SyncEntityType.stackDeleteV1:
|
||||
return _syncStreamRepository.deleteStacksV1(data.cast());
|
||||
case SyncEntityType.partnerStackV1:
|
||||
return _syncStreamRepository.updateStacksV1(
|
||||
data.cast(),
|
||||
debugLabel: 'partner',
|
||||
);
|
||||
case SyncEntityType.partnerStackBackfillV1:
|
||||
return _syncStreamRepository.updateStacksV1(
|
||||
data.cast(),
|
||||
debugLabel: 'partner backfill',
|
||||
);
|
||||
case SyncEntityType.partnerStackDeleteV1:
|
||||
return _syncStreamRepository.deleteStacksV1(
|
||||
data.cast(),
|
||||
debugLabel: 'partner',
|
||||
);
|
||||
default:
|
||||
_logger.warning("Unknown sync data type: $type");
|
||||
}
|
||||
|
||||
@@ -42,16 +42,66 @@ class TimelineFactory {
|
||||
|
||||
TimelineService localAlbum({required String albumId}) => TimelineService(
|
||||
assetSource: (offset, count) => _timelineRepository
|
||||
.getLocalBucketAssets(albumId, offset: offset, count: count),
|
||||
bucketSource: () =>
|
||||
_timelineRepository.watchLocalBucket(albumId, groupBy: groupBy),
|
||||
.getLocalAlbumBucketAssets(albumId, offset: offset, count: count),
|
||||
bucketSource: () => _timelineRepository.watchLocalAlbumBucket(
|
||||
albumId,
|
||||
groupBy: groupBy,
|
||||
),
|
||||
);
|
||||
|
||||
TimelineService remoteAlbum({required String albumId}) => TimelineService(
|
||||
assetSource: (offset, count) => _timelineRepository
|
||||
.getRemoteBucketAssets(albumId, offset: offset, count: count),
|
||||
.getRemoteAlbumBucketAssets(albumId, offset: offset, count: count),
|
||||
bucketSource: () => _timelineRepository.watchRemoteAlbumBucket(
|
||||
albumId,
|
||||
groupBy: groupBy,
|
||||
),
|
||||
);
|
||||
|
||||
TimelineService remoteAssets(List<String> timelineUsers) => TimelineService(
|
||||
assetSource: (offset, count) => _timelineRepository
|
||||
.getRemoteBucketAssets(timelineUsers, offset: offset, count: count),
|
||||
bucketSource: () => _timelineRepository.watchRemoteBucket(
|
||||
timelineUsers,
|
||||
groupBy: GroupAssetsBy.month,
|
||||
),
|
||||
);
|
||||
|
||||
TimelineService favorite(String userId) => TimelineService(
|
||||
assetSource: (offset, count) => _timelineRepository
|
||||
.getFavoriteBucketAssets(userId, offset: offset, count: count),
|
||||
bucketSource: () =>
|
||||
_timelineRepository.watchRemoteBucket(albumId, groupBy: groupBy),
|
||||
_timelineRepository.watchFavoriteBucket(userId, groupBy: groupBy),
|
||||
);
|
||||
|
||||
TimelineService trash(String userId) => TimelineService(
|
||||
assetSource: (offset, count) => _timelineRepository
|
||||
.getTrashBucketAssets(userId, offset: offset, count: count),
|
||||
bucketSource: () =>
|
||||
_timelineRepository.watchTrashBucket(userId, groupBy: groupBy),
|
||||
);
|
||||
|
||||
TimelineService archive(String userId) => TimelineService(
|
||||
assetSource: (offset, count) => _timelineRepository
|
||||
.getArchiveBucketAssets(userId, offset: offset, count: count),
|
||||
bucketSource: () =>
|
||||
_timelineRepository.watchArchiveBucket(userId, groupBy: groupBy),
|
||||
);
|
||||
|
||||
TimelineService lockedFolder(String userId) => TimelineService(
|
||||
assetSource: (offset, count) => _timelineRepository
|
||||
.getLockedFolderBucketAssets(userId, offset: offset, count: count),
|
||||
bucketSource: () => _timelineRepository.watchLockedFolderBucket(
|
||||
userId,
|
||||
groupBy: groupBy,
|
||||
),
|
||||
);
|
||||
|
||||
TimelineService video(String userId) => TimelineService(
|
||||
assetSource: (offset, count) => _timelineRepository
|
||||
.getVideoBucketAssets(userId, offset: offset, count: count),
|
||||
bucketSource: () =>
|
||||
_timelineRepository.watchVideoBucket(userId, groupBy: groupBy),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
58
mobile/lib/infrastructure/entities/asset_triggers.drift
Normal file
58
mobile/lib/infrastructure/entities/asset_triggers.drift
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'remote_asset.entity.dart';
|
||||
import 'local_asset.entity.dart';
|
||||
|
||||
-- TRIGGERS ON local_asset_entity
|
||||
|
||||
-- Find and update the remote_id in local_asset_entity and local_id in remote_asset_entity when checksum is set
|
||||
CREATE TRIGGER IF NOT EXISTS tr_local_asset_update_checksum_set_ids
|
||||
AFTER UPDATE OF checksum ON local_asset_entity
|
||||
FOR EACH ROW
|
||||
WHEN NEW.checksum IS NOT NULL
|
||||
BEGIN
|
||||
UPDATE local_asset_entity
|
||||
SET remote_id = (SELECT id FROM remote_asset_entity WHERE checksum = NEW.checksum LIMIT 1)
|
||||
WHERE id = NEW.id;
|
||||
|
||||
UPDATE remote_asset_entity
|
||||
SET local_id = (SELECT id FROM local_asset_entity WHERE checksum = NEW.checksum ORDER BY id ASC LIMIT 1)
|
||||
WHERE checksum = NEW.checksum;
|
||||
END;
|
||||
|
||||
-- When a local asset is updated, relink remote assets that had a checksum match
|
||||
CREATE TRIGGER IF NOT EXISTS tr_local_asset_update_old_checksum_set_remote_asset_local_id
|
||||
AFTER UPDATE OF checksum ON local_asset_entity
|
||||
FOR EACH ROW
|
||||
WHEN OLD.checksum IS NOT NULL
|
||||
BEGIN
|
||||
UPDATE remote_asset_entity
|
||||
SET local_id = (SELECT id FROM local_asset_entity WHERE checksum = OLD.checksum ORDER BY id ASC LIMIT 1)
|
||||
WHERE checksum = OLD.checksum;
|
||||
END;
|
||||
|
||||
-- remote_asset_entity.checksum is a 1..* relationship with local_asset_entity.checksum.
|
||||
-- When a local asset is deleted, update remote assets that had a checksum match
|
||||
-- to ensure their local_id is set to the first matching local asset or NULL
|
||||
CREATE TRIGGER IF NOT EXISTS tr_local_asset_delete_update_remote_asset_local_id
|
||||
AFTER DELETE ON local_asset_entity
|
||||
FOR EACH ROW
|
||||
WHEN OLD.checksum IS NOT NULL
|
||||
BEGIN
|
||||
UPDATE remote_asset_entity
|
||||
SET local_id = (SELECT id FROM local_asset_entity WHERE checksum = OLD.checksum ORDER BY id ASC LIMIT 1)
|
||||
WHERE checksum = OLD.checksum;
|
||||
END;
|
||||
|
||||
|
||||
-- TRIGGERS ON remote_asset_entity
|
||||
|
||||
-- Find and update local_id in remote_asset_entity when a new remote asset is inserted
|
||||
CREATE TRIGGER IF NOT EXISTS tr_remote_asset_insert_set_local_id
|
||||
AFTER INSERT ON remote_asset_entity
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE remote_asset_entity
|
||||
SET local_id = (SELECT id FROM local_asset_entity WHERE checksum = NEW.checksum ORDER BY id ASC LIMIT 1)
|
||||
WHERE id = NEW.id;
|
||||
|
||||
UPDATE local_asset_entity SET remote_id = NEW.id WHERE checksum = NEW.checksum;
|
||||
END;
|
||||
16
mobile/lib/infrastructure/entities/asset_triggers.drift.dart
generated
Normal file
16
mobile/lib/infrastructure/entities/asset_triggers.drift.dart
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
// dart format width=80
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
|
||||
i0.Trigger get trLocalAssetUpdateChecksumSetIds => i0.Trigger(
|
||||
'CREATE TRIGGER IF NOT EXISTS tr_local_asset_update_checksum_set_ids AFTER UPDATE OF checksum ON local_asset_entity WHEN NEW.checksum IS NOT NULL BEGIN UPDATE local_asset_entity SET remote_id = (SELECT id FROM remote_asset_entity WHERE checksum = NEW.checksum LIMIT 1) WHERE id = NEW.id;UPDATE remote_asset_entity SET local_id = (SELECT id FROM local_asset_entity WHERE checksum = NEW.checksum ORDER BY id ASC LIMIT 1) WHERE checksum = NEW.checksum;END',
|
||||
'tr_local_asset_update_checksum_set_ids');
|
||||
i0.Trigger get trLocalAssetUpdateOldChecksumSetRemoteAssetLocalId => i0.Trigger(
|
||||
'CREATE TRIGGER IF NOT EXISTS tr_local_asset_update_old_checksum_set_remote_asset_local_id AFTER UPDATE OF checksum ON local_asset_entity WHEN OLD.checksum IS NOT NULL BEGIN UPDATE remote_asset_entity SET local_id = (SELECT id FROM local_asset_entity WHERE checksum = OLD.checksum ORDER BY id ASC LIMIT 1) WHERE checksum = OLD.checksum;END',
|
||||
'tr_local_asset_update_old_checksum_set_remote_asset_local_id');
|
||||
i0.Trigger get trLocalAssetDeleteUpdateRemoteAssetLocalId => i0.Trigger(
|
||||
'CREATE TRIGGER IF NOT EXISTS tr_local_asset_delete_update_remote_asset_local_id AFTER DELETE ON local_asset_entity WHEN OLD.checksum IS NOT NULL BEGIN UPDATE remote_asset_entity SET local_id = (SELECT id FROM local_asset_entity WHERE checksum = OLD.checksum ORDER BY id ASC LIMIT 1) WHERE checksum = OLD.checksum;END',
|
||||
'tr_local_asset_delete_update_remote_asset_local_id');
|
||||
i0.Trigger get trRemoteAssetInsertSetLocalId => i0.Trigger(
|
||||
'CREATE TRIGGER IF NOT EXISTS tr_remote_asset_insert_set_local_id AFTER INSERT ON remote_asset_entity BEGIN UPDATE remote_asset_entity SET local_id = (SELECT id FROM local_asset_entity WHERE checksum = NEW.checksum ORDER BY id ASC LIMIT 1) WHERE id = NEW.id;UPDATE local_asset_entity SET remote_id = NEW.id WHERE checksum = NEW.checksum;END',
|
||||
'tr_remote_asset_insert_set_local_id');
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
|
||||
@@ -9,6 +10,11 @@ class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
||||
const LocalAssetEntity();
|
||||
|
||||
TextColumn get id => text()();
|
||||
|
||||
TextColumn get remoteId => text()
|
||||
.nullable()
|
||||
.references(RemoteAssetEntity, #id, onDelete: KeyAction.setNull)();
|
||||
|
||||
TextColumn get checksum => text().nullable()();
|
||||
|
||||
// Only used during backup to mirror the favorite status of the asset in the server
|
||||
@@ -30,6 +36,6 @@ extension LocalAssetEntityDataDomainEx on LocalAssetEntityData {
|
||||
isFavorite: isFavorite,
|
||||
height: height,
|
||||
width: width,
|
||||
remoteId: null,
|
||||
remoteId: remoteId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as i2;
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'
|
||||
as i3;
|
||||
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
|
||||
as i5;
|
||||
import 'package:drift/internal/modular.dart' as i6;
|
||||
|
||||
typedef $$LocalAssetEntityTableCreateCompanionBuilder
|
||||
= i1.LocalAssetEntityCompanion Function({
|
||||
@@ -18,6 +21,7 @@ typedef $$LocalAssetEntityTableCreateCompanionBuilder
|
||||
i0.Value<int?> height,
|
||||
i0.Value<int?> durationInSeconds,
|
||||
required String id,
|
||||
i0.Value<String?> remoteId,
|
||||
i0.Value<String?> checksum,
|
||||
i0.Value<bool> isFavorite,
|
||||
});
|
||||
@@ -31,10 +35,43 @@ typedef $$LocalAssetEntityTableUpdateCompanionBuilder
|
||||
i0.Value<int?> height,
|
||||
i0.Value<int?> durationInSeconds,
|
||||
i0.Value<String> id,
|
||||
i0.Value<String?> remoteId,
|
||||
i0.Value<String?> checksum,
|
||||
i0.Value<bool> isFavorite,
|
||||
});
|
||||
|
||||
final class $$LocalAssetEntityTableReferences extends i0.BaseReferences<
|
||||
i0.GeneratedDatabase, i1.$LocalAssetEntityTable, i1.LocalAssetEntityData> {
|
||||
$$LocalAssetEntityTableReferences(
|
||||
super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static i5.$RemoteAssetEntityTable _remoteIdTable(i0.GeneratedDatabase db) =>
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity')
|
||||
.createAlias(i0.$_aliasNameGenerator(
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i1.$LocalAssetEntityTable>('local_asset_entity')
|
||||
.remoteId,
|
||||
i6.ReadDatabaseContainer(db)
|
||||
.resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity')
|
||||
.id));
|
||||
|
||||
i5.$$RemoteAssetEntityTableProcessedTableManager? get remoteId {
|
||||
final $_column = $_itemColumn<String>('remote_id');
|
||||
if ($_column == null) return null;
|
||||
final manager = i5
|
||||
.$$RemoteAssetEntityTableTableManager(
|
||||
$_db,
|
||||
i6.ReadDatabaseContainer($_db)
|
||||
.resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'))
|
||||
.filter((f) => f.id.sqlEquals($_column));
|
||||
final item = $_typedResult.readTableOrNull(_remoteIdTable($_db));
|
||||
if (item == null) return manager;
|
||||
return i0.ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: [item]));
|
||||
}
|
||||
}
|
||||
|
||||
class $$LocalAssetEntityTableFilterComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAssetEntityTable> {
|
||||
$$LocalAssetEntityTableFilterComposer({
|
||||
@@ -76,6 +113,28 @@ class $$LocalAssetEntityTableFilterComposer
|
||||
|
||||
i0.ColumnFilters<bool> get isFavorite => $composableBuilder(
|
||||
column: $table.isFavorite, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i5.$$RemoteAssetEntityTableFilterComposer get remoteId {
|
||||
final i5.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.remoteId,
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i5.$$RemoteAssetEntityTableFilterComposer(
|
||||
$db: $db,
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $$LocalAssetEntityTableOrderingComposer
|
||||
@@ -120,6 +179,30 @@ class $$LocalAssetEntityTableOrderingComposer
|
||||
i0.ColumnOrderings<bool> get isFavorite => $composableBuilder(
|
||||
column: $table.isFavorite,
|
||||
builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i5.$$RemoteAssetEntityTableOrderingComposer get remoteId {
|
||||
final i5.$$RemoteAssetEntityTableOrderingComposer composer =
|
||||
$composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.remoteId,
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i5.$$RemoteAssetEntityTableOrderingComposer(
|
||||
$db: $db,
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$RemoteAssetEntityTable>(
|
||||
'remote_asset_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $$LocalAssetEntityTableAnnotationComposer
|
||||
@@ -160,6 +243,30 @@ class $$LocalAssetEntityTableAnnotationComposer
|
||||
|
||||
i0.GeneratedColumn<bool> get isFavorite => $composableBuilder(
|
||||
column: $table.isFavorite, builder: (column) => column);
|
||||
|
||||
i5.$$RemoteAssetEntityTableAnnotationComposer get remoteId {
|
||||
final i5.$$RemoteAssetEntityTableAnnotationComposer composer =
|
||||
$composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.remoteId,
|
||||
referencedTable: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i5.$$RemoteAssetEntityTableAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: i6.ReadDatabaseContainer($db)
|
||||
.resultSet<i5.$RemoteAssetEntityTable>(
|
||||
'remote_asset_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $$LocalAssetEntityTableTableManager extends i0.RootTableManager<
|
||||
@@ -171,13 +278,9 @@ class $$LocalAssetEntityTableTableManager extends i0.RootTableManager<
|
||||
i1.$$LocalAssetEntityTableAnnotationComposer,
|
||||
$$LocalAssetEntityTableCreateCompanionBuilder,
|
||||
$$LocalAssetEntityTableUpdateCompanionBuilder,
|
||||
(
|
||||
i1.LocalAssetEntityData,
|
||||
i0.BaseReferences<i0.GeneratedDatabase, i1.$LocalAssetEntityTable,
|
||||
i1.LocalAssetEntityData>
|
||||
),
|
||||
(i1.LocalAssetEntityData, i1.$$LocalAssetEntityTableReferences),
|
||||
i1.LocalAssetEntityData,
|
||||
i0.PrefetchHooks Function()> {
|
||||
i0.PrefetchHooks Function({bool remoteId})> {
|
||||
$$LocalAssetEntityTableTableManager(
|
||||
i0.GeneratedDatabase db, i1.$LocalAssetEntityTable table)
|
||||
: super(i0.TableManagerState(
|
||||
@@ -199,6 +302,7 @@ class $$LocalAssetEntityTableTableManager extends i0.RootTableManager<
|
||||
i0.Value<int?> height = const i0.Value.absent(),
|
||||
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
|
||||
i0.Value<String> id = const i0.Value.absent(),
|
||||
i0.Value<String?> remoteId = const i0.Value.absent(),
|
||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||
}) =>
|
||||
@@ -211,6 +315,7 @@ class $$LocalAssetEntityTableTableManager extends i0.RootTableManager<
|
||||
height: height,
|
||||
durationInSeconds: durationInSeconds,
|
||||
id: id,
|
||||
remoteId: remoteId,
|
||||
checksum: checksum,
|
||||
isFavorite: isFavorite,
|
||||
),
|
||||
@@ -223,6 +328,7 @@ class $$LocalAssetEntityTableTableManager extends i0.RootTableManager<
|
||||
i0.Value<int?> height = const i0.Value.absent(),
|
||||
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
|
||||
required String id,
|
||||
i0.Value<String?> remoteId = const i0.Value.absent(),
|
||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||
}) =>
|
||||
@@ -235,13 +341,52 @@ class $$LocalAssetEntityTableTableManager extends i0.RootTableManager<
|
||||
height: height,
|
||||
durationInSeconds: durationInSeconds,
|
||||
id: id,
|
||||
remoteId: remoteId,
|
||||
checksum: checksum,
|
||||
isFavorite: isFavorite,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||
.map((e) => (
|
||||
e.readTable(table),
|
||||
i1.$$LocalAssetEntityTableReferences(db, table, e)
|
||||
))
|
||||
.toList(),
|
||||
prefetchHooksCallback: null,
|
||||
prefetchHooksCallback: ({remoteId = false}) {
|
||||
return i0.PrefetchHooks(
|
||||
db: db,
|
||||
explicitlyWatchedTables: [],
|
||||
addJoins: <
|
||||
T extends i0.TableManagerState<
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic>>(state) {
|
||||
if (remoteId) {
|
||||
state = state.withJoin(
|
||||
currentTable: table,
|
||||
currentColumn: table.remoteId,
|
||||
referencedTable:
|
||||
i1.$$LocalAssetEntityTableReferences._remoteIdTable(db),
|
||||
referencedColumn: i1.$$LocalAssetEntityTableReferences
|
||||
._remoteIdTable(db)
|
||||
.id,
|
||||
) as T;
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
getPrefetchedDataCallback: (items) async {
|
||||
return [];
|
||||
},
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -254,13 +399,9 @@ typedef $$LocalAssetEntityTableProcessedTableManager = i0.ProcessedTableManager<
|
||||
i1.$$LocalAssetEntityTableAnnotationComposer,
|
||||
$$LocalAssetEntityTableCreateCompanionBuilder,
|
||||
$$LocalAssetEntityTableUpdateCompanionBuilder,
|
||||
(
|
||||
i1.LocalAssetEntityData,
|
||||
i0.BaseReferences<i0.GeneratedDatabase, i1.$LocalAssetEntityTable,
|
||||
i1.LocalAssetEntityData>
|
||||
),
|
||||
(i1.LocalAssetEntityData, i1.$$LocalAssetEntityTableReferences),
|
||||
i1.LocalAssetEntityData,
|
||||
i0.PrefetchHooks Function()>;
|
||||
i0.PrefetchHooks Function({bool remoteId})>;
|
||||
i0.Index get idxLocalAssetChecksum => i0.Index('idx_local_asset_checksum',
|
||||
'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)');
|
||||
|
||||
@@ -321,6 +462,15 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
||||
late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>(
|
||||
'id', aliasedName, false,
|
||||
type: i0.DriftSqlType.string, requiredDuringInsert: true);
|
||||
static const i0.VerificationMeta _remoteIdMeta =
|
||||
const i0.VerificationMeta('remoteId');
|
||||
@override
|
||||
late final i0.GeneratedColumn<String> remoteId = i0.GeneratedColumn<String>(
|
||||
'remote_id', aliasedName, true,
|
||||
type: i0.DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES remote_asset_entity (id) ON DELETE SET NULL'));
|
||||
static const i0.VerificationMeta _checksumMeta =
|
||||
const i0.VerificationMeta('checksum');
|
||||
@override
|
||||
@@ -347,6 +497,7 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
||||
height,
|
||||
durationInSeconds,
|
||||
id,
|
||||
remoteId,
|
||||
checksum,
|
||||
isFavorite
|
||||
];
|
||||
@@ -394,6 +545,10 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
||||
} else if (isInserting) {
|
||||
context.missing(_idMeta);
|
||||
}
|
||||
if (data.containsKey('remote_id')) {
|
||||
context.handle(_remoteIdMeta,
|
||||
remoteId.isAcceptableOrUnknown(data['remote_id']!, _remoteIdMeta));
|
||||
}
|
||||
if (data.containsKey('checksum')) {
|
||||
context.handle(_checksumMeta,
|
||||
checksum.isAcceptableOrUnknown(data['checksum']!, _checksumMeta));
|
||||
@@ -431,6 +586,8 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
||||
i0.DriftSqlType.int, data['${effectivePrefix}duration_in_seconds']),
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||
remoteId: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}remote_id']),
|
||||
checksum: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}checksum']),
|
||||
isFavorite: attachedDatabase.typeMapping
|
||||
@@ -461,6 +618,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
final int? height;
|
||||
final int? durationInSeconds;
|
||||
final String id;
|
||||
final String? remoteId;
|
||||
final String? checksum;
|
||||
final bool isFavorite;
|
||||
const LocalAssetEntityData(
|
||||
@@ -472,6 +630,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
this.height,
|
||||
this.durationInSeconds,
|
||||
required this.id,
|
||||
this.remoteId,
|
||||
this.checksum,
|
||||
required this.isFavorite});
|
||||
@override
|
||||
@@ -494,6 +653,9 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds);
|
||||
}
|
||||
map['id'] = i0.Variable<String>(id);
|
||||
if (!nullToAbsent || remoteId != null) {
|
||||
map['remote_id'] = i0.Variable<String>(remoteId);
|
||||
}
|
||||
if (!nullToAbsent || checksum != null) {
|
||||
map['checksum'] = i0.Variable<String>(checksum);
|
||||
}
|
||||
@@ -514,6 +676,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
height: serializer.fromJson<int?>(json['height']),
|
||||
durationInSeconds: serializer.fromJson<int?>(json['durationInSeconds']),
|
||||
id: serializer.fromJson<String>(json['id']),
|
||||
remoteId: serializer.fromJson<String?>(json['remoteId']),
|
||||
checksum: serializer.fromJson<String?>(json['checksum']),
|
||||
isFavorite: serializer.fromJson<bool>(json['isFavorite']),
|
||||
);
|
||||
@@ -531,6 +694,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
'height': serializer.toJson<int?>(height),
|
||||
'durationInSeconds': serializer.toJson<int?>(durationInSeconds),
|
||||
'id': serializer.toJson<String>(id),
|
||||
'remoteId': serializer.toJson<String?>(remoteId),
|
||||
'checksum': serializer.toJson<String?>(checksum),
|
||||
'isFavorite': serializer.toJson<bool>(isFavorite),
|
||||
};
|
||||
@@ -545,6 +709,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
i0.Value<int?> height = const i0.Value.absent(),
|
||||
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
|
||||
String? id,
|
||||
i0.Value<String?> remoteId = const i0.Value.absent(),
|
||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||
bool? isFavorite}) =>
|
||||
i1.LocalAssetEntityData(
|
||||
@@ -558,6 +723,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
? durationInSeconds.value
|
||||
: this.durationInSeconds,
|
||||
id: id ?? this.id,
|
||||
remoteId: remoteId.present ? remoteId.value : this.remoteId,
|
||||
checksum: checksum.present ? checksum.value : this.checksum,
|
||||
isFavorite: isFavorite ?? this.isFavorite,
|
||||
);
|
||||
@@ -573,6 +739,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
? data.durationInSeconds.value
|
||||
: this.durationInSeconds,
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
remoteId: data.remoteId.present ? data.remoteId.value : this.remoteId,
|
||||
checksum: data.checksum.present ? data.checksum.value : this.checksum,
|
||||
isFavorite:
|
||||
data.isFavorite.present ? data.isFavorite.value : this.isFavorite,
|
||||
@@ -590,6 +757,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
..write('height: $height, ')
|
||||
..write('durationInSeconds: $durationInSeconds, ')
|
||||
..write('id: $id, ')
|
||||
..write('remoteId: $remoteId, ')
|
||||
..write('checksum: $checksum, ')
|
||||
..write('isFavorite: $isFavorite')
|
||||
..write(')'))
|
||||
@@ -598,7 +766,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(name, type, createdAt, updatedAt, width,
|
||||
height, durationInSeconds, id, checksum, isFavorite);
|
||||
height, durationInSeconds, id, remoteId, checksum, isFavorite);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
@@ -611,6 +779,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
other.height == this.height &&
|
||||
other.durationInSeconds == this.durationInSeconds &&
|
||||
other.id == this.id &&
|
||||
other.remoteId == this.remoteId &&
|
||||
other.checksum == this.checksum &&
|
||||
other.isFavorite == this.isFavorite);
|
||||
}
|
||||
@@ -625,6 +794,7 @@ class LocalAssetEntityCompanion
|
||||
final i0.Value<int?> height;
|
||||
final i0.Value<int?> durationInSeconds;
|
||||
final i0.Value<String> id;
|
||||
final i0.Value<String?> remoteId;
|
||||
final i0.Value<String?> checksum;
|
||||
final i0.Value<bool> isFavorite;
|
||||
const LocalAssetEntityCompanion({
|
||||
@@ -636,6 +806,7 @@ class LocalAssetEntityCompanion
|
||||
this.height = const i0.Value.absent(),
|
||||
this.durationInSeconds = const i0.Value.absent(),
|
||||
this.id = const i0.Value.absent(),
|
||||
this.remoteId = const i0.Value.absent(),
|
||||
this.checksum = const i0.Value.absent(),
|
||||
this.isFavorite = const i0.Value.absent(),
|
||||
});
|
||||
@@ -648,6 +819,7 @@ class LocalAssetEntityCompanion
|
||||
this.height = const i0.Value.absent(),
|
||||
this.durationInSeconds = const i0.Value.absent(),
|
||||
required String id,
|
||||
this.remoteId = const i0.Value.absent(),
|
||||
this.checksum = const i0.Value.absent(),
|
||||
this.isFavorite = const i0.Value.absent(),
|
||||
}) : name = i0.Value(name),
|
||||
@@ -662,6 +834,7 @@ class LocalAssetEntityCompanion
|
||||
i0.Expression<int>? height,
|
||||
i0.Expression<int>? durationInSeconds,
|
||||
i0.Expression<String>? id,
|
||||
i0.Expression<String>? remoteId,
|
||||
i0.Expression<String>? checksum,
|
||||
i0.Expression<bool>? isFavorite,
|
||||
}) {
|
||||
@@ -674,6 +847,7 @@ class LocalAssetEntityCompanion
|
||||
if (height != null) 'height': height,
|
||||
if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds,
|
||||
if (id != null) 'id': id,
|
||||
if (remoteId != null) 'remote_id': remoteId,
|
||||
if (checksum != null) 'checksum': checksum,
|
||||
if (isFavorite != null) 'is_favorite': isFavorite,
|
||||
});
|
||||
@@ -688,6 +862,7 @@ class LocalAssetEntityCompanion
|
||||
i0.Value<int?>? height,
|
||||
i0.Value<int?>? durationInSeconds,
|
||||
i0.Value<String>? id,
|
||||
i0.Value<String?>? remoteId,
|
||||
i0.Value<String?>? checksum,
|
||||
i0.Value<bool>? isFavorite}) {
|
||||
return i1.LocalAssetEntityCompanion(
|
||||
@@ -699,6 +874,7 @@ class LocalAssetEntityCompanion
|
||||
height: height ?? this.height,
|
||||
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
||||
id: id ?? this.id,
|
||||
remoteId: remoteId ?? this.remoteId,
|
||||
checksum: checksum ?? this.checksum,
|
||||
isFavorite: isFavorite ?? this.isFavorite,
|
||||
);
|
||||
@@ -732,6 +908,9 @@ class LocalAssetEntityCompanion
|
||||
if (id.present) {
|
||||
map['id'] = i0.Variable<String>(id.value);
|
||||
}
|
||||
if (remoteId.present) {
|
||||
map['remote_id'] = i0.Variable<String>(remoteId.value);
|
||||
}
|
||||
if (checksum.present) {
|
||||
map['checksum'] = i0.Variable<String>(checksum.value);
|
||||
}
|
||||
@@ -752,6 +931,7 @@ class LocalAssetEntityCompanion
|
||||
..write('height: $height, ')
|
||||
..write('durationInSeconds: $durationInSeconds, ')
|
||||
..write('id: $id, ')
|
||||
..write('remoteId: $remoteId, ')
|
||||
..write('checksum: $checksum, ')
|
||||
..write('isFavorite: $isFavorite')
|
||||
..write(')'))
|
||||
|
||||
@@ -5,7 +5,7 @@ mergedAsset: SELECT * FROM
|
||||
(
|
||||
SELECT
|
||||
rae.id as remote_id,
|
||||
lae.id as local_id,
|
||||
rae.local_id as local_id,
|
||||
rae.name,
|
||||
rae."type",
|
||||
rae.created_at,
|
||||
@@ -19,13 +19,11 @@ mergedAsset: SELECT * FROM
|
||||
rae.owner_id
|
||||
FROM
|
||||
remote_asset_entity rae
|
||||
LEFT JOIN
|
||||
local_asset_entity lae ON rae.checksum = lae.checksum
|
||||
WHERE
|
||||
rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id in ?
|
||||
UNION ALL
|
||||
SELECT
|
||||
NULL as remote_id,
|
||||
lae.remote_id as remote_id,
|
||||
lae.id as local_id,
|
||||
lae.name,
|
||||
lae."type",
|
||||
@@ -40,10 +38,8 @@ mergedAsset: SELECT * FROM
|
||||
NULL as owner_id
|
||||
FROM
|
||||
local_asset_entity lae
|
||||
LEFT JOIN
|
||||
remote_asset_entity rae ON rae.checksum = lae.checksum
|
||||
WHERE
|
||||
rae.id IS NULL
|
||||
lae.remote_id IS NULL
|
||||
)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $limit;
|
||||
@@ -62,8 +58,6 @@ FROM
|
||||
rae.created_at
|
||||
FROM
|
||||
remote_asset_entity rae
|
||||
LEFT JOIN
|
||||
local_asset_entity lae ON rae.checksum = lae.checksum
|
||||
WHERE
|
||||
rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id in ?
|
||||
UNION ALL
|
||||
@@ -72,10 +66,8 @@ FROM
|
||||
lae.created_at
|
||||
FROM
|
||||
local_asset_entity lae
|
||||
LEFT JOIN
|
||||
remote_asset_entity rae ON rae.checksum = lae.checksum
|
||||
WHERE
|
||||
rae.id IS NULL
|
||||
lae.remote_id IS NULL
|
||||
)
|
||||
GROUP BY bucket_date
|
||||
ORDER BY bucket_date DESC;
|
||||
|
||||
@@ -18,7 +18,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
|
||||
final generatedlimit = $write(limit, startIndex: $arrayStartIndex);
|
||||
$arrayStartIndex += generatedlimit.amountOfVariables;
|
||||
return customSelect(
|
||||
'SELECT * FROM (SELECT rae.id AS remote_id, lae.id AS local_id, rae.name, rae.type, rae.created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id FROM remote_asset_entity AS rae LEFT JOIN local_asset_entity AS lae ON rae.checksum = lae.checksum WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandedvar1) UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.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 FROM local_asset_entity AS lae LEFT JOIN remote_asset_entity AS rae ON rae.checksum = lae.checksum WHERE rae.id IS NULL) ORDER BY created_at DESC ${generatedlimit.sql}',
|
||||
'SELECT * FROM (SELECT rae.id AS remote_id, rae.local_id AS local_id, rae.name, rae.type, rae.created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id FROM remote_asset_entity AS rae WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandedvar1) UNION ALL SELECT lae.remote_id AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.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 FROM local_asset_entity AS lae WHERE lae.remote_id IS NULL) ORDER BY created_at DESC ${generatedlimit.sql}',
|
||||
variables: [
|
||||
for (var $ in var1) i0.Variable<String>($),
|
||||
...generatedlimit.introducedVariables
|
||||
@@ -51,7 +51,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
|
||||
final expandedvar2 = $expandVar($arrayStartIndex, var2.length);
|
||||
$arrayStartIndex += var2.length;
|
||||
return customSelect(
|
||||
'SELECT COUNT(*) AS asset_count, CASE WHEN ?1 = 0 THEN STRFTIME(\'%Y-%m-%d\', created_at, \'localtime\') WHEN ?1 = 1 THEN STRFTIME(\'%Y-%m\', created_at, \'localtime\') END AS bucket_date FROM (SELECT rae.name, rae.created_at FROM remote_asset_entity AS rae LEFT JOIN local_asset_entity AS lae ON rae.checksum = lae.checksum WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandedvar2) UNION ALL SELECT lae.name, lae.created_at FROM local_asset_entity AS lae LEFT JOIN remote_asset_entity AS rae ON rae.checksum = lae.checksum WHERE rae.id IS NULL) GROUP BY bucket_date ORDER BY bucket_date DESC',
|
||||
'SELECT COUNT(*) AS asset_count, CASE WHEN ?1 = 0 THEN STRFTIME(\'%Y-%m-%d\', created_at, \'localtime\') WHEN ?1 = 1 THEN STRFTIME(\'%Y-%m\', created_at, \'localtime\') END AS bucket_date FROM (SELECT rae.name, rae.created_at FROM remote_asset_entity AS rae WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandedvar2) UNION ALL SELECT lae.name, lae.created_at FROM local_asset_entity AS lae WHERE lae.remote_id IS NULL) GROUP BY bucket_date ORDER BY bucket_date DESC',
|
||||
variables: [
|
||||
i0.Variable<int>(groupBy),
|
||||
for (var $ in var2) i0.Variable<String>($)
|
||||
|
||||
@@ -17,6 +17,8 @@ class RemoteAssetEntity extends Table
|
||||
|
||||
TextColumn get id => text()();
|
||||
|
||||
TextColumn get localId => text().nullable()();
|
||||
|
||||
TextColumn get checksum => text()();
|
||||
|
||||
BoolColumn get isFavorite => boolean().withDefault(const Constant(false))();
|
||||
@@ -51,6 +53,6 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
|
||||
width: width,
|
||||
thumbHash: thumbHash,
|
||||
visibility: visibility,
|
||||
localId: null,
|
||||
localId: localId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ typedef $$RemoteAssetEntityTableCreateCompanionBuilder
|
||||
i0.Value<int?> height,
|
||||
i0.Value<int?> durationInSeconds,
|
||||
required String id,
|
||||
i0.Value<String?> localId,
|
||||
required String checksum,
|
||||
i0.Value<bool> isFavorite,
|
||||
required String ownerId,
|
||||
@@ -39,6 +40,7 @@ typedef $$RemoteAssetEntityTableUpdateCompanionBuilder
|
||||
i0.Value<int?> height,
|
||||
i0.Value<int?> durationInSeconds,
|
||||
i0.Value<String> id,
|
||||
i0.Value<String?> localId,
|
||||
i0.Value<String> checksum,
|
||||
i0.Value<bool> isFavorite,
|
||||
i0.Value<String> ownerId,
|
||||
@@ -118,6 +120,9 @@ class $$RemoteAssetEntityTableFilterComposer
|
||||
i0.ColumnFilters<String> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i0.ColumnFilters<String> get localId => $composableBuilder(
|
||||
column: $table.localId, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i0.ColumnFilters<String> get checksum => $composableBuilder(
|
||||
column: $table.checksum, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
@@ -198,6 +203,9 @@ class $$RemoteAssetEntityTableOrderingComposer
|
||||
i0.ColumnOrderings<String> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i0.ColumnOrderings<String> get localId => $composableBuilder(
|
||||
column: $table.localId, builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i0.ColumnOrderings<String> get checksum => $composableBuilder(
|
||||
column: $table.checksum, builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
@@ -277,6 +285,9 @@ class $$RemoteAssetEntityTableAnnotationComposer
|
||||
i0.GeneratedColumn<String> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<String> get localId =>
|
||||
$composableBuilder(column: $table.localId, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<String> get checksum =>
|
||||
$composableBuilder(column: $table.checksum, builder: (column) => column);
|
||||
|
||||
@@ -352,6 +363,7 @@ class $$RemoteAssetEntityTableTableManager extends i0.RootTableManager<
|
||||
i0.Value<int?> height = const i0.Value.absent(),
|
||||
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
|
||||
i0.Value<String> id = const i0.Value.absent(),
|
||||
i0.Value<String?> localId = const i0.Value.absent(),
|
||||
i0.Value<String> checksum = const i0.Value.absent(),
|
||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||
i0.Value<String> ownerId = const i0.Value.absent(),
|
||||
@@ -369,6 +381,7 @@ class $$RemoteAssetEntityTableTableManager extends i0.RootTableManager<
|
||||
height: height,
|
||||
durationInSeconds: durationInSeconds,
|
||||
id: id,
|
||||
localId: localId,
|
||||
checksum: checksum,
|
||||
isFavorite: isFavorite,
|
||||
ownerId: ownerId,
|
||||
@@ -386,6 +399,7 @@ class $$RemoteAssetEntityTableTableManager extends i0.RootTableManager<
|
||||
i0.Value<int?> height = const i0.Value.absent(),
|
||||
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
|
||||
required String id,
|
||||
i0.Value<String?> localId = const i0.Value.absent(),
|
||||
required String checksum,
|
||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||
required String ownerId,
|
||||
@@ -403,6 +417,7 @@ class $$RemoteAssetEntityTableTableManager extends i0.RootTableManager<
|
||||
height: height,
|
||||
durationInSeconds: durationInSeconds,
|
||||
id: id,
|
||||
localId: localId,
|
||||
checksum: checksum,
|
||||
isFavorite: isFavorite,
|
||||
ownerId: ownerId,
|
||||
@@ -530,6 +545,12 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
|
||||
late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>(
|
||||
'id', aliasedName, false,
|
||||
type: i0.DriftSqlType.string, requiredDuringInsert: true);
|
||||
static const i0.VerificationMeta _localIdMeta =
|
||||
const i0.VerificationMeta('localId');
|
||||
@override
|
||||
late final i0.GeneratedColumn<String> localId = i0.GeneratedColumn<String>(
|
||||
'local_id', aliasedName, true,
|
||||
type: i0.DriftSqlType.string, requiredDuringInsert: false);
|
||||
static const i0.VerificationMeta _checksumMeta =
|
||||
const i0.VerificationMeta('checksum');
|
||||
@override
|
||||
@@ -589,6 +610,7 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
|
||||
height,
|
||||
durationInSeconds,
|
||||
id,
|
||||
localId,
|
||||
checksum,
|
||||
isFavorite,
|
||||
ownerId,
|
||||
@@ -641,6 +663,10 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
|
||||
} else if (isInserting) {
|
||||
context.missing(_idMeta);
|
||||
}
|
||||
if (data.containsKey('local_id')) {
|
||||
context.handle(_localIdMeta,
|
||||
localId.isAcceptableOrUnknown(data['local_id']!, _localIdMeta));
|
||||
}
|
||||
if (data.containsKey('checksum')) {
|
||||
context.handle(_checksumMeta,
|
||||
checksum.isAcceptableOrUnknown(data['checksum']!, _checksumMeta));
|
||||
@@ -700,6 +726,8 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
|
||||
i0.DriftSqlType.int, data['${effectivePrefix}duration_in_seconds']),
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||
localId: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}local_id']),
|
||||
checksum: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}checksum'])!,
|
||||
isFavorite: attachedDatabase.typeMapping
|
||||
@@ -744,6 +772,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
||||
final int? height;
|
||||
final int? durationInSeconds;
|
||||
final String id;
|
||||
final String? localId;
|
||||
final String checksum;
|
||||
final bool isFavorite;
|
||||
final String ownerId;
|
||||
@@ -760,6 +789,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
||||
this.height,
|
||||
this.durationInSeconds,
|
||||
required this.id,
|
||||
this.localId,
|
||||
required this.checksum,
|
||||
required this.isFavorite,
|
||||
required this.ownerId,
|
||||
@@ -787,6 +817,9 @@ class RemoteAssetEntityData extends i0.DataClass
|
||||
map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds);
|
||||
}
|
||||
map['id'] = i0.Variable<String>(id);
|
||||
if (!nullToAbsent || localId != null) {
|
||||
map['local_id'] = i0.Variable<String>(localId);
|
||||
}
|
||||
map['checksum'] = i0.Variable<String>(checksum);
|
||||
map['is_favorite'] = i0.Variable<bool>(isFavorite);
|
||||
map['owner_id'] = i0.Variable<String>(ownerId);
|
||||
@@ -819,6 +852,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
||||
height: serializer.fromJson<int?>(json['height']),
|
||||
durationInSeconds: serializer.fromJson<int?>(json['durationInSeconds']),
|
||||
id: serializer.fromJson<String>(json['id']),
|
||||
localId: serializer.fromJson<String?>(json['localId']),
|
||||
checksum: serializer.fromJson<String>(json['checksum']),
|
||||
isFavorite: serializer.fromJson<bool>(json['isFavorite']),
|
||||
ownerId: serializer.fromJson<String>(json['ownerId']),
|
||||
@@ -842,6 +876,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
||||
'height': serializer.toJson<int?>(height),
|
||||
'durationInSeconds': serializer.toJson<int?>(durationInSeconds),
|
||||
'id': serializer.toJson<String>(id),
|
||||
'localId': serializer.toJson<String?>(localId),
|
||||
'checksum': serializer.toJson<String>(checksum),
|
||||
'isFavorite': serializer.toJson<bool>(isFavorite),
|
||||
'ownerId': serializer.toJson<String>(ownerId),
|
||||
@@ -862,6 +897,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
||||
i0.Value<int?> height = const i0.Value.absent(),
|
||||
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
|
||||
String? id,
|
||||
i0.Value<String?> localId = const i0.Value.absent(),
|
||||
String? checksum,
|
||||
bool? isFavorite,
|
||||
String? ownerId,
|
||||
@@ -880,6 +916,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
||||
? durationInSeconds.value
|
||||
: this.durationInSeconds,
|
||||
id: id ?? this.id,
|
||||
localId: localId.present ? localId.value : this.localId,
|
||||
checksum: checksum ?? this.checksum,
|
||||
isFavorite: isFavorite ?? this.isFavorite,
|
||||
ownerId: ownerId ?? this.ownerId,
|
||||
@@ -901,6 +938,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
||||
? data.durationInSeconds.value
|
||||
: this.durationInSeconds,
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
localId: data.localId.present ? data.localId.value : this.localId,
|
||||
checksum: data.checksum.present ? data.checksum.value : this.checksum,
|
||||
isFavorite:
|
||||
data.isFavorite.present ? data.isFavorite.value : this.isFavorite,
|
||||
@@ -926,6 +964,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
||||
..write('height: $height, ')
|
||||
..write('durationInSeconds: $durationInSeconds, ')
|
||||
..write('id: $id, ')
|
||||
..write('localId: $localId, ')
|
||||
..write('checksum: $checksum, ')
|
||||
..write('isFavorite: $isFavorite, ')
|
||||
..write('ownerId: $ownerId, ')
|
||||
@@ -947,6 +986,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
||||
height,
|
||||
durationInSeconds,
|
||||
id,
|
||||
localId,
|
||||
checksum,
|
||||
isFavorite,
|
||||
ownerId,
|
||||
@@ -966,6 +1006,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
||||
other.height == this.height &&
|
||||
other.durationInSeconds == this.durationInSeconds &&
|
||||
other.id == this.id &&
|
||||
other.localId == this.localId &&
|
||||
other.checksum == this.checksum &&
|
||||
other.isFavorite == this.isFavorite &&
|
||||
other.ownerId == this.ownerId &&
|
||||
@@ -985,6 +1026,7 @@ class RemoteAssetEntityCompanion
|
||||
final i0.Value<int?> height;
|
||||
final i0.Value<int?> durationInSeconds;
|
||||
final i0.Value<String> id;
|
||||
final i0.Value<String?> localId;
|
||||
final i0.Value<String> checksum;
|
||||
final i0.Value<bool> isFavorite;
|
||||
final i0.Value<String> ownerId;
|
||||
@@ -1001,6 +1043,7 @@ class RemoteAssetEntityCompanion
|
||||
this.height = const i0.Value.absent(),
|
||||
this.durationInSeconds = const i0.Value.absent(),
|
||||
this.id = const i0.Value.absent(),
|
||||
this.localId = const i0.Value.absent(),
|
||||
this.checksum = const i0.Value.absent(),
|
||||
this.isFavorite = const i0.Value.absent(),
|
||||
this.ownerId = const i0.Value.absent(),
|
||||
@@ -1018,6 +1061,7 @@ class RemoteAssetEntityCompanion
|
||||
this.height = const i0.Value.absent(),
|
||||
this.durationInSeconds = const i0.Value.absent(),
|
||||
required String id,
|
||||
this.localId = const i0.Value.absent(),
|
||||
required String checksum,
|
||||
this.isFavorite = const i0.Value.absent(),
|
||||
required String ownerId,
|
||||
@@ -1040,6 +1084,7 @@ class RemoteAssetEntityCompanion
|
||||
i0.Expression<int>? height,
|
||||
i0.Expression<int>? durationInSeconds,
|
||||
i0.Expression<String>? id,
|
||||
i0.Expression<String>? localId,
|
||||
i0.Expression<String>? checksum,
|
||||
i0.Expression<bool>? isFavorite,
|
||||
i0.Expression<String>? ownerId,
|
||||
@@ -1057,6 +1102,7 @@ class RemoteAssetEntityCompanion
|
||||
if (height != null) 'height': height,
|
||||
if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds,
|
||||
if (id != null) 'id': id,
|
||||
if (localId != null) 'local_id': localId,
|
||||
if (checksum != null) 'checksum': checksum,
|
||||
if (isFavorite != null) 'is_favorite': isFavorite,
|
||||
if (ownerId != null) 'owner_id': ownerId,
|
||||
@@ -1076,6 +1122,7 @@ class RemoteAssetEntityCompanion
|
||||
i0.Value<int?>? height,
|
||||
i0.Value<int?>? durationInSeconds,
|
||||
i0.Value<String>? id,
|
||||
i0.Value<String?>? localId,
|
||||
i0.Value<String>? checksum,
|
||||
i0.Value<bool>? isFavorite,
|
||||
i0.Value<String>? ownerId,
|
||||
@@ -1092,6 +1139,7 @@ class RemoteAssetEntityCompanion
|
||||
height: height ?? this.height,
|
||||
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
||||
id: id ?? this.id,
|
||||
localId: localId ?? this.localId,
|
||||
checksum: checksum ?? this.checksum,
|
||||
isFavorite: isFavorite ?? this.isFavorite,
|
||||
ownerId: ownerId ?? this.ownerId,
|
||||
@@ -1130,6 +1178,9 @@ class RemoteAssetEntityCompanion
|
||||
if (id.present) {
|
||||
map['id'] = i0.Variable<String>(id.value);
|
||||
}
|
||||
if (localId.present) {
|
||||
map['local_id'] = i0.Variable<String>(localId.value);
|
||||
}
|
||||
if (checksum.present) {
|
||||
map['checksum'] = i0.Variable<String>(checksum.value);
|
||||
}
|
||||
@@ -1167,6 +1218,7 @@ class RemoteAssetEntityCompanion
|
||||
..write('height: $height, ')
|
||||
..write('durationInSeconds: $durationInSeconds, ')
|
||||
..write('id: $id, ')
|
||||
..write('localId: $localId, ')
|
||||
..write('checksum: $checksum, ')
|
||||
..write('isFavorite: $isFavorite, ')
|
||||
..write('ownerId: $ownerId, ')
|
||||
|
||||
22
mobile/lib/infrastructure/entities/stack.entity.dart
Normal file
22
mobile/lib/infrastructure/entities/stack.entity.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'package:drift/drift.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';
|
||||
|
||||
class StackEntity extends Table with DriftDefaultsMixin {
|
||||
const StackEntity();
|
||||
|
||||
TextColumn get id => text()();
|
||||
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
|
||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
|
||||
TextColumn get ownerId =>
|
||||
text().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
||||
|
||||
TextColumn get primaryAssetId => text().references(RemoteAssetEntity, #id)();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
706
mobile/lib/infrastructure/entities/stack.entity.drift.dart
generated
Normal file
706
mobile/lib/infrastructure/entities/stack.entity.drift.dart
generated
Normal file
@@ -0,0 +1,706 @@
|
||||
// dart format width=80
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart'
|
||||
as i1;
|
||||
import 'package:immich_mobile/infrastructure/entities/stack.entity.dart' as i2;
|
||||
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
|
||||
as i4;
|
||||
import 'package:drift/internal/modular.dart' as i5;
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
|
||||
as i6;
|
||||
|
||||
typedef $$StackEntityTableCreateCompanionBuilder = i1.StackEntityCompanion
|
||||
Function({
|
||||
required String id,
|
||||
i0.Value<DateTime> createdAt,
|
||||
i0.Value<DateTime> updatedAt,
|
||||
required String ownerId,
|
||||
required String primaryAssetId,
|
||||
});
|
||||
typedef $$StackEntityTableUpdateCompanionBuilder = i1.StackEntityCompanion
|
||||
Function({
|
||||
i0.Value<String> id,
|
||||
i0.Value<DateTime> createdAt,
|
||||
i0.Value<DateTime> updatedAt,
|
||||
i0.Value<String> ownerId,
|
||||
i0.Value<String> primaryAssetId,
|
||||
});
|
||||
|
||||
final class $$StackEntityTableReferences extends i0.BaseReferences<
|
||||
i0.GeneratedDatabase, i1.$StackEntityTable, i1.StackEntityData> {
|
||||
$$StackEntityTableReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static i4.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) =>
|
||||
i5.ReadDatabaseContainer(db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity')
|
||||
.createAlias(i0.$_aliasNameGenerator(
|
||||
i5.ReadDatabaseContainer(db)
|
||||
.resultSet<i1.$StackEntityTable>('stack_entity')
|
||||
.ownerId,
|
||||
i5.ReadDatabaseContainer(db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity')
|
||||
.id));
|
||||
|
||||
i4.$$UserEntityTableProcessedTableManager get ownerId {
|
||||
final $_column = $_itemColumn<String>('owner_id')!;
|
||||
|
||||
final manager = i4
|
||||
.$$UserEntityTableTableManager(
|
||||
$_db,
|
||||
i5.ReadDatabaseContainer($_db)
|
||||
.resultSet<i4.$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 i6.$RemoteAssetEntityTable _primaryAssetIdTable(
|
||||
i0.GeneratedDatabase db) =>
|
||||
i5.ReadDatabaseContainer(db)
|
||||
.resultSet<i6.$RemoteAssetEntityTable>('remote_asset_entity')
|
||||
.createAlias(i0.$_aliasNameGenerator(
|
||||
i5.ReadDatabaseContainer(db)
|
||||
.resultSet<i1.$StackEntityTable>('stack_entity')
|
||||
.primaryAssetId,
|
||||
i5.ReadDatabaseContainer(db)
|
||||
.resultSet<i6.$RemoteAssetEntityTable>('remote_asset_entity')
|
||||
.id));
|
||||
|
||||
i6.$$RemoteAssetEntityTableProcessedTableManager get primaryAssetId {
|
||||
final $_column = $_itemColumn<String>('primary_asset_id')!;
|
||||
|
||||
final manager = i6
|
||||
.$$RemoteAssetEntityTableTableManager(
|
||||
$_db,
|
||||
i5.ReadDatabaseContainer($_db)
|
||||
.resultSet<i6.$RemoteAssetEntityTable>('remote_asset_entity'))
|
||||
.filter((f) => f.id.sqlEquals($_column));
|
||||
final item = $_typedResult.readTableOrNull(_primaryAssetIdTable($_db));
|
||||
if (item == null) return manager;
|
||||
return i0.ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: [item]));
|
||||
}
|
||||
}
|
||||
|
||||
class $$StackEntityTableFilterComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$StackEntityTable> {
|
||||
$$StackEntityTableFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnFilters<String> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i0.ColumnFilters<DateTime> get createdAt => $composableBuilder(
|
||||
column: $table.createdAt, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i0.ColumnFilters<DateTime> get updatedAt => $composableBuilder(
|
||||
column: $table.updatedAt, builder: (column) => i0.ColumnFilters(column));
|
||||
|
||||
i4.$$UserEntityTableFilterComposer get ownerId {
|
||||
final i4.$$UserEntityTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.ownerId,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i4.$$UserEntityTableFilterComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
|
||||
i6.$$RemoteAssetEntityTableFilterComposer get primaryAssetId {
|
||||
final i6.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.primaryAssetId,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i6.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i6.$$RemoteAssetEntityTableFilterComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i6.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $$StackEntityTableOrderingComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$StackEntityTable> {
|
||||
$$StackEntityTableOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnOrderings<String> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i0.ColumnOrderings<DateTime> get createdAt => $composableBuilder(
|
||||
column: $table.createdAt,
|
||||
builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i0.ColumnOrderings<DateTime> get updatedAt => $composableBuilder(
|
||||
column: $table.updatedAt,
|
||||
builder: (column) => i0.ColumnOrderings(column));
|
||||
|
||||
i4.$$UserEntityTableOrderingComposer get ownerId {
|
||||
final i4.$$UserEntityTableOrderingComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.ownerId,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i4.$$UserEntityTableOrderingComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
|
||||
i6.$$RemoteAssetEntityTableOrderingComposer get primaryAssetId {
|
||||
final i6.$$RemoteAssetEntityTableOrderingComposer composer =
|
||||
$composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.primaryAssetId,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i6.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i6.$$RemoteAssetEntityTableOrderingComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i6.$RemoteAssetEntityTable>(
|
||||
'remote_asset_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $$StackEntityTableAnnotationComposer
|
||||
extends i0.Composer<i0.GeneratedDatabase, i1.$StackEntityTable> {
|
||||
$$StackEntityTableAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.GeneratedColumn<String> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<DateTime> get createdAt =>
|
||||
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<DateTime> get updatedAt =>
|
||||
$composableBuilder(column: $table.updatedAt, builder: (column) => column);
|
||||
|
||||
i4.$$UserEntityTableAnnotationComposer get ownerId {
|
||||
final i4.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.ownerId,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i4.$$UserEntityTableAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i4.$UserEntityTable>('user_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
|
||||
i6.$$RemoteAssetEntityTableAnnotationComposer get primaryAssetId {
|
||||
final i6.$$RemoteAssetEntityTableAnnotationComposer composer =
|
||||
$composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.primaryAssetId,
|
||||
referencedTable: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i6.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
i6.$$RemoteAssetEntityTableAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: i5.ReadDatabaseContainer($db)
|
||||
.resultSet<i6.$RemoteAssetEntityTable>(
|
||||
'remote_asset_entity'),
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $$StackEntityTableTableManager extends i0.RootTableManager<
|
||||
i0.GeneratedDatabase,
|
||||
i1.$StackEntityTable,
|
||||
i1.StackEntityData,
|
||||
i1.$$StackEntityTableFilterComposer,
|
||||
i1.$$StackEntityTableOrderingComposer,
|
||||
i1.$$StackEntityTableAnnotationComposer,
|
||||
$$StackEntityTableCreateCompanionBuilder,
|
||||
$$StackEntityTableUpdateCompanionBuilder,
|
||||
(i1.StackEntityData, i1.$$StackEntityTableReferences),
|
||||
i1.StackEntityData,
|
||||
i0.PrefetchHooks Function({bool ownerId, bool primaryAssetId})> {
|
||||
$$StackEntityTableTableManager(
|
||||
i0.GeneratedDatabase db, i1.$StackEntityTable table)
|
||||
: super(i0.TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
i1.$$StackEntityTableFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
i1.$$StackEntityTableOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
i1.$$StackEntityTableAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
i0.Value<String> id = const i0.Value.absent(),
|
||||
i0.Value<DateTime> createdAt = const i0.Value.absent(),
|
||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||
i0.Value<String> ownerId = const i0.Value.absent(),
|
||||
i0.Value<String> primaryAssetId = const i0.Value.absent(),
|
||||
}) =>
|
||||
i1.StackEntityCompanion(
|
||||
id: id,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
ownerId: ownerId,
|
||||
primaryAssetId: primaryAssetId,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String id,
|
||||
i0.Value<DateTime> createdAt = const i0.Value.absent(),
|
||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||
required String ownerId,
|
||||
required String primaryAssetId,
|
||||
}) =>
|
||||
i1.StackEntityCompanion.insert(
|
||||
id: id,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
ownerId: ownerId,
|
||||
primaryAssetId: primaryAssetId,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (
|
||||
e.readTable(table),
|
||||
i1.$$StackEntityTableReferences(db, table, e)
|
||||
))
|
||||
.toList(),
|
||||
prefetchHooksCallback: ({ownerId = false, primaryAssetId = false}) {
|
||||
return i0.PrefetchHooks(
|
||||
db: db,
|
||||
explicitlyWatchedTables: [],
|
||||
addJoins: <
|
||||
T extends i0.TableManagerState<
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic>>(state) {
|
||||
if (ownerId) {
|
||||
state = state.withJoin(
|
||||
currentTable: table,
|
||||
currentColumn: table.ownerId,
|
||||
referencedTable:
|
||||
i1.$$StackEntityTableReferences._ownerIdTable(db),
|
||||
referencedColumn:
|
||||
i1.$$StackEntityTableReferences._ownerIdTable(db).id,
|
||||
) as T;
|
||||
}
|
||||
if (primaryAssetId) {
|
||||
state = state.withJoin(
|
||||
currentTable: table,
|
||||
currentColumn: table.primaryAssetId,
|
||||
referencedTable: i1.$$StackEntityTableReferences
|
||||
._primaryAssetIdTable(db),
|
||||
referencedColumn: i1.$$StackEntityTableReferences
|
||||
._primaryAssetIdTable(db)
|
||||
.id,
|
||||
) as T;
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
getPrefetchedDataCallback: (items) async {
|
||||
return [];
|
||||
},
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
typedef $$StackEntityTableProcessedTableManager = i0.ProcessedTableManager<
|
||||
i0.GeneratedDatabase,
|
||||
i1.$StackEntityTable,
|
||||
i1.StackEntityData,
|
||||
i1.$$StackEntityTableFilterComposer,
|
||||
i1.$$StackEntityTableOrderingComposer,
|
||||
i1.$$StackEntityTableAnnotationComposer,
|
||||
$$StackEntityTableCreateCompanionBuilder,
|
||||
$$StackEntityTableUpdateCompanionBuilder,
|
||||
(i1.StackEntityData, i1.$$StackEntityTableReferences),
|
||||
i1.StackEntityData,
|
||||
i0.PrefetchHooks Function({bool ownerId, bool primaryAssetId})>;
|
||||
|
||||
class $StackEntityTable extends i2.StackEntity
|
||||
with i0.TableInfo<$StackEntityTable, i1.StackEntityData> {
|
||||
@override
|
||||
final i0.GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$StackEntityTable(this.attachedDatabase, [this._alias]);
|
||||
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
|
||||
@override
|
||||
late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>(
|
||||
'id', aliasedName, false,
|
||||
type: i0.DriftSqlType.string, requiredDuringInsert: true);
|
||||
static const i0.VerificationMeta _createdAtMeta =
|
||||
const i0.VerificationMeta('createdAt');
|
||||
@override
|
||||
late final i0.GeneratedColumn<DateTime> createdAt =
|
||||
i0.GeneratedColumn<DateTime>('created_at', aliasedName, false,
|
||||
type: i0.DriftSqlType.dateTime,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: i3.currentDateAndTime);
|
||||
static const i0.VerificationMeta _updatedAtMeta =
|
||||
const i0.VerificationMeta('updatedAt');
|
||||
@override
|
||||
late final i0.GeneratedColumn<DateTime> updatedAt =
|
||||
i0.GeneratedColumn<DateTime>('updated_at', aliasedName, false,
|
||||
type: i0.DriftSqlType.dateTime,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: i3.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 _primaryAssetIdMeta =
|
||||
const i0.VerificationMeta('primaryAssetId');
|
||||
@override
|
||||
late final i0.GeneratedColumn<String> primaryAssetId =
|
||||
i0.GeneratedColumn<String>(
|
||||
'primary_asset_id', aliasedName, false,
|
||||
type: i0.DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES remote_asset_entity (id)'));
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns =>
|
||||
[id, createdAt, updatedAt, ownerId, primaryAssetId];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'stack_entity';
|
||||
@override
|
||||
i0.VerificationContext validateIntegrity(
|
||||
i0.Insertable<i1.StackEntityData> instance,
|
||||
{bool isInserting = false}) {
|
||||
final context = i0.VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('id')) {
|
||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_idMeta);
|
||||
}
|
||||
if (data.containsKey('created_at')) {
|
||||
context.handle(_createdAtMeta,
|
||||
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
|
||||
}
|
||||
if (data.containsKey('updated_at')) {
|
||||
context.handle(_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('primary_asset_id')) {
|
||||
context.handle(
|
||||
_primaryAssetIdMeta,
|
||||
primaryAssetId.isAcceptableOrUnknown(
|
||||
data['primary_asset_id']!, _primaryAssetIdMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_primaryAssetIdMeta);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<i0.GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
i1.StackEntityData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return i1.StackEntityData(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||
createdAt: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
|
||||
updatedAt: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!,
|
||||
ownerId: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}owner_id'])!,
|
||||
primaryAssetId: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.string, data['${effectivePrefix}primary_asset_id'])!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
$StackEntityTable createAlias(String alias) {
|
||||
return $StackEntityTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get withoutRowId => true;
|
||||
@override
|
||||
bool get isStrict => true;
|
||||
}
|
||||
|
||||
class StackEntityData extends i0.DataClass
|
||||
implements i0.Insertable<i1.StackEntityData> {
|
||||
final String id;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final String ownerId;
|
||||
final String primaryAssetId;
|
||||
const StackEntityData(
|
||||
{required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.ownerId,
|
||||
required this.primaryAssetId});
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
map['id'] = i0.Variable<String>(id);
|
||||
map['created_at'] = i0.Variable<DateTime>(createdAt);
|
||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
||||
map['owner_id'] = i0.Variable<String>(ownerId);
|
||||
map['primary_asset_id'] = i0.Variable<String>(primaryAssetId);
|
||||
return map;
|
||||
}
|
||||
|
||||
factory StackEntityData.fromJson(Map<String, dynamic> json,
|
||||
{i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return StackEntityData(
|
||||
id: serializer.fromJson<String>(json['id']),
|
||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||
ownerId: serializer.fromJson<String>(json['ownerId']),
|
||||
primaryAssetId: serializer.fromJson<String>(json['primaryAssetId']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<String>(id),
|
||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
||||
'ownerId': serializer.toJson<String>(ownerId),
|
||||
'primaryAssetId': serializer.toJson<String>(primaryAssetId),
|
||||
};
|
||||
}
|
||||
|
||||
i1.StackEntityData copyWith(
|
||||
{String? id,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
String? ownerId,
|
||||
String? primaryAssetId}) =>
|
||||
i1.StackEntityData(
|
||||
id: id ?? this.id,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
ownerId: ownerId ?? this.ownerId,
|
||||
primaryAssetId: primaryAssetId ?? this.primaryAssetId,
|
||||
);
|
||||
StackEntityData copyWithCompanion(i1.StackEntityCompanion data) {
|
||||
return StackEntityData(
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
||||
ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId,
|
||||
primaryAssetId: data.primaryAssetId.present
|
||||
? data.primaryAssetId.value
|
||||
: this.primaryAssetId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('StackEntityData(')
|
||||
..write('id: $id, ')
|
||||
..write('createdAt: $createdAt, ')
|
||||
..write('updatedAt: $updatedAt, ')
|
||||
..write('ownerId: $ownerId, ')
|
||||
..write('primaryAssetId: $primaryAssetId')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is i1.StackEntityData &&
|
||||
other.id == this.id &&
|
||||
other.createdAt == this.createdAt &&
|
||||
other.updatedAt == this.updatedAt &&
|
||||
other.ownerId == this.ownerId &&
|
||||
other.primaryAssetId == this.primaryAssetId);
|
||||
}
|
||||
|
||||
class StackEntityCompanion extends i0.UpdateCompanion<i1.StackEntityData> {
|
||||
final i0.Value<String> id;
|
||||
final i0.Value<DateTime> createdAt;
|
||||
final i0.Value<DateTime> updatedAt;
|
||||
final i0.Value<String> ownerId;
|
||||
final i0.Value<String> primaryAssetId;
|
||||
const StackEntityCompanion({
|
||||
this.id = const i0.Value.absent(),
|
||||
this.createdAt = const i0.Value.absent(),
|
||||
this.updatedAt = const i0.Value.absent(),
|
||||
this.ownerId = const i0.Value.absent(),
|
||||
this.primaryAssetId = const i0.Value.absent(),
|
||||
});
|
||||
StackEntityCompanion.insert({
|
||||
required String id,
|
||||
this.createdAt = const i0.Value.absent(),
|
||||
this.updatedAt = const i0.Value.absent(),
|
||||
required String ownerId,
|
||||
required String primaryAssetId,
|
||||
}) : id = i0.Value(id),
|
||||
ownerId = i0.Value(ownerId),
|
||||
primaryAssetId = i0.Value(primaryAssetId);
|
||||
static i0.Insertable<i1.StackEntityData> custom({
|
||||
i0.Expression<String>? id,
|
||||
i0.Expression<DateTime>? createdAt,
|
||||
i0.Expression<DateTime>? updatedAt,
|
||||
i0.Expression<String>? ownerId,
|
||||
i0.Expression<String>? primaryAssetId,
|
||||
}) {
|
||||
return i0.RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (createdAt != null) 'created_at': createdAt,
|
||||
if (updatedAt != null) 'updated_at': updatedAt,
|
||||
if (ownerId != null) 'owner_id': ownerId,
|
||||
if (primaryAssetId != null) 'primary_asset_id': primaryAssetId,
|
||||
});
|
||||
}
|
||||
|
||||
i1.StackEntityCompanion copyWith(
|
||||
{i0.Value<String>? id,
|
||||
i0.Value<DateTime>? createdAt,
|
||||
i0.Value<DateTime>? updatedAt,
|
||||
i0.Value<String>? ownerId,
|
||||
i0.Value<String>? primaryAssetId}) {
|
||||
return i1.StackEntityCompanion(
|
||||
id: id ?? this.id,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
ownerId: ownerId ?? this.ownerId,
|
||||
primaryAssetId: primaryAssetId ?? this.primaryAssetId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = i0.Variable<String>(id.value);
|
||||
}
|
||||
if (createdAt.present) {
|
||||
map['created_at'] = i0.Variable<DateTime>(createdAt.value);
|
||||
}
|
||||
if (updatedAt.present) {
|
||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
||||
}
|
||||
if (ownerId.present) {
|
||||
map['owner_id'] = i0.Variable<String>(ownerId.value);
|
||||
}
|
||||
if (primaryAssetId.present) {
|
||||
map['primary_asset_id'] = i0.Variable<String>(primaryAssetId.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('StackEntityCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('createdAt: $createdAt, ')
|
||||
..write('updatedAt: $updatedAt, ')
|
||||
..write('ownerId: $ownerId, ')
|
||||
..write('primaryAssetId: $primaryAssetId')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/stack.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
@@ -50,9 +51,11 @@ class IsarDatabaseRepository implements IDatabaseRepository {
|
||||
RemoteAlbumUserEntity,
|
||||
MemoryEntity,
|
||||
MemoryAssetEntity,
|
||||
StackEntity,
|
||||
],
|
||||
include: {
|
||||
'package:immich_mobile/infrastructure/entities/merged_asset.drift',
|
||||
'package:immich_mobile/infrastructure/entities/asset_triggers.drift',
|
||||
},
|
||||
)
|
||||
class Drift extends $Drift implements IDatabaseRepository {
|
||||
|
||||
@@ -7,29 +7,33 @@ import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.
|
||||
as i2;
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'
|
||||
as i3;
|
||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/asset_triggers.drift.dart'
|
||||
as i4;
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart'
|
||||
as i5;
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'
|
||||
as i6;
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'
|
||||
as i7;
|
||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'
|
||||
as i8;
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'
|
||||
as i9;
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart'
|
||||
as i10;
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart'
|
||||
as i11;
|
||||
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'
|
||||
as i12;
|
||||
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'
|
||||
as i13;
|
||||
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart'
|
||||
as i14;
|
||||
import 'package:drift/internal/modular.dart' as i15;
|
||||
import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart'
|
||||
as i15;
|
||||
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||
as i16;
|
||||
import 'package:drift/internal/modular.dart' as i17;
|
||||
|
||||
abstract class $Drift extends i0.GeneratedDatabase {
|
||||
$Drift(i0.QueryExecutor e) : super(e);
|
||||
@@ -39,27 +43,28 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
i2.$RemoteAssetEntityTable(this);
|
||||
late final i3.$LocalAssetEntityTable localAssetEntity =
|
||||
i3.$LocalAssetEntityTable(this);
|
||||
late final i4.$UserMetadataEntityTable userMetadataEntity =
|
||||
i4.$UserMetadataEntityTable(this);
|
||||
late final i5.$PartnerEntityTable partnerEntity =
|
||||
i5.$PartnerEntityTable(this);
|
||||
late final i6.$LocalAlbumEntityTable localAlbumEntity =
|
||||
i6.$LocalAlbumEntityTable(this);
|
||||
late final i7.$LocalAlbumAssetEntityTable localAlbumAssetEntity =
|
||||
i7.$LocalAlbumAssetEntityTable(this);
|
||||
late final i8.$RemoteExifEntityTable remoteExifEntity =
|
||||
i8.$RemoteExifEntityTable(this);
|
||||
late final i9.$RemoteAlbumEntityTable remoteAlbumEntity =
|
||||
i9.$RemoteAlbumEntityTable(this);
|
||||
late final i10.$RemoteAlbumAssetEntityTable remoteAlbumAssetEntity =
|
||||
i10.$RemoteAlbumAssetEntityTable(this);
|
||||
late final i11.$RemoteAlbumUserEntityTable remoteAlbumUserEntity =
|
||||
i11.$RemoteAlbumUserEntityTable(this);
|
||||
late final i12.$MemoryEntityTable memoryEntity = i12.$MemoryEntityTable(this);
|
||||
late final i13.$MemoryAssetEntityTable memoryAssetEntity =
|
||||
i13.$MemoryAssetEntityTable(this);
|
||||
i14.MergedAssetDrift get mergedAssetDrift => i15.ReadDatabaseContainer(this)
|
||||
.accessor<i14.MergedAssetDrift>(i14.MergedAssetDrift.new);
|
||||
late final i5.$UserMetadataEntityTable userMetadataEntity =
|
||||
i5.$UserMetadataEntityTable(this);
|
||||
late final i6.$PartnerEntityTable partnerEntity =
|
||||
i6.$PartnerEntityTable(this);
|
||||
late final i7.$LocalAlbumEntityTable localAlbumEntity =
|
||||
i7.$LocalAlbumEntityTable(this);
|
||||
late final i8.$LocalAlbumAssetEntityTable localAlbumAssetEntity =
|
||||
i8.$LocalAlbumAssetEntityTable(this);
|
||||
late final i9.$RemoteExifEntityTable remoteExifEntity =
|
||||
i9.$RemoteExifEntityTable(this);
|
||||
late final i10.$RemoteAlbumEntityTable remoteAlbumEntity =
|
||||
i10.$RemoteAlbumEntityTable(this);
|
||||
late final i11.$RemoteAlbumAssetEntityTable remoteAlbumAssetEntity =
|
||||
i11.$RemoteAlbumAssetEntityTable(this);
|
||||
late final i12.$RemoteAlbumUserEntityTable remoteAlbumUserEntity =
|
||||
i12.$RemoteAlbumUserEntityTable(this);
|
||||
late final i13.$MemoryEntityTable memoryEntity = i13.$MemoryEntityTable(this);
|
||||
late final i14.$MemoryAssetEntityTable memoryAssetEntity =
|
||||
i14.$MemoryAssetEntityTable(this);
|
||||
late final i15.$StackEntityTable stackEntity = i15.$StackEntityTable(this);
|
||||
i16.MergedAssetDrift get mergedAssetDrift => i17.ReadDatabaseContainer(this)
|
||||
.accessor<i16.MergedAssetDrift>(i16.MergedAssetDrift.new);
|
||||
@override
|
||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
||||
@@ -68,6 +73,10 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
userEntity,
|
||||
remoteAssetEntity,
|
||||
localAssetEntity,
|
||||
i4.trLocalAssetUpdateChecksumSetIds,
|
||||
i4.trLocalAssetUpdateOldChecksumSetRemoteAssetLocalId,
|
||||
i4.trLocalAssetDeleteUpdateRemoteAssetLocalId,
|
||||
i4.trRemoteAssetInsertSetLocalId,
|
||||
i3.idxLocalAssetChecksum,
|
||||
i2.uQRemoteAssetOwnerChecksum,
|
||||
i2.idxRemoteAssetChecksum,
|
||||
@@ -80,7 +89,8 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
remoteAlbumAssetEntity,
|
||||
remoteAlbumUserEntity,
|
||||
memoryEntity,
|
||||
memoryAssetEntity
|
||||
memoryAssetEntity,
|
||||
stackEntity
|
||||
];
|
||||
@override
|
||||
i0.StreamQueryUpdateRules get streamUpdateRules =>
|
||||
@@ -93,6 +103,43 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
i0.TableUpdate('remote_asset_entity', kind: i0.UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName('remote_asset_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete),
|
||||
result: [
|
||||
i0.TableUpdate('local_asset_entity', kind: i0.UpdateKind.update),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName('local_asset_entity',
|
||||
limitUpdateKind: i0.UpdateKind.update),
|
||||
result: [
|
||||
i0.TableUpdate('local_asset_entity', kind: i0.UpdateKind.update),
|
||||
i0.TableUpdate('remote_asset_entity', kind: i0.UpdateKind.update),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName('local_asset_entity',
|
||||
limitUpdateKind: i0.UpdateKind.update),
|
||||
result: [
|
||||
i0.TableUpdate('remote_asset_entity', kind: i0.UpdateKind.update),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName('local_asset_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete),
|
||||
result: [
|
||||
i0.TableUpdate('remote_asset_entity', kind: i0.UpdateKind.update),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName('remote_asset_entity',
|
||||
limitUpdateKind: i0.UpdateKind.insert),
|
||||
result: [
|
||||
i0.TableUpdate('remote_asset_entity', kind: i0.UpdateKind.update),
|
||||
i0.TableUpdate('local_asset_entity', kind: i0.UpdateKind.update),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName('user_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete),
|
||||
@@ -205,6 +252,13 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
i0.TableUpdate('memory_asset_entity', kind: i0.UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName('user_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete),
|
||||
result: [
|
||||
i0.TableUpdate('stack_entity', kind: i0.UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
@override
|
||||
@@ -221,25 +275,27 @@ class $DriftManager {
|
||||
i2.$$RemoteAssetEntityTableTableManager(_db, _db.remoteAssetEntity);
|
||||
i3.$$LocalAssetEntityTableTableManager get localAssetEntity =>
|
||||
i3.$$LocalAssetEntityTableTableManager(_db, _db.localAssetEntity);
|
||||
i4.$$UserMetadataEntityTableTableManager get userMetadataEntity =>
|
||||
i4.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity);
|
||||
i5.$$PartnerEntityTableTableManager get partnerEntity =>
|
||||
i5.$$PartnerEntityTableTableManager(_db, _db.partnerEntity);
|
||||
i6.$$LocalAlbumEntityTableTableManager get localAlbumEntity =>
|
||||
i6.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity);
|
||||
i7.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i7
|
||||
i5.$$UserMetadataEntityTableTableManager get userMetadataEntity =>
|
||||
i5.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity);
|
||||
i6.$$PartnerEntityTableTableManager get partnerEntity =>
|
||||
i6.$$PartnerEntityTableTableManager(_db, _db.partnerEntity);
|
||||
i7.$$LocalAlbumEntityTableTableManager get localAlbumEntity =>
|
||||
i7.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity);
|
||||
i8.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i8
|
||||
.$$LocalAlbumAssetEntityTableTableManager(_db, _db.localAlbumAssetEntity);
|
||||
i8.$$RemoteExifEntityTableTableManager get remoteExifEntity =>
|
||||
i8.$$RemoteExifEntityTableTableManager(_db, _db.remoteExifEntity);
|
||||
i9.$$RemoteAlbumEntityTableTableManager get remoteAlbumEntity =>
|
||||
i9.$$RemoteAlbumEntityTableTableManager(_db, _db.remoteAlbumEntity);
|
||||
i10.$$RemoteAlbumAssetEntityTableTableManager get remoteAlbumAssetEntity =>
|
||||
i10.$$RemoteAlbumAssetEntityTableTableManager(
|
||||
i9.$$RemoteExifEntityTableTableManager get remoteExifEntity =>
|
||||
i9.$$RemoteExifEntityTableTableManager(_db, _db.remoteExifEntity);
|
||||
i10.$$RemoteAlbumEntityTableTableManager get remoteAlbumEntity =>
|
||||
i10.$$RemoteAlbumEntityTableTableManager(_db, _db.remoteAlbumEntity);
|
||||
i11.$$RemoteAlbumAssetEntityTableTableManager get remoteAlbumAssetEntity =>
|
||||
i11.$$RemoteAlbumAssetEntityTableTableManager(
|
||||
_db, _db.remoteAlbumAssetEntity);
|
||||
i11.$$RemoteAlbumUserEntityTableTableManager get remoteAlbumUserEntity => i11
|
||||
i12.$$RemoteAlbumUserEntityTableTableManager get remoteAlbumUserEntity => i12
|
||||
.$$RemoteAlbumUserEntityTableTableManager(_db, _db.remoteAlbumUserEntity);
|
||||
i12.$$MemoryEntityTableTableManager get memoryEntity =>
|
||||
i12.$$MemoryEntityTableTableManager(_db, _db.memoryEntity);
|
||||
i13.$$MemoryAssetEntityTableTableManager get memoryAssetEntity =>
|
||||
i13.$$MemoryAssetEntityTableTableManager(_db, _db.memoryAssetEntity);
|
||||
i13.$$MemoryEntityTableTableManager get memoryEntity =>
|
||||
i13.$$MemoryEntityTableTableManager(_db, _db.memoryEntity);
|
||||
i14.$$MemoryAssetEntityTableTableManager get memoryAssetEntity =>
|
||||
i14.$$MemoryAssetEntityTableTableManager(_db, _db.memoryAssetEntity);
|
||||
i15.$$StackEntityTableTableManager get stackEntity =>
|
||||
i15.$$StackEntityTableTableManager(_db, _db.stackEntity);
|
||||
}
|
||||
|
||||
@@ -282,6 +282,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
durationInSeconds: Value(asset.durationInSeconds),
|
||||
id: asset.id,
|
||||
checksum: const Value(null),
|
||||
remoteId: const Value(null),
|
||||
);
|
||||
batch.insert<$LocalAssetEntityTable, LocalAssetEntityData>(
|
||||
_db.localAssetEntity,
|
||||
|
||||
@@ -9,23 +9,10 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
|
||||
const DriftLocalAssetRepository(this._db) : super(_db);
|
||||
|
||||
Stream<LocalAsset?> watchAsset(String id) {
|
||||
final query = _db.localAssetEntity
|
||||
.select()
|
||||
.addColumns([_db.localAssetEntity.id]).join([
|
||||
leftOuterJoin(
|
||||
_db.remoteAssetEntity,
|
||||
_db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum),
|
||||
useColumns: false,
|
||||
),
|
||||
])
|
||||
..where(_db.localAssetEntity.id.equals(id));
|
||||
final query = _db.localAssetEntity.select()
|
||||
..where((row) => row.id.equals(id));
|
||||
|
||||
return query.map((row) {
|
||||
final asset = row.readTable(_db.localAssetEntity).toDto();
|
||||
return asset.copyWith(
|
||||
remoteId: row.read(_db.remoteAssetEntity.id),
|
||||
);
|
||||
}).watchSingleOrNull();
|
||||
return query.map((row) => row.toDto()).watchSingleOrNull();
|
||||
}
|
||||
|
||||
Future<void> updateHashes(Iterable<LocalAsset> hashes) {
|
||||
|
||||
@@ -13,24 +13,27 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
|
||||
final Drift _db;
|
||||
const RemoteAssetRepository(this._db) : super(_db);
|
||||
|
||||
Stream<RemoteAsset?> watchAsset(String id) {
|
||||
final query = _db.remoteAssetEntity
|
||||
.select()
|
||||
.addColumns([_db.localAssetEntity.id]).join([
|
||||
leftOuterJoin(
|
||||
_db.localAssetEntity,
|
||||
_db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum),
|
||||
useColumns: false,
|
||||
),
|
||||
])
|
||||
..where(_db.remoteAssetEntity.id.equals(id));
|
||||
/// For testing purposes
|
||||
Future<List<RemoteAsset>> getSome(String userId) {
|
||||
final query = _db.remoteAssetEntity.select()
|
||||
..where(
|
||||
(row) =>
|
||||
_db.remoteAssetEntity.ownerId.equals(userId) &
|
||||
_db.remoteAssetEntity.deletedAt.isNull() &
|
||||
_db.remoteAssetEntity.visibility
|
||||
.equalsValue(AssetVisibility.timeline),
|
||||
)
|
||||
..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
|
||||
..limit(10);
|
||||
|
||||
return query.map((row) {
|
||||
final asset = row.readTable(_db.remoteAssetEntity).toDto();
|
||||
return asset.copyWith(
|
||||
localId: row.read(_db.localAssetEntity.id),
|
||||
);
|
||||
}).watchSingleOrNull();
|
||||
return query.map((row) => row.toDto()).get();
|
||||
}
|
||||
|
||||
Stream<RemoteAsset?> watchAsset(String id) {
|
||||
final query = _db.remoteAssetEntity.select()
|
||||
..where((row) => row.id.equals(id));
|
||||
|
||||
return query.map((row) => row.toDto()).watchSingleOrNull();
|
||||
}
|
||||
|
||||
Future<ExifInfo?> getExif(String id) {
|
||||
|
||||
30
mobile/lib/infrastructure/repositories/stack.repository.dart
Normal file
30
mobile/lib/infrastructure/repositories/stack.repository.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/domain/models/stack.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
|
||||
class DriftStackRepository extends DriftDatabaseRepository {
|
||||
final Drift _db;
|
||||
const DriftStackRepository(this._db) : super(_db);
|
||||
|
||||
Future<List<Stack>> getAll(String userId) {
|
||||
final query = _db.stackEntity.select()
|
||||
..where((e) => e.ownerId.equals(userId));
|
||||
|
||||
return query.map((stack) {
|
||||
return stack.toDto();
|
||||
}).get();
|
||||
}
|
||||
}
|
||||
|
||||
extension on StackEntityData {
|
||||
Stack toDto() {
|
||||
return Stack(
|
||||
id: id,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
ownerId: ownerId,
|
||||
primaryAssetId: primaryAssetId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,8 @@ class SyncApiRepository {
|
||||
SyncRequestType.albumToAssetsV1,
|
||||
SyncRequestType.memoriesV1,
|
||||
SyncRequestType.memoryToAssetsV1,
|
||||
SyncRequestType.stacksV1,
|
||||
SyncRequestType.partnerStacksV1,
|
||||
],
|
||||
).toJson(),
|
||||
);
|
||||
@@ -163,6 +165,11 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
||||
SyncEntityType.memoryDeleteV1: SyncMemoryDeleteV1.fromJson,
|
||||
SyncEntityType.memoryToAssetV1: SyncMemoryAssetV1.fromJson,
|
||||
SyncEntityType.memoryToAssetDeleteV1: SyncMemoryAssetDeleteV1.fromJson,
|
||||
SyncEntityType.stackV1: SyncStackV1.fromJson,
|
||||
SyncEntityType.stackDeleteV1: SyncStackDeleteV1.fromJson,
|
||||
SyncEntityType.partnerStackV1: SyncStackV1.fromJson,
|
||||
SyncEntityType.partnerStackBackfillV1: SyncStackV1.fromJson,
|
||||
SyncEntityType.partnerStackDeleteV1: SyncStackDeleteV1.fromJson,
|
||||
};
|
||||
|
||||
class _SyncAckV1 {
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -69,8 +70,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
_logger.severe('Error: SyncPartnerDeleteV1', error, stackTrace);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: SyncPartnerDeleteV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -92,8 +93,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
_logger.severe('Error: SyncPartnerV1', error, stackTrace);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: SyncPartnerV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -104,10 +105,10 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
}) async {
|
||||
try {
|
||||
await _db.remoteAssetEntity.deleteWhere(
|
||||
(row) => row.id.isIn(data.map((error) => error.assetId)),
|
||||
(row) => row.id.isIn(data.map((e) => e.assetId)),
|
||||
);
|
||||
} catch (error, stackTrace) {
|
||||
_logger.severe('Error: deleteAssetsV1 - $debugLabel', error, stackTrace);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: deleteAssetsV1 - $debugLabel', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -142,8 +143,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
_logger.severe('Error: updateAssetsV1 - $debugLabel', error, stackTrace);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: updateAssetsV1 - $debugLabel', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -186,11 +187,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
} catch (error, stack) {
|
||||
_logger.severe(
|
||||
'Error: updateAssetsExifV1 - $debugLabel',
|
||||
error,
|
||||
stackTrace,
|
||||
stack,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
@@ -201,8 +202,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
await _db.remoteAlbumEntity.deleteWhere(
|
||||
(row) => row.id.isIn(data.map((e) => e.albumId)),
|
||||
);
|
||||
} catch (error, stackTrace) {
|
||||
_logger.severe('Error: deleteAlbumsV1', error, stackTrace);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: deleteAlbumsV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -229,8 +230,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
_logger.severe('Error: updateAlbumsV1', error, stackTrace);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: updateAlbumsV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -248,8 +249,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
_logger.severe('Error: deleteAlbumUsersV1', error, stackTrace);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: deleteAlbumUsersV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -275,11 +276,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
} catch (error, stack) {
|
||||
_logger.severe(
|
||||
'Error: updateAlbumUsersV1 - $debugLabel',
|
||||
error,
|
||||
stackTrace,
|
||||
stack,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
@@ -300,8 +301,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
_logger.severe('Error: deleteAlbumToAssetsV1', error, stackTrace);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: deleteAlbumToAssetsV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -325,11 +326,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
} catch (error, stack) {
|
||||
_logger.severe(
|
||||
'Error: updateAlbumToAssetsV1 - $debugLabel',
|
||||
error,
|
||||
stackTrace,
|
||||
stack,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
@@ -359,8 +360,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
_logger.severe('Error: updateMemoriesV1', error, stackTrace);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: updateMemoriesV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -370,8 +371,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
await _db.memoryEntity.deleteWhere(
|
||||
(row) => row.id.isIn(data.map((e) => e.memoryId)),
|
||||
);
|
||||
} catch (error, stackTrace) {
|
||||
_logger.severe('Error: deleteMemoriesV1', error, stackTrace);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: deleteMemoriesV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -392,8 +393,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
_logger.severe('Error: updateMemoryAssetsV1', error, stackTrace);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: updateMemoryAssetsV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -413,8 +414,49 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
_logger.severe('Error: deleteMemoryAssetsV1', error, stackTrace);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: deleteMemoryAssetsV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateStacksV1(
|
||||
Iterable<SyncStackV1> data, {
|
||||
String debugLabel = 'user',
|
||||
}) async {
|
||||
try {
|
||||
await _db.batch((batch) {
|
||||
for (final stack in data) {
|
||||
final companion = StackEntityCompanion(
|
||||
createdAt: Value(stack.createdAt),
|
||||
updatedAt: Value(stack.updatedAt),
|
||||
ownerId: Value(stack.ownerId),
|
||||
primaryAssetId: Value(stack.primaryAssetId),
|
||||
);
|
||||
|
||||
batch.insert(
|
||||
_db.stackEntity,
|
||||
companion.copyWith(id: Value(stack.id)),
|
||||
onConflict: DoUpdate((_) => companion),
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: updateStacksV1 - $debugLabel', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteStacksV1(
|
||||
Iterable<SyncStackDeleteV1> data, {
|
||||
String debugLabel = 'user',
|
||||
}) async {
|
||||
try {
|
||||
await _db.stackEntity.deleteWhere(
|
||||
(row) => row.id.isIn(data.map((e) => e.stackId)),
|
||||
);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: deleteStacksV1 - $debugLabel', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -467,7 +509,7 @@ extension on String {
|
||||
Duration? toDuration() {
|
||||
try {
|
||||
final parts = split(':')
|
||||
.map((error) => double.parse(error).toInt())
|
||||
.map((e) => double.parse(e).toInt())
|
||||
.toList(growable: false);
|
||||
|
||||
return Duration(hours: parts[0], minutes: parts[1], seconds: parts[2]);
|
||||
|
||||
@@ -104,7 +104,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
).get();
|
||||
}
|
||||
|
||||
Stream<List<Bucket>> watchLocalBucket(
|
||||
Stream<List<Bucket>> watchLocalAlbumBucket(
|
||||
String albumId, {
|
||||
GroupAssetsBy groupBy = GroupAssetsBy.day,
|
||||
}) {
|
||||
@@ -137,7 +137,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
}).watch();
|
||||
}
|
||||
|
||||
Future<List<BaseAsset>> getLocalBucketAssets(
|
||||
Future<List<BaseAsset>> getLocalAlbumBucketAssets(
|
||||
String albumId, {
|
||||
required int offset,
|
||||
required int count,
|
||||
@@ -158,7 +158,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
.get();
|
||||
}
|
||||
|
||||
Stream<List<Bucket>> watchRemoteBucket(
|
||||
Stream<List<Bucket>> watchRemoteAlbumBucket(
|
||||
String albumId, {
|
||||
GroupAssetsBy groupBy = GroupAssetsBy.day,
|
||||
}) {
|
||||
@@ -192,7 +192,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
}).watch();
|
||||
}
|
||||
|
||||
Future<List<BaseAsset>> getRemoteBucketAssets(
|
||||
Future<List<BaseAsset>> getRemoteAlbumBucketAssets(
|
||||
String albumId, {
|
||||
required int offset,
|
||||
required int count,
|
||||
@@ -213,6 +213,317 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
.map((row) => row.readTable(_db.remoteAssetEntity).toDto())
|
||||
.get();
|
||||
}
|
||||
|
||||
Stream<List<Bucket>> watchFavoriteBucket(
|
||||
String userId, {
|
||||
GroupAssetsBy groupBy = GroupAssetsBy.day,
|
||||
}) {
|
||||
if (groupBy == GroupAssetsBy.none) {
|
||||
return _db.remoteAssetEntity
|
||||
.count(
|
||||
where: (row) =>
|
||||
row.isFavorite.equals(true) & row.ownerId.equals(userId),
|
||||
)
|
||||
.map(_generateBuckets)
|
||||
.watchSingle();
|
||||
}
|
||||
|
||||
final assetCountExp = _db.remoteAssetEntity.id.count();
|
||||
final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
|
||||
|
||||
final query = _db.remoteAssetEntity.selectOnly()
|
||||
..addColumns([assetCountExp, dateExp])
|
||||
..where(
|
||||
_db.remoteAssetEntity.ownerId.equals(userId) &
|
||||
_db.remoteAssetEntity.isFavorite.equals(true),
|
||||
)
|
||||
..groupBy([dateExp])
|
||||
..orderBy([OrderingTerm.desc(dateExp)]);
|
||||
|
||||
return query.map((row) {
|
||||
final timeline = row.read(dateExp)!.dateFmt(groupBy);
|
||||
final assetCount = row.read(assetCountExp)!;
|
||||
return TimeBucket(date: timeline, assetCount: assetCount);
|
||||
}).watch();
|
||||
}
|
||||
|
||||
Future<List<BaseAsset>> getFavoriteBucketAssets(
|
||||
String userId, {
|
||||
required int offset,
|
||||
required int count,
|
||||
}) {
|
||||
final query = _db.remoteAssetEntity.select()
|
||||
..where(
|
||||
(row) => row.isFavorite.equals(true) & row.ownerId.equals(userId),
|
||||
)
|
||||
..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
|
||||
..limit(count, offset: offset);
|
||||
|
||||
return query.map((row) => row.toDto()).get();
|
||||
}
|
||||
|
||||
Stream<List<Bucket>> watchTrashBucket(
|
||||
String userId, {
|
||||
GroupAssetsBy groupBy = GroupAssetsBy.day,
|
||||
}) {
|
||||
if (groupBy == GroupAssetsBy.none) {
|
||||
return _db.remoteAssetEntity
|
||||
.count(
|
||||
where: (row) =>
|
||||
row.deletedAt.isNotNull() & row.ownerId.equals(userId),
|
||||
)
|
||||
.map(_generateBuckets)
|
||||
.watchSingle();
|
||||
}
|
||||
|
||||
final assetCountExp = _db.remoteAssetEntity.id.count();
|
||||
final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
|
||||
|
||||
final query = _db.remoteAssetEntity.selectOnly()
|
||||
..addColumns([assetCountExp, dateExp])
|
||||
..where(
|
||||
_db.remoteAssetEntity.ownerId.equals(userId) &
|
||||
_db.remoteAssetEntity.deletedAt.isNotNull(),
|
||||
)
|
||||
..groupBy([dateExp])
|
||||
..orderBy([OrderingTerm.desc(dateExp)]);
|
||||
|
||||
return query.map((row) {
|
||||
final timeline = row.read(dateExp)!.dateFmt(groupBy);
|
||||
final assetCount = row.read(assetCountExp)!;
|
||||
return TimeBucket(date: timeline, assetCount: assetCount);
|
||||
}).watch();
|
||||
}
|
||||
|
||||
Future<List<BaseAsset>> getTrashBucketAssets(
|
||||
String userId, {
|
||||
required int offset,
|
||||
required int count,
|
||||
}) {
|
||||
final query = _db.remoteAssetEntity.select()
|
||||
..where(
|
||||
(row) => row.deletedAt.isNotNull() & row.ownerId.equals(userId),
|
||||
)
|
||||
..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
|
||||
..limit(count, offset: offset);
|
||||
|
||||
return query.map((row) => row.toDto()).get();
|
||||
}
|
||||
|
||||
Stream<List<Bucket>> watchArchiveBucket(
|
||||
String userId, {
|
||||
GroupAssetsBy groupBy = GroupAssetsBy.day,
|
||||
}) {
|
||||
if (groupBy == GroupAssetsBy.none) {
|
||||
return _db.remoteAssetEntity
|
||||
.count(
|
||||
where: (row) =>
|
||||
row.visibility.equalsValue(AssetVisibility.archive) &
|
||||
row.ownerId.equals(userId),
|
||||
)
|
||||
.map(_generateBuckets)
|
||||
.watchSingle();
|
||||
}
|
||||
|
||||
final assetCountExp = _db.remoteAssetEntity.id.count();
|
||||
final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
|
||||
|
||||
final query = _db.remoteAssetEntity.selectOnly()
|
||||
..addColumns([assetCountExp, dateExp])
|
||||
..where(
|
||||
_db.remoteAssetEntity.ownerId.equals(userId) &
|
||||
_db.remoteAssetEntity.visibility
|
||||
.equalsValue(AssetVisibility.archive),
|
||||
)
|
||||
..groupBy([dateExp])
|
||||
..orderBy([OrderingTerm.desc(dateExp)]);
|
||||
|
||||
return query.map((row) {
|
||||
final timeline = row.read(dateExp)!.dateFmt(groupBy);
|
||||
final assetCount = row.read(assetCountExp)!;
|
||||
return TimeBucket(date: timeline, assetCount: assetCount);
|
||||
}).watch();
|
||||
}
|
||||
|
||||
Future<List<BaseAsset>> getArchiveBucketAssets(
|
||||
String userId, {
|
||||
required int offset,
|
||||
required int count,
|
||||
}) {
|
||||
final query = _db.remoteAssetEntity.select()
|
||||
..where(
|
||||
(row) =>
|
||||
row.ownerId.equals(userId) &
|
||||
row.visibility.equalsValue(AssetVisibility.archive),
|
||||
)
|
||||
..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
|
||||
..limit(count, offset: offset);
|
||||
|
||||
return query.map((row) => row.toDto()).get();
|
||||
}
|
||||
|
||||
Stream<List<Bucket>> watchLockedFolderBucket(
|
||||
String userId, {
|
||||
GroupAssetsBy groupBy = GroupAssetsBy.day,
|
||||
}) {
|
||||
if (groupBy == GroupAssetsBy.none) {
|
||||
return _db.remoteAssetEntity
|
||||
.count(
|
||||
where: (row) =>
|
||||
row.visibility.equalsValue(AssetVisibility.locked) &
|
||||
row.ownerId.equals(userId),
|
||||
)
|
||||
.map(_generateBuckets)
|
||||
.watchSingle();
|
||||
}
|
||||
|
||||
final assetCountExp = _db.remoteAssetEntity.id.count();
|
||||
final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
|
||||
|
||||
final query = _db.remoteAssetEntity.selectOnly()
|
||||
..addColumns([assetCountExp, dateExp])
|
||||
..where(
|
||||
_db.remoteAssetEntity.ownerId.equals(userId) &
|
||||
_db.remoteAssetEntity.visibility
|
||||
.equalsValue(AssetVisibility.locked),
|
||||
)
|
||||
..groupBy([dateExp])
|
||||
..orderBy([OrderingTerm.desc(dateExp)]);
|
||||
|
||||
return query.map((row) {
|
||||
final timeline = row.read(dateExp)!.dateFmt(groupBy);
|
||||
final assetCount = row.read(assetCountExp)!;
|
||||
return TimeBucket(date: timeline, assetCount: assetCount);
|
||||
}).watch();
|
||||
}
|
||||
|
||||
Future<List<BaseAsset>> getLockedFolderBucketAssets(
|
||||
String userId, {
|
||||
required int offset,
|
||||
required int count,
|
||||
}) {
|
||||
final query = _db.remoteAssetEntity.select()
|
||||
..where(
|
||||
(row) =>
|
||||
row.visibility.equalsValue(AssetVisibility.locked) &
|
||||
row.ownerId.equals(userId),
|
||||
)
|
||||
..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
|
||||
..limit(count, offset: offset);
|
||||
|
||||
return query.map((row) => row.toDto()).get();
|
||||
}
|
||||
|
||||
Stream<List<Bucket>> watchVideoBucket(
|
||||
String userId, {
|
||||
GroupAssetsBy groupBy = GroupAssetsBy.day,
|
||||
}) {
|
||||
if (groupBy == GroupAssetsBy.none) {
|
||||
return _db.remoteAssetEntity
|
||||
.count(
|
||||
where: (row) =>
|
||||
row.type.equalsValue(AssetType.video) &
|
||||
row.visibility.equalsValue(AssetVisibility.timeline) &
|
||||
row.ownerId.equals(userId),
|
||||
)
|
||||
.map(_generateBuckets)
|
||||
.watchSingle();
|
||||
}
|
||||
|
||||
final assetCountExp = _db.remoteAssetEntity.id.count();
|
||||
final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
|
||||
|
||||
final query = _db.remoteAssetEntity.selectOnly()
|
||||
..addColumns([assetCountExp, dateExp])
|
||||
..where(
|
||||
_db.remoteAssetEntity.ownerId.equals(userId) &
|
||||
_db.remoteAssetEntity.type.equalsValue(AssetType.video) &
|
||||
_db.remoteAssetEntity.visibility
|
||||
.equalsValue(AssetVisibility.timeline),
|
||||
)
|
||||
..groupBy([dateExp])
|
||||
..orderBy([OrderingTerm.desc(dateExp)]);
|
||||
|
||||
return query.map((row) {
|
||||
final timeline = row.read(dateExp)!.dateFmt(groupBy);
|
||||
final assetCount = row.read(assetCountExp)!;
|
||||
return TimeBucket(date: timeline, assetCount: assetCount);
|
||||
}).watch();
|
||||
}
|
||||
|
||||
Future<List<BaseAsset>> getVideoBucketAssets(
|
||||
String userId, {
|
||||
required int offset,
|
||||
required int count,
|
||||
}) {
|
||||
final query = _db.remoteAssetEntity.select()
|
||||
..where(
|
||||
(row) =>
|
||||
_db.remoteAssetEntity.type.equalsValue(AssetType.video) &
|
||||
_db.remoteAssetEntity.visibility
|
||||
.equalsValue(AssetVisibility.timeline) &
|
||||
_db.remoteAssetEntity.ownerId.equals(userId),
|
||||
)
|
||||
..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
|
||||
..limit(count, offset: offset);
|
||||
|
||||
return query.map((row) => row.toDto()).get();
|
||||
}
|
||||
|
||||
Stream<List<Bucket>> watchRemoteBucket(
|
||||
List<String> userIds, {
|
||||
GroupAssetsBy groupBy = GroupAssetsBy.day,
|
||||
}) {
|
||||
if (groupBy == GroupAssetsBy.none) {
|
||||
return _db.remoteAssetEntity
|
||||
.count(
|
||||
where: (row) =>
|
||||
row.deletedAt.isNull() &
|
||||
row.visibility.equalsValue(AssetVisibility.timeline) &
|
||||
row.ownerId.isIn(userIds),
|
||||
)
|
||||
.map(_generateBuckets)
|
||||
.watchSingle();
|
||||
}
|
||||
|
||||
final assetCountExp = _db.remoteAssetEntity.id.count();
|
||||
final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
|
||||
|
||||
final query = _db.remoteAssetEntity.selectOnly()
|
||||
..addColumns([assetCountExp, dateExp])
|
||||
..where(
|
||||
_db.remoteAssetEntity.deletedAt.isNull() &
|
||||
_db.remoteAssetEntity.visibility
|
||||
.equalsValue(AssetVisibility.timeline) &
|
||||
_db.remoteAssetEntity.ownerId.isIn(userIds),
|
||||
)
|
||||
..groupBy([dateExp])
|
||||
..orderBy([OrderingTerm.desc(dateExp)]);
|
||||
|
||||
return query.map((row) {
|
||||
final timeline = row.read(dateExp)!.dateFmt(groupBy);
|
||||
final assetCount = row.read(assetCountExp)!;
|
||||
return TimeBucket(date: timeline, assetCount: assetCount);
|
||||
}).watch();
|
||||
}
|
||||
|
||||
Future<List<BaseAsset>> getRemoteBucketAssets(
|
||||
List<String> userIds, {
|
||||
required int offset,
|
||||
required int count,
|
||||
}) {
|
||||
final query = _db.remoteAssetEntity.select()
|
||||
..where(
|
||||
(row) =>
|
||||
row.deletedAt.isNull() &
|
||||
row.visibility.equalsValue(AssetVisibility.timeline) &
|
||||
row.ownerId.isIn(userIds),
|
||||
)
|
||||
..orderBy([(t) => OrderingTerm.desc(t.createdAt)])
|
||||
..limit(count, offset: offset);
|
||||
|
||||
return query.map((row) => row.toDto()).get();
|
||||
}
|
||||
}
|
||||
|
||||
extension on Expression<DateTime> {
|
||||
|
||||
@@ -118,7 +118,7 @@ class TabShellPage extends ConsumerWidget {
|
||||
const MainTimelineRoute(),
|
||||
SearchRoute(),
|
||||
const DriftAlbumsRoute(),
|
||||
const LibraryRoute(),
|
||||
const DriftLibraryRoute(),
|
||||
],
|
||||
duration: const Duration(milliseconds: 600),
|
||||
transitionBuilder: (context, child, animation) => FadeTransition(
|
||||
|
||||
33
mobile/lib/presentation/pages/dev/drift_archive.page.dart
Normal file
33
mobile/lib/presentation/pages/dev/drift_archive.page.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/user.provider.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftArchivePage extends StatelessWidget {
|
||||
const DriftArchivePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
timelineServiceProvider.overrideWith(
|
||||
(ref) {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user == null) {
|
||||
throw Exception('User must be logged in to access archive');
|
||||
}
|
||||
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).archive(user.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
),
|
||||
],
|
||||
child: const Timeline(),
|
||||
);
|
||||
}
|
||||
}
|
||||
33
mobile/lib/presentation/pages/dev/drift_favorite.page.dart
Normal file
33
mobile/lib/presentation/pages/dev/drift_favorite.page.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/user.provider.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftFavoritePage extends StatelessWidget {
|
||||
const DriftFavoritePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
timelineServiceProvider.overrideWith(
|
||||
(ref) {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user == null) {
|
||||
throw Exception('User must be logged in to access favorite');
|
||||
}
|
||||
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).favorite(user.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
),
|
||||
],
|
||||
child: const Timeline(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/user.provider.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftLockedFolderPage extends StatelessWidget {
|
||||
const DriftLockedFolderPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
timelineServiceProvider.overrideWith(
|
||||
(ref) {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user == null) {
|
||||
throw Exception('User must be logged in to access locked folder');
|
||||
}
|
||||
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).lockedFolder(user.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
),
|
||||
],
|
||||
child: const Timeline(),
|
||||
);
|
||||
}
|
||||
}
|
||||
33
mobile/lib/presentation/pages/dev/drift_trash.page.dart
Normal file
33
mobile/lib/presentation/pages/dev/drift_trash.page.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/user.provider.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftTrashPage extends StatelessWidget {
|
||||
const DriftTrashPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
timelineServiceProvider.overrideWith(
|
||||
(ref) {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user == null) {
|
||||
throw Exception('User must be logged in to access trash');
|
||||
}
|
||||
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).trash(user.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
),
|
||||
],
|
||||
child: const Timeline(),
|
||||
);
|
||||
}
|
||||
}
|
||||
33
mobile/lib/presentation/pages/dev/drift_video.page.dart
Normal file
33
mobile/lib/presentation/pages/dev/drift_video.page.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/user.provider.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftVideoPage extends StatelessWidget {
|
||||
const DriftVideoPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
timelineServiceProvider.overrideWith(
|
||||
(ref) {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user == null) {
|
||||
throw Exception('User must be logged in to video');
|
||||
}
|
||||
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).video(user.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
),
|
||||
],
|
||||
child: const Timeline(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,43 @@ import 'package:drift/drift.dart' hide Column;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/dev_logger.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
final _features = [
|
||||
_Feature(
|
||||
name: 'Selection Mode Timeline',
|
||||
icon: Icons.developer_mode_rounded,
|
||||
onTap: (ctx, ref) async {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user == null) {
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
final assets =
|
||||
await ref.read(remoteAssetRepositoryProvider).getSome(user.id);
|
||||
|
||||
final selectedAssets = await ctx.pushRoute<Set<BaseAsset>>(
|
||||
DriftAssetSelectionTimelineRoute(
|
||||
lockedSelectionAssets: assets.toSet(),
|
||||
),
|
||||
);
|
||||
|
||||
DLog.log(
|
||||
"Selected ${selectedAssets?.length ?? 0} assets",
|
||||
);
|
||||
|
||||
return Future.value();
|
||||
},
|
||||
),
|
||||
_Feature(
|
||||
name: 'Sync Local',
|
||||
icon: Icons.photo_album_rounded,
|
||||
@@ -66,6 +94,9 @@ final _features = [
|
||||
await db.remoteAlbumEntity.deleteAll();
|
||||
await db.remoteAlbumUserEntity.deleteAll();
|
||||
await db.remoteAlbumAssetEntity.deleteAll();
|
||||
await db.memoryEntity.deleteAll();
|
||||
await db.memoryAssetEntity.deleteAll();
|
||||
await db.stackEntity.deleteAll();
|
||||
},
|
||||
),
|
||||
_Feature(
|
||||
@@ -96,6 +127,11 @@ final _features = [
|
||||
icon: Icons.timeline_rounded,
|
||||
onTap: (ctx, _) => ctx.pushRoute(const TabShellRoute()),
|
||||
),
|
||||
_Feature(
|
||||
name: 'Video',
|
||||
icon: Icons.video_collection_outlined,
|
||||
onTap: (ctx, _) => ctx.pushRoute(const DriftVideoRoute()),
|
||||
),
|
||||
];
|
||||
|
||||
@RoutePage()
|
||||
|
||||
@@ -162,6 +162,10 @@ final _remoteStats = [
|
||||
name: 'Memories Assets',
|
||||
load: (db) => db.managers.memoryAssetEntity.count(),
|
||||
),
|
||||
_Stat(
|
||||
name: 'Stacks',
|
||||
load: (db) => db.managers.stackEntity.count(),
|
||||
),
|
||||
];
|
||||
|
||||
@RoutePage()
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftAssetSelectionTimelinePage extends ConsumerWidget {
|
||||
final Set<BaseAsset> lockedSelectionAssets;
|
||||
const DriftAssetSelectionTimelinePage({
|
||||
super.key,
|
||||
this.lockedSelectionAssets = const {},
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
multiSelectProvider.overrideWith(
|
||||
() => MultiSelectNotifier(
|
||||
MultiSelectState(
|
||||
selectedAssets: {},
|
||||
lockedSelectionAssets: lockedSelectionAssets,
|
||||
forceEnable: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
timelineServiceProvider.overrideWith(
|
||||
(ref) {
|
||||
final timelineUsers =
|
||||
ref.watch(timelineUsersProvider).valueOrNull ?? [];
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).remoteAssets(timelineUsers);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
),
|
||||
],
|
||||
child: const Timeline(),
|
||||
);
|
||||
}
|
||||
}
|
||||
501
mobile/lib/presentation/pages/drift_library.page.dart
Normal file
501
mobile/lib/presentation/pages/drift_library.page.dart
Normal file
@@ -0,0 +1,501 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/partner.provider.dart';
|
||||
import 'package:immich_mobile/providers/search/people.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
|
||||
import 'package:immich_mobile/widgets/common/user_avatar.dart';
|
||||
import 'package:immich_mobile/widgets/map/map_thumbnail.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftLibraryPage extends ConsumerWidget {
|
||||
const DriftLibraryPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return const Scaffold(
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
ImmichSliverAppBar(),
|
||||
_ActionButtonGrid(),
|
||||
_CollectionCards(),
|
||||
_QuickAccessButtonList(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ActionButtonGrid extends ConsumerWidget {
|
||||
const _ActionButtonGrid();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isTrashEnable = ref.watch(
|
||||
serverInfoProvider.select((state) => state.serverFeatures.trash),
|
||||
);
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.only(left: 16, top: 16, right: 16, bottom: 12),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
_ActionButton(
|
||||
icon: Icons.favorite_outline_rounded,
|
||||
onTap: () => context.pushRoute(const DriftFavoriteRoute()),
|
||||
label: 'favorites'.t(context: context),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_ActionButton(
|
||||
icon: Icons.archive_outlined,
|
||||
onTap: () => context.pushRoute(const DriftArchiveRoute()),
|
||||
label: 'archived'.t(context: context),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
_ActionButton(
|
||||
icon: Icons.link_outlined,
|
||||
onTap: () => context.pushRoute(const SharedLinkRoute()),
|
||||
label: 'shared_links'.t(context: context),
|
||||
),
|
||||
isTrashEnable
|
||||
? const SizedBox(width: 8)
|
||||
: const SizedBox.shrink(),
|
||||
isTrashEnable
|
||||
? _ActionButton(
|
||||
icon: Icons.delete_outline_rounded,
|
||||
onTap: () => context.pushRoute(const DriftTrashRoute()),
|
||||
label: 'trash'.t(context: context),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ActionButton extends StatelessWidget {
|
||||
const _ActionButton({
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
required this.label,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final VoidCallback onTap;
|
||||
final String label;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: onTap,
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(left: 4.0),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: context.colorScheme.onSurface,
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
style: FilledButton.styleFrom(
|
||||
elevation: 0,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||
backgroundColor: context.colorScheme.surfaceContainerLow,
|
||||
alignment: Alignment.centerLeft,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(25)),
|
||||
side: BorderSide(
|
||||
color: context.colorScheme.onSurface.withAlpha(10),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
icon: Icon(
|
||||
icon,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CollectionCards extends StatelessWidget {
|
||||
const _CollectionCards();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_PeopleCollectionCard(),
|
||||
_PlacesCollectionCard(),
|
||||
_LocalAlbumsCollectionCard(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PeopleCollectionCard extends ConsumerWidget {
|
||||
const _PeopleCollectionCard();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final people = ref.watch(getAllPeopleProvider);
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isTablet = constraints.maxWidth > 600;
|
||||
final widthFactor = isTablet ? 0.25 : 0.5;
|
||||
final size = context.width * widthFactor - 20.0;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => context.pushRoute(const PeopleCollectionRoute()),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
height: size,
|
||||
width: size,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.colorScheme.primary.withAlpha(30),
|
||||
context.colorScheme.primary.withAlpha(25),
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: people.widgetWhen(
|
||||
onLoading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
onData: (people) {
|
||||
return GridView.count(
|
||||
crossAxisCount: 2,
|
||||
padding: const EdgeInsets.all(12),
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: people.take(4).map((person) {
|
||||
return CircleAvatar(
|
||||
backgroundImage: NetworkImage(
|
||||
getFaceThumbnailUrl(person.id),
|
||||
headers: ApiService.getRequestHeaders(),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'people'.t(context: context),
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PlacesCollectionCard extends StatelessWidget {
|
||||
const _PlacesCollectionCard();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isTablet = constraints.maxWidth > 600;
|
||||
final widthFactor = isTablet ? 0.25 : 0.5;
|
||||
final size = context.width * widthFactor - 20.0;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => context.pushRoute(
|
||||
PlacesCollectionRoute(
|
||||
currentLocation: null,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: size,
|
||||
width: size,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
color:
|
||||
context.colorScheme.secondaryContainer.withAlpha(100),
|
||||
),
|
||||
child: IgnorePointer(
|
||||
child: MapThumbnail(
|
||||
zoom: 8,
|
||||
centre: const LatLng(
|
||||
21.44950,
|
||||
-157.91959,
|
||||
),
|
||||
showAttribution: false,
|
||||
themeMode: context.isDarkTheme
|
||||
? ThemeMode.dark
|
||||
: ThemeMode.light,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'places'.t(),
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LocalAlbumsCollectionCard extends ConsumerWidget {
|
||||
const _LocalAlbumsCollectionCard();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// TODO: Migrate to the drift after local album page
|
||||
final albums = ref.watch(localAlbumsProvider);
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isTablet = constraints.maxWidth > 600;
|
||||
final widthFactor = isTablet ? 0.25 : 0.5;
|
||||
final size = context.width * widthFactor - 20.0;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => context.pushRoute(
|
||||
const LocalAlbumsRoute(),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: size,
|
||||
width: size,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.colorScheme.primary.withAlpha(30),
|
||||
context.colorScheme.primary.withAlpha(25),
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: GridView.count(
|
||||
crossAxisCount: 2,
|
||||
padding: const EdgeInsets.all(12),
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: albums.take(4).map((album) {
|
||||
return AlbumThumbnailCard(
|
||||
album: album,
|
||||
showTitle: false,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'on_this_device'.t(context: context),
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _QuickAccessButtonList extends ConsumerWidget {
|
||||
const _QuickAccessButtonList();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final partners = ref.watch(partnerSharedWithProvider);
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.only(left: 16, top: 12, right: 16, bottom: 32),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: context.colorScheme.onSurface.withAlpha(10),
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.colorScheme.primary.withAlpha(10),
|
||||
context.colorScheme.primary.withAlpha(15),
|
||||
context.colorScheme.primary.withAlpha(20),
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.all(0),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
ListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: const Radius.circular(20),
|
||||
topRight: const Radius.circular(20),
|
||||
bottomLeft: Radius.circular(partners.isEmpty ? 20 : 0),
|
||||
bottomRight: Radius.circular(partners.isEmpty ? 20 : 0),
|
||||
),
|
||||
),
|
||||
leading: const Icon(
|
||||
Icons.folder_outlined,
|
||||
size: 26,
|
||||
),
|
||||
title: Text(
|
||||
'folders'.t(context: context),
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
onTap: () => context.pushRoute(FolderRoute()),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.lock_outline_rounded,
|
||||
size: 26,
|
||||
),
|
||||
title: Text(
|
||||
'locked_folder'.t(context: context),
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
// TODO: PIN code is needed
|
||||
onTap: () => context.pushRoute(const DriftLockedFolderRoute()),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.group_outlined,
|
||||
size: 26,
|
||||
),
|
||||
title: Text(
|
||||
'partners'.t(context: context),
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
onTap: () => context.pushRoute(const PartnerRoute()),
|
||||
),
|
||||
_PartnerList(partners: partners),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PartnerList extends StatelessWidget {
|
||||
const _PartnerList({required this.partners});
|
||||
|
||||
final List<UserDto> partners;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(0),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: partners.length,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
final partner = partners[index];
|
||||
final isLastItem = index == partners.length - 1;
|
||||
return ListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(isLastItem ? 20 : 0),
|
||||
bottomRight: Radius.circular(isLastItem ? 20 : 0),
|
||||
),
|
||||
),
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 12.0,
|
||||
right: 18.0,
|
||||
),
|
||||
leading: userAvatar(context, partner, radius: 16),
|
||||
title: const Text(
|
||||
"partner_list_user_photos",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
).t(context: context, args: {'user': partner.name}),
|
||||
onTap: () => context.pushRoute(PartnerDetailRoute(partner: partner)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import 'dart:convert' hide Codec;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:thumbhash/thumbhash.dart';
|
||||
|
||||
class ThumbHashProvider extends ImageProvider<ThumbHashProvider> {
|
||||
final String thumbHash;
|
||||
|
||||
const ThumbHashProvider({
|
||||
required this.thumbHash,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<ThumbHashProvider> obtainKey(ImageConfiguration configuration) {
|
||||
return SynchronousFuture(this);
|
||||
}
|
||||
|
||||
@override
|
||||
ImageStreamCompleter loadImage(
|
||||
ThumbHashProvider key,
|
||||
ImageDecoderCallback decode,
|
||||
) {
|
||||
return MultiFrameImageStreamCompleter(
|
||||
codec: _loadCodec(key, decode),
|
||||
scale: 1.0,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Codec> _loadCodec(
|
||||
ThumbHashProvider key,
|
||||
ImageDecoderCallback decode,
|
||||
) async {
|
||||
final image = thumbHashToRGBA(base64Decode(key.thumbHash));
|
||||
return decode(await ImmutableBuffer.fromUint8List(rgbaToBmp(image)));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is ThumbHashProvider) {
|
||||
return thumbHash == other.thumbHash;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => thumbHash.hashCode;
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/thumb_hash_provider.dart';
|
||||
import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart';
|
||||
import 'package:immich_mobile/widgets/common/fade_in_placeholder_image.dart';
|
||||
import 'package:immich_mobile/widgets/common/thumbhash.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:octo_image/octo_image.dart';
|
||||
|
||||
@@ -56,13 +54,7 @@ OctoPlaceholderBuilder _blurHashPlaceholderBuilder(
|
||||
String? thumbHash, {
|
||||
BoxFit? fit,
|
||||
}) {
|
||||
return (context) => thumbHash == null
|
||||
? const ThumbnailPlaceholder()
|
||||
: FadeInPlaceholderImage(
|
||||
placeholder: const ThumbnailPlaceholder(),
|
||||
image: ThumbHashProvider(thumbHash: thumbHash),
|
||||
fit: fit ?? BoxFit.cover,
|
||||
);
|
||||
return (context) => Thumbhash(blurhash: thumbHash, fit: fit ?? BoxFit.cover);
|
||||
}
|
||||
|
||||
OctoErrorBuilder _blurHashErrorBuilder(
|
||||
|
||||
@@ -12,7 +12,7 @@ class ThumbnailTile extends ConsumerWidget {
|
||||
this.size = const Size.square(256),
|
||||
this.fit = BoxFit.cover,
|
||||
this.showStorageIndicator = true,
|
||||
this.canDeselect = true,
|
||||
this.lockSelection = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@@ -20,15 +20,13 @@ class ThumbnailTile extends ConsumerWidget {
|
||||
final Size size;
|
||||
final BoxFit fit;
|
||||
final bool showStorageIndicator;
|
||||
|
||||
/// If we are allowed to deselect this image
|
||||
final bool canDeselect;
|
||||
final bool lockSelection;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final assetContainerColor = context.isDarkTheme
|
||||
? context.primaryColor.darken(amount: 0.6)
|
||||
: context.primaryColor.lighten(amount: 0.8);
|
||||
? context.primaryColor.darken(amount: 0.4)
|
||||
: context.primaryColor.lighten(amount: 0.75);
|
||||
|
||||
final isSelected = ref.watch(
|
||||
multiSelectProvider.select(
|
||||
@@ -36,24 +34,29 @@ class ThumbnailTile extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
|
||||
final borderStyle = lockSelection
|
||||
? BoxDecoration(
|
||||
color: context.colorScheme.surfaceContainerHighest,
|
||||
border: Border.all(
|
||||
color: context.colorScheme.surfaceContainerHighest,
|
||||
width: 6,
|
||||
),
|
||||
)
|
||||
: isSelected
|
||||
? BoxDecoration(
|
||||
color: assetContainerColor,
|
||||
border: Border.all(color: assetContainerColor, width: 6),
|
||||
)
|
||||
: const BoxDecoration();
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
duration: Durations.short4,
|
||||
curve: Curves.decelerate,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? (canDeselect ? assetContainerColor : Colors.grey)
|
||||
: null,
|
||||
border: isSelected
|
||||
? Border.all(
|
||||
color: canDeselect ? assetContainerColor : Colors.grey,
|
||||
width: 8,
|
||||
)
|
||||
: const Border(),
|
||||
),
|
||||
decoration: borderStyle,
|
||||
child: ClipRRect(
|
||||
borderRadius: isSelected
|
||||
borderRadius: isSelected || lockSelection
|
||||
? const BorderRadius.all(Radius.circular(15.0))
|
||||
: BorderRadius.zero,
|
||||
child: Stack(
|
||||
@@ -102,14 +105,17 @@ class ThumbnailTile extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isSelected)
|
||||
if (isSelected || lockSelection)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(3.0),
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: _SelectionIndicator(
|
||||
isSelected: isSelected,
|
||||
color: assetContainerColor,
|
||||
isLocked: lockSelection,
|
||||
color: lockSelection
|
||||
? context.colorScheme.surfaceContainerHighest
|
||||
: assetContainerColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -120,15 +126,29 @@ class ThumbnailTile extends ConsumerWidget {
|
||||
|
||||
class _SelectionIndicator extends StatelessWidget {
|
||||
final bool isSelected;
|
||||
final bool isLocked;
|
||||
final Color? color;
|
||||
|
||||
const _SelectionIndicator({
|
||||
required this.isSelected,
|
||||
required this.isLocked,
|
||||
this.color,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isSelected) {
|
||||
if (isLocked) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: color,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.check_circle_rounded,
|
||||
color: Colors.grey,
|
||||
),
|
||||
);
|
||||
} else if (isSelected) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/full_image.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||
import 'package:immich_mobile/utils/hooks/blurhash_hook.dart';
|
||||
import 'package:immich_mobile/widgets/common/thumbhash.dart';
|
||||
|
||||
class DriftMemoryCard extends StatelessWidget {
|
||||
final RemoteAsset asset;
|
||||
@@ -117,43 +117,29 @@ class _BlurredBackdrop extends HookWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final blurhash = useDriftBlurHashRef(asset).value;
|
||||
final blurhash = asset.thumbHash;
|
||||
if (blurhash != null) {
|
||||
// Use a nice cheap blur hash image decoration
|
||||
return Container(
|
||||
return Thumbhash(blurhash: blurhash, fit: BoxFit.cover);
|
||||
}
|
||||
|
||||
// Fall back to using a more expensive image filtered
|
||||
// Since the ImmichImage is already precached, we can
|
||||
// safely use that as the image provider
|
||||
return ImageFiltered(
|
||||
imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: MemoryImage(
|
||||
blurhash,
|
||||
image: getFullImageProvider(
|
||||
asset,
|
||||
size: Size(context.width, context.height),
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// Fall back to using a more expensive image filtered
|
||||
// Since the ImmichImage is already precached, we can
|
||||
// safely use that as the image provider
|
||||
return ImageFiltered(
|
||||
imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: getFullImageProvider(
|
||||
asset,
|
||||
size: Size(context.width, context.height),
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
child: const ColoredBox(color: Color.fromRGBO(0, 0, 0, 0.2)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,22 +166,22 @@ class _AssetTileWidget extends ConsumerWidget {
|
||||
BaseAsset asset,
|
||||
) {
|
||||
final multiSelectState = ref.read(multiSelectProvider);
|
||||
if (!multiSelectState.isEnabled) {
|
||||
|
||||
if (multiSelectState.forceEnable || multiSelectState.isEnabled) {
|
||||
ref.read(multiSelectProvider.notifier).toggleAssetSelection(asset);
|
||||
} else {
|
||||
ctx.pushRoute(
|
||||
AssetViewerRoute(
|
||||
initialIndex: assetIndex,
|
||||
timelineService: ref.read(timelineServiceProvider),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(multiSelectProvider.notifier).toggleAssetSelection(asset);
|
||||
}
|
||||
|
||||
void _handleOnLongPress(WidgetRef ref, BaseAsset asset) {
|
||||
final multiSelectState = ref.read(multiSelectProvider);
|
||||
if (multiSelectState.isEnabled) {
|
||||
if (multiSelectState.isEnabled || multiSelectState.forceEnable) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -189,13 +189,35 @@ class _AssetTileWidget extends ConsumerWidget {
|
||||
ref.read(multiSelectProvider.notifier).toggleAssetSelection(asset);
|
||||
}
|
||||
|
||||
bool _getLockSelectionStatus(WidgetRef ref) {
|
||||
final lockSelectionAssets = ref.read(
|
||||
multiSelectProvider.select(
|
||||
(state) => state.lockedSelectionAssets,
|
||||
),
|
||||
);
|
||||
|
||||
if (lockSelectionAssets.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return lockSelectionAssets.contains(asset);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final lockSelection = _getLockSelectionStatus(ref);
|
||||
|
||||
return RepaintBoundary(
|
||||
child: GestureDetector(
|
||||
onTap: () => _handleOnTap(context, ref, assetIndex, asset),
|
||||
onLongPress: () => _handleOnLongPress(ref, asset),
|
||||
child: ThumbnailTile(asset),
|
||||
onTap: () => lockSelection
|
||||
? null
|
||||
: _handleOnTap(context, ref, assetIndex, asset),
|
||||
onLongPress: () =>
|
||||
lockSelection ? null : _handleOnLongPress(ref, asset),
|
||||
child: ThumbnailTile(
|
||||
asset,
|
||||
lockSelection: lockSelection,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -354,22 +354,24 @@ class ScrubberState extends ConsumerState<Scrubber>
|
||||
isDragging: _isDragging,
|
||||
),
|
||||
),
|
||||
PositionedDirectional(
|
||||
top: _thumbTopOffset + widget.topPadding,
|
||||
end: 0,
|
||||
child: RepaintBoundary(
|
||||
child: GestureDetector(
|
||||
onVerticalDragStart: _onDragStart,
|
||||
onVerticalDragUpdate: _onDragUpdate,
|
||||
onVerticalDragEnd: _onDragEnd,
|
||||
child: _Scrubber(
|
||||
thumbAnimation: _thumbAnimation,
|
||||
labelAnimation: _labelAnimation,
|
||||
label: label,
|
||||
if (_scrollController.hasClients &&
|
||||
_scrollController.position.maxScrollExtent > 0)
|
||||
PositionedDirectional(
|
||||
top: _thumbTopOffset + widget.topPadding,
|
||||
end: 0,
|
||||
child: RepaintBoundary(
|
||||
child: GestureDetector(
|
||||
onVerticalDragStart: _onDragStart,
|
||||
onVerticalDragUpdate: _onDragUpdate,
|
||||
onVerticalDragEnd: _onDragEnd,
|
||||
child: _Scrubber(
|
||||
thumbAnimation: _thumbAnimation,
|
||||
labelAnimation: _labelAnimation,
|
||||
label: label,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -18,9 +18,14 @@ import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
|
||||
import 'package:immich_mobile/widgets/common/selection_sliver_app_bar.dart';
|
||||
|
||||
class Timeline extends StatelessWidget {
|
||||
const Timeline({super.key, this.topSliverWidget, this.topSliverWidgetHeight});
|
||||
const Timeline({
|
||||
super.key,
|
||||
this.topSliverWidget,
|
||||
this.topSliverWidgetHeight,
|
||||
});
|
||||
|
||||
final Widget? topSliverWidget;
|
||||
final double? topSliverWidgetHeight;
|
||||
@@ -52,7 +57,10 @@ class Timeline extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _SliverTimeline extends ConsumerStatefulWidget {
|
||||
const _SliverTimeline({this.topSliverWidget, this.topSliverWidgetHeight});
|
||||
const _SliverTimeline({
|
||||
this.topSliverWidget,
|
||||
this.topSliverWidgetHeight,
|
||||
});
|
||||
|
||||
final Widget? topSliverWidget;
|
||||
final double? topSliverWidgetHeight;
|
||||
@@ -84,6 +92,10 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
final asyncSegments = ref.watch(timelineSegmentProvider);
|
||||
final maxHeight =
|
||||
ref.watch(timelineArgsProvider.select((args) => args.maxHeight));
|
||||
final isSelectionMode = ref.watch(
|
||||
multiSelectProvider.select((s) => s.forceEnable),
|
||||
);
|
||||
|
||||
return asyncSegments.widgetWhen(
|
||||
onData: (segments) {
|
||||
final childCount = (segments.lastOrNull?.lastIndex ?? -1) + 1;
|
||||
@@ -105,11 +117,14 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
primary: true,
|
||||
cacheExtent: maxHeight * 2,
|
||||
slivers: [
|
||||
const ImmichSliverAppBar(
|
||||
floating: true,
|
||||
pinned: false,
|
||||
snap: false,
|
||||
),
|
||||
if (isSelectionMode)
|
||||
const SelectionSliverAppBar()
|
||||
else
|
||||
const ImmichSliverAppBar(
|
||||
floating: true,
|
||||
pinned: false,
|
||||
snap: false,
|
||||
),
|
||||
if (widget.topSliverWidget != null) widget.topSliverWidget!,
|
||||
_SliverSegmentedList(
|
||||
segments: segments,
|
||||
@@ -134,40 +149,42 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
],
|
||||
),
|
||||
),
|
||||
Consumer(
|
||||
builder: (_, consumerRef, child) {
|
||||
final isMultiSelectEnabled = consumerRef.watch(
|
||||
multiSelectProvider.select(
|
||||
(s) => s.isEnabled,
|
||||
),
|
||||
);
|
||||
if (!isSelectionMode) ...[
|
||||
Consumer(
|
||||
builder: (_, consumerRef, child) {
|
||||
final isMultiSelectEnabled = consumerRef.watch(
|
||||
multiSelectProvider.select(
|
||||
(s) => s.isEnabled,
|
||||
),
|
||||
);
|
||||
|
||||
if (isMultiSelectEnabled) {
|
||||
return child!;
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
child: const Positioned(
|
||||
top: 60,
|
||||
left: 25,
|
||||
child: _MultiSelectStatusButton(),
|
||||
if (isMultiSelectEnabled) {
|
||||
return child!;
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
child: const Positioned(
|
||||
top: 60,
|
||||
left: 25,
|
||||
child: _MultiSelectStatusButton(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Consumer(
|
||||
builder: (_, consumerRef, child) {
|
||||
final isMultiSelectEnabled = consumerRef.watch(
|
||||
multiSelectProvider.select(
|
||||
(s) => s.isEnabled,
|
||||
),
|
||||
);
|
||||
Consumer(
|
||||
builder: (_, consumerRef, child) {
|
||||
final isMultiSelectEnabled = consumerRef.watch(
|
||||
multiSelectProvider.select(
|
||||
(s) => s.isEnabled,
|
||||
),
|
||||
);
|
||||
|
||||
if (isMultiSelectEnabled) {
|
||||
return child!;
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
child: const HomeBottomAppBar(),
|
||||
),
|
||||
if (isMultiSelectEnabled) {
|
||||
return child!;
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
child: const HomeBottomAppBar(),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
7
mobile/lib/providers/stack.provider.dart
Normal file
7
mobile/lib/providers/stack.provider.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/stack.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
|
||||
final driftStackProvider = Provider<DriftStackRepository>(
|
||||
(ref) => DriftStackRepository(ref.watch(driftProvider)),
|
||||
);
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/infrastructure/timeline.provider.dart';
|
||||
@@ -12,8 +13,14 @@ final multiSelectProvider =
|
||||
|
||||
class MultiSelectState {
|
||||
final Set<BaseAsset> selectedAssets;
|
||||
final Set<BaseAsset> lockedSelectionAssets;
|
||||
final bool forceEnable;
|
||||
|
||||
const MultiSelectState({required this.selectedAssets});
|
||||
const MultiSelectState({
|
||||
required this.selectedAssets,
|
||||
required this.lockedSelectionAssets,
|
||||
this.forceEnable = false,
|
||||
});
|
||||
|
||||
bool get isEnabled => selectedAssets.isNotEmpty;
|
||||
bool get hasRemote => selectedAssets.any(
|
||||
@@ -25,33 +32,54 @@ class MultiSelectState {
|
||||
(asset) => asset.storage == AssetState.local,
|
||||
);
|
||||
|
||||
MultiSelectState copyWith({Set<BaseAsset>? selectedAssets}) {
|
||||
MultiSelectState copyWith({
|
||||
Set<BaseAsset>? selectedAssets,
|
||||
Set<BaseAsset>? lockedSelectionAssets,
|
||||
bool? forceEnable,
|
||||
}) {
|
||||
return MultiSelectState(
|
||||
selectedAssets: selectedAssets ?? this.selectedAssets,
|
||||
lockedSelectionAssets:
|
||||
lockedSelectionAssets ?? this.lockedSelectionAssets,
|
||||
forceEnable: forceEnable ?? this.forceEnable,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'MultiSelectState(selectedAssets: $selectedAssets)';
|
||||
String toString() =>
|
||||
'MultiSelectState(selectedAssets: $selectedAssets, lockedSelectionAssets: $lockedSelectionAssets, forceEnable: $forceEnable)';
|
||||
|
||||
@override
|
||||
bool operator ==(covariant MultiSelectState other) {
|
||||
if (identical(this, other)) return true;
|
||||
final listEquals = const DeepCollectionEquality().equals;
|
||||
final setEquals = const DeepCollectionEquality().equals;
|
||||
|
||||
return listEquals(other.selectedAssets, selectedAssets);
|
||||
return setEquals(other.selectedAssets, selectedAssets) &&
|
||||
setEquals(other.lockedSelectionAssets, lockedSelectionAssets) &&
|
||||
other.forceEnable == forceEnable;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => selectedAssets.hashCode;
|
||||
int get hashCode =>
|
||||
selectedAssets.hashCode ^
|
||||
lockedSelectionAssets.hashCode ^
|
||||
forceEnable.hashCode;
|
||||
}
|
||||
|
||||
class MultiSelectNotifier extends Notifier<MultiSelectState> {
|
||||
MultiSelectNotifier([this._defaultState]);
|
||||
final MultiSelectState? _defaultState;
|
||||
|
||||
TimelineService get _timelineService => ref.read(timelineServiceProvider);
|
||||
|
||||
@override
|
||||
MultiSelectState build() {
|
||||
return const MultiSelectState(selectedAssets: {});
|
||||
return _defaultState ??
|
||||
const MultiSelectState(
|
||||
selectedAssets: {},
|
||||
lockedSelectionAssets: {},
|
||||
forceEnable: false,
|
||||
);
|
||||
}
|
||||
|
||||
void selectAsset(BaseAsset asset) {
|
||||
@@ -83,7 +111,11 @@ class MultiSelectNotifier extends Notifier<MultiSelectState> {
|
||||
}
|
||||
|
||||
void reset() {
|
||||
state = const MultiSelectState(selectedAssets: {});
|
||||
state = const MultiSelectState(
|
||||
selectedAssets: {},
|
||||
lockedSelectionAssets: {},
|
||||
forceEnable: false,
|
||||
);
|
||||
}
|
||||
|
||||
/// Bucket bulk operations
|
||||
@@ -131,6 +163,12 @@ class MultiSelectNotifier extends Notifier<MultiSelectState> {
|
||||
|
||||
state = state.copyWith(selectedAssets: selectedAssets);
|
||||
}
|
||||
|
||||
void setLockedSelectionAssets(Set<BaseAsset> assets) {
|
||||
state = state.copyWith(
|
||||
lockedSelectionAssets: assets,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final bucketSelectionProvider = Provider.family<bool, List<BaseAsset>>(
|
||||
|
||||
@@ -40,6 +40,9 @@ class AuthRepository extends DatabaseRepository {
|
||||
_drift.remoteAlbumEntity.deleteAll(),
|
||||
_drift.remoteAlbumAssetEntity.deleteAll(),
|
||||
_drift.remoteAlbumUserEntity.deleteAll(),
|
||||
_drift.memoryEntity.deleteAll(),
|
||||
_drift.memoryAssetEntity.deleteAll(),
|
||||
_drift.stackEntity.deleteAll(),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||
import 'package:immich_mobile/domain/models/memory.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
@@ -66,12 +67,19 @@ import 'package:immich_mobile/pages/search/person_result.page.dart';
|
||||
import 'package:immich_mobile/pages/search/recently_taken.page.dart';
|
||||
import 'package:immich_mobile/pages/search/search.page.dart';
|
||||
import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/drift_favorite.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/drift_video.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/drift_trash.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/drift_archive.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/drift_locked_folder.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/local_timeline.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/main_timeline.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/media_stat.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/remote_timeline.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_album.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_library.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_asset_selection_timeline.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_memory.page.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
@@ -174,7 +182,7 @@ class AppRouter extends RootStackRouter {
|
||||
maintainState: false,
|
||||
),
|
||||
AutoRoute(
|
||||
page: LibraryRoute.page,
|
||||
page: DriftLibraryRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
AutoRoute(
|
||||
@@ -392,6 +400,34 @@ class AppRouter extends RootStackRouter {
|
||||
page: DriftMemoryRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
AutoRoute(
|
||||
page: DriftFavoriteRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
AutoRoute(
|
||||
page: DriftTrashRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
AutoRoute(
|
||||
page: DriftArchiveRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
AutoRoute(
|
||||
page: DriftLockedFolderRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
AutoRoute(
|
||||
page: DriftVideoRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
AutoRoute(
|
||||
page: DriftLibraryRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
AutoRoute(
|
||||
page: DriftAssetSelectionTimelineRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
|
||||
// required to handle all deeplinks in deep_link.service.dart
|
||||
// auto_route_library#1722
|
||||
|
||||
@@ -618,6 +618,119 @@ class DriftAlbumsRoute extends PageRouteInfo<void> {
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [DriftArchivePage]
|
||||
class DriftArchiveRoute extends PageRouteInfo<void> {
|
||||
const DriftArchiveRoute({List<PageRouteInfo>? children})
|
||||
: super(DriftArchiveRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'DriftArchiveRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const DriftArchivePage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [DriftAssetSelectionTimelinePage]
|
||||
class DriftAssetSelectionTimelineRoute
|
||||
extends PageRouteInfo<DriftAssetSelectionTimelineRouteArgs> {
|
||||
DriftAssetSelectionTimelineRoute({
|
||||
Key? key,
|
||||
Set<BaseAsset> lockedSelectionAssets = const {},
|
||||
List<PageRouteInfo>? children,
|
||||
}) : super(
|
||||
DriftAssetSelectionTimelineRoute.name,
|
||||
args: DriftAssetSelectionTimelineRouteArgs(
|
||||
key: key,
|
||||
lockedSelectionAssets: lockedSelectionAssets,
|
||||
),
|
||||
initialChildren: children,
|
||||
);
|
||||
|
||||
static const String name = 'DriftAssetSelectionTimelineRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
final args = data.argsAs<DriftAssetSelectionTimelineRouteArgs>(
|
||||
orElse: () => const DriftAssetSelectionTimelineRouteArgs(),
|
||||
);
|
||||
return DriftAssetSelectionTimelinePage(
|
||||
key: args.key,
|
||||
lockedSelectionAssets: args.lockedSelectionAssets,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class DriftAssetSelectionTimelineRouteArgs {
|
||||
const DriftAssetSelectionTimelineRouteArgs({
|
||||
this.key,
|
||||
this.lockedSelectionAssets = const {},
|
||||
});
|
||||
|
||||
final Key? key;
|
||||
|
||||
final Set<BaseAsset> lockedSelectionAssets;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DriftAssetSelectionTimelineRouteArgs{key: $key, lockedSelectionAssets: $lockedSelectionAssets}';
|
||||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [DriftFavoritePage]
|
||||
class DriftFavoriteRoute extends PageRouteInfo<void> {
|
||||
const DriftFavoriteRoute({List<PageRouteInfo>? children})
|
||||
: super(DriftFavoriteRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'DriftFavoriteRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const DriftFavoritePage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [DriftLibraryPage]
|
||||
class DriftLibraryRoute extends PageRouteInfo<void> {
|
||||
const DriftLibraryRoute({List<PageRouteInfo>? children})
|
||||
: super(DriftLibraryRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'DriftLibraryRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const DriftLibraryPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [DriftLockedFolderPage]
|
||||
class DriftLockedFolderRoute extends PageRouteInfo<void> {
|
||||
const DriftLockedFolderRoute({List<PageRouteInfo>? children})
|
||||
: super(DriftLockedFolderRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'DriftLockedFolderRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const DriftLockedFolderPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [DriftMemoryPage]
|
||||
class DriftMemoryRoute extends PageRouteInfo<DriftMemoryRouteArgs> {
|
||||
@@ -670,6 +783,38 @@ class DriftMemoryRouteArgs {
|
||||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [DriftTrashPage]
|
||||
class DriftTrashRoute extends PageRouteInfo<void> {
|
||||
const DriftTrashRoute({List<PageRouteInfo>? children})
|
||||
: super(DriftTrashRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'DriftTrashRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const DriftTrashPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [DriftVideoPage]
|
||||
class DriftVideoRoute extends PageRouteInfo<void> {
|
||||
const DriftVideoRoute({List<PageRouteInfo>? children})
|
||||
: super(DriftVideoRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'DriftVideoRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const DriftVideoPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [EditImagePage]
|
||||
class EditImageRoute extends PageRouteInfo<EditImageRouteArgs> {
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:thumbhash/thumbhash.dart' as thumbhash;
|
||||
|
||||
ObjectRef<Uint8List?> useBlurHashRef(Asset? asset) {
|
||||
if (asset?.thumbhash == null) {
|
||||
return useRef(null);
|
||||
}
|
||||
|
||||
final rbga = thumbhash.thumbHashToRGBA(
|
||||
base64Decode(asset!.thumbhash!),
|
||||
);
|
||||
|
||||
return useRef(thumbhash.rgbaToBmp(rbga));
|
||||
}
|
||||
|
||||
ObjectRef<Uint8List?> useDriftBlurHashRef(RemoteAsset? asset) {
|
||||
if (asset?.thumbHash == null) {
|
||||
return useRef(null);
|
||||
}
|
||||
|
||||
final rbga = thumbhash.thumbHashToRGBA(
|
||||
base64Decode(asset!.thumbHash!),
|
||||
);
|
||||
|
||||
return useRef(thumbhash.rgbaToBmp(rbga));
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/widgets/common/transparent_image.dart';
|
||||
|
||||
class FadeInPlaceholderImage extends StatelessWidget {
|
||||
final Widget placeholder;
|
||||
final ImageProvider image;
|
||||
final Duration duration;
|
||||
final BoxFit fit;
|
||||
|
||||
const FadeInPlaceholderImage({
|
||||
super.key,
|
||||
required this.placeholder,
|
||||
required this.image,
|
||||
this.duration = const Duration(milliseconds: 100),
|
||||
this.fit = BoxFit.cover,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox.expand(
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
placeholder,
|
||||
FadeInImage(
|
||||
fadeInDuration: duration,
|
||||
image: image,
|
||||
fit: fit,
|
||||
placeholder: MemoryImage(kTransparentImage),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/providers/image/immich_local_thumbnail_provider.dart';
|
||||
import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/utils/hooks/blurhash_hook.dart';
|
||||
import 'package:immich_mobile/utils/thumbnail_utils.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
||||
import 'package:immich_mobile/widgets/common/thumbhash_placeholder.dart';
|
||||
@@ -64,7 +61,6 @@ class ImmichThumbnail extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Uint8List? blurhash = useBlurHashRef(asset).value;
|
||||
final userId = ref.watch(currentUserProvider)?.id;
|
||||
|
||||
if (asset == null) {
|
||||
@@ -82,7 +78,7 @@ class ImmichThumbnail extends HookConsumerWidget {
|
||||
asset!.exifInfo,
|
||||
asset!.fileCreatedAt,
|
||||
asset!.type,
|
||||
[],
|
||||
const [],
|
||||
);
|
||||
|
||||
final thumbnailProviderInstance = ImmichThumbnail.imageProvider(
|
||||
@@ -94,7 +90,7 @@ class ImmichThumbnail extends HookConsumerWidget {
|
||||
thumbnailProviderInstance.evict();
|
||||
|
||||
final originalErrorWidgetBuilder =
|
||||
blurHashErrorBuilder(blurhash, fit: fit);
|
||||
blurHashErrorBuilder(asset?.thumbhash, fit: fit);
|
||||
return originalErrorWidgetBuilder(ctx, error, stackTrace);
|
||||
}
|
||||
|
||||
@@ -105,7 +101,8 @@ class ImmichThumbnail extends HookConsumerWidget {
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: const Duration(milliseconds: 100),
|
||||
octoSet: OctoSet(
|
||||
placeholderBuilder: blurHashPlaceholderBuilder(blurhash, fit: fit),
|
||||
placeholderBuilder:
|
||||
blurHashPlaceholderBuilder(asset?.thumbhash, fit: fit),
|
||||
errorBuilder: customErrorBuilder,
|
||||
),
|
||||
image: thumbnailProviderInstance,
|
||||
|
||||
77
mobile/lib/widgets/common/selection_sliver_app_bar.dart
Normal file
77
mobile/lib/widgets/common/selection_sliver_app_bar.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
|
||||
class SelectionSliverAppBar extends ConsumerStatefulWidget {
|
||||
const SelectionSliverAppBar({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<SelectionSliverAppBar> createState() =>
|
||||
_SelectionSliverAppBarState();
|
||||
}
|
||||
|
||||
class _SelectionSliverAppBarState extends ConsumerState<SelectionSliverAppBar> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selection = ref.watch(
|
||||
multiSelectProvider.select((s) => s.selectedAssets),
|
||||
);
|
||||
|
||||
final toExclude = ref.watch(
|
||||
multiSelectProvider.select((s) => s.lockedSelectionAssets),
|
||||
);
|
||||
|
||||
final filteredAssets = selection.where((asset) {
|
||||
return !toExclude.contains(asset);
|
||||
}).toSet();
|
||||
|
||||
onDone(Set<BaseAsset> selected) {
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
context.maybePop<Set<BaseAsset>>(selected);
|
||||
}
|
||||
|
||||
return SliverAppBar(
|
||||
floating: true,
|
||||
pinned: true,
|
||||
snap: false,
|
||||
backgroundColor: context.colorScheme.surfaceContainer,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5)),
|
||||
),
|
||||
automaticallyImplyLeading: false,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close_rounded),
|
||||
onPressed: () {
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
context.pop<Set<BaseAsset>>(null);
|
||||
},
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
"Select {count}".t(
|
||||
context: context,
|
||||
args: {
|
||||
'count': filteredAssets.length.toString(),
|
||||
},
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => onDone(filteredAssets),
|
||||
child: Text(
|
||||
'done'.t(context: context),
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
color: context.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
194
mobile/lib/widgets/common/thumbhash.dart
Normal file
194
mobile/lib/widgets/common/thumbhash.dart
Normal file
@@ -0,0 +1,194 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thumbhash/thumbhash.dart' as thumbhash;
|
||||
|
||||
class ThumbhashImage extends RenderBox {
|
||||
Color _placeholderColor;
|
||||
ui.Image? _image;
|
||||
BoxFit _fit;
|
||||
|
||||
ThumbhashImage({
|
||||
required ui.Image? image,
|
||||
required BoxFit fit,
|
||||
required Color placeholderColor,
|
||||
}) : _image = image,
|
||||
_fit = fit,
|
||||
_placeholderColor = placeholderColor;
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
final image = _image;
|
||||
final rect = offset & size;
|
||||
if (image == null) {
|
||||
final paint = Paint();
|
||||
paint.color = _placeholderColor;
|
||||
context.canvas.drawRect(rect, paint);
|
||||
return;
|
||||
}
|
||||
|
||||
paintImage(
|
||||
canvas: context.canvas,
|
||||
rect: rect,
|
||||
image: image,
|
||||
fit: _fit,
|
||||
filterQuality: FilterQuality.low,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
size = constraints.biggest;
|
||||
}
|
||||
|
||||
set image(ui.Image? value) {
|
||||
if (_image != value) {
|
||||
_image = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
}
|
||||
|
||||
set fit(BoxFit value) {
|
||||
if (_fit != value) {
|
||||
_fit = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
}
|
||||
|
||||
set placeholderColor(Color value) {
|
||||
if (_placeholderColor != value) {
|
||||
_placeholderColor = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ThumbhashLeaf extends LeafRenderObjectWidget {
|
||||
final ui.Image? image;
|
||||
final BoxFit fit;
|
||||
final Color placeholderColor;
|
||||
|
||||
const ThumbhashLeaf({
|
||||
super.key,
|
||||
required this.image,
|
||||
required this.fit,
|
||||
required this.placeholderColor,
|
||||
});
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return ThumbhashImage(
|
||||
image: image,
|
||||
fit: fit,
|
||||
placeholderColor: placeholderColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, ThumbhashImage renderObject) {
|
||||
renderObject.fit = fit;
|
||||
renderObject.image = image;
|
||||
renderObject.placeholderColor = placeholderColor;
|
||||
}
|
||||
}
|
||||
|
||||
class Thumbhash extends StatefulWidget {
|
||||
final String? blurhash;
|
||||
final BoxFit fit;
|
||||
final Color placeholderColor;
|
||||
|
||||
const Thumbhash({
|
||||
required this.blurhash,
|
||||
this.fit = BoxFit.cover,
|
||||
this.placeholderColor = const Color.fromRGBO(0, 0, 0, 0.2),
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<Thumbhash> createState() => _ThumbhashState();
|
||||
}
|
||||
|
||||
class _ThumbhashState extends State<Thumbhash> {
|
||||
String? blurhash;
|
||||
BoxFit? fit;
|
||||
ui.Image? _image;
|
||||
Color? placeholderColor;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final blurhash_ = blurhash = widget.blurhash;
|
||||
fit = widget.fit;
|
||||
placeholderColor = widget.placeholderColor;
|
||||
if (blurhash_ == null) {
|
||||
return;
|
||||
}
|
||||
final image = thumbhash.thumbHashToRGBA(base64.decode(blurhash_));
|
||||
_decode(image);
|
||||
}
|
||||
|
||||
Future<void> _decode(thumbhash.Image image) async {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
final buffer = await ImmutableBuffer.fromUint8List(image.rgba);
|
||||
if (!mounted) {
|
||||
buffer.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
final descriptor = ImageDescriptor.raw(
|
||||
buffer,
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
pixelFormat: PixelFormat.rgba8888,
|
||||
);
|
||||
if (!mounted) {
|
||||
buffer.dispose();
|
||||
descriptor.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
final codec = await descriptor.instantiateCodec(
|
||||
targetWidth: image.width,
|
||||
targetHeight: image.height,
|
||||
);
|
||||
if (!mounted) {
|
||||
buffer.dispose();
|
||||
descriptor.dispose();
|
||||
codec.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
final frame = (await codec.getNextFrame()).image;
|
||||
buffer.dispose();
|
||||
descriptor.dispose();
|
||||
codec.dispose();
|
||||
if (!mounted) {
|
||||
frame.dispose();
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_image = frame;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ThumbhashLeaf(
|
||||
image: _image,
|
||||
fit: fit!,
|
||||
placeholderColor: placeholderColor!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_image?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart';
|
||||
import 'package:immich_mobile/widgets/common/fade_in_placeholder_image.dart';
|
||||
import 'package:immich_mobile/widgets/common/thumbhash.dart';
|
||||
import 'package:octo_image/octo_image.dart';
|
||||
|
||||
/// Simple set to show [OctoPlaceholder.circularProgressIndicator] as
|
||||
/// placeholder and [OctoError.icon] as error.
|
||||
OctoSet blurHashOrPlaceholder(
|
||||
Uint8List? blurhash, {
|
||||
BoxFit? fit,
|
||||
String? blurhash, {
|
||||
BoxFit fit = BoxFit.cover,
|
||||
Text? errorMessage,
|
||||
}) {
|
||||
return OctoSet(
|
||||
@@ -19,21 +17,15 @@ OctoSet blurHashOrPlaceholder(
|
||||
}
|
||||
|
||||
OctoPlaceholderBuilder blurHashPlaceholderBuilder(
|
||||
Uint8List? blurhash, {
|
||||
BoxFit? fit,
|
||||
String? blurhash, {
|
||||
required BoxFit fit,
|
||||
}) {
|
||||
return (context) => blurhash == null
|
||||
? const ThumbnailPlaceholder()
|
||||
: FadeInPlaceholderImage(
|
||||
placeholder: const ThumbnailPlaceholder(),
|
||||
image: MemoryImage(blurhash),
|
||||
fit: fit ?? BoxFit.cover,
|
||||
);
|
||||
return (context) => Thumbhash(blurhash: blurhash, fit: fit);
|
||||
}
|
||||
|
||||
OctoErrorBuilder blurHashErrorBuilder(
|
||||
Uint8List? blurhash, {
|
||||
BoxFit? fit,
|
||||
String? blurhash, {
|
||||
BoxFit fit = BoxFit.cover,
|
||||
Text? message,
|
||||
IconData? icon,
|
||||
Color? iconColor,
|
||||
|
||||
@@ -5,9 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/widgets/common/transparent_image.dart';
|
||||
|
||||
@@ -26,7 +24,7 @@ class UserCircleAvatar extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
bool isDarkTheme = context.themeData.brightness == Brightness.dark;
|
||||
final userAvatarColor = user.avatarColor.toColor();
|
||||
final profileImageUrl =
|
||||
'${Store.get(StoreKey.serverEndpoint)}/users/${user.id}/profile-image?d=${Random().nextInt(1024)}';
|
||||
|
||||
@@ -34,14 +32,14 @@ class UserCircleAvatar extends ConsumerWidget {
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
color: isDarkTheme && user.avatarColor == AvatarColor.primary
|
||||
color: userAvatarColor.computeLuminance() > 0.5
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
),
|
||||
child: Text(user.name[0].toUpperCase()),
|
||||
);
|
||||
return CircleAvatar(
|
||||
backgroundColor: user.avatarColor.toColor(),
|
||||
backgroundColor: userAvatarColor,
|
||||
radius: radius,
|
||||
child: user.profileImagePath == null
|
||||
? textIcon
|
||||
|
||||
@@ -5,8 +5,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/pages/common/native_video_viewer.page.dart';
|
||||
import 'package:immich_mobile/utils/hooks/blurhash_hook.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
||||
import 'package:immich_mobile/widgets/common/thumbhash.dart';
|
||||
|
||||
class MemoryCard extends StatelessWidget {
|
||||
final Asset asset;
|
||||
@@ -113,44 +113,35 @@ class _BlurredBackdrop extends HookWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final blurhash = useBlurHashRef(asset).value;
|
||||
final blurhash = asset.thumbhash;
|
||||
if (blurhash != null) {
|
||||
// Use a nice cheap blur hash image decoration
|
||||
return Container(
|
||||
return Stack(
|
||||
children: [
|
||||
const ColoredBox(color: Color.fromRGBO(0, 0, 0, 0.2)),
|
||||
Thumbhash(blurhash: blurhash, fit: BoxFit.cover),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Fall back to using a more expensive image filtered
|
||||
// Since the ImmichImage is already precached, we can
|
||||
// safely use that as the image provider
|
||||
return ImageFiltered(
|
||||
imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: MemoryImage(
|
||||
blurhash,
|
||||
image: ImmichImage.imageProvider(
|
||||
asset: asset,
|
||||
height: context.height,
|
||||
width: context.width,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// Fall back to using a more expensive image filtered
|
||||
// Since the ImmichImage is already precached, we can
|
||||
// safely use that as the image provider
|
||||
return ImageFiltered(
|
||||
imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: ImmichImage.imageProvider(
|
||||
asset: asset,
|
||||
height: context.height,
|
||||
width: context.width,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
child: const ColoredBox(color: Color.fromRGBO(0, 0, 0, 0.2)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ class SystemConfigOAuthDto {
|
||||
required this.mobileOverrideEnabled,
|
||||
required this.mobileRedirectUri,
|
||||
required this.profileSigningAlgorithm,
|
||||
required this.roleClaim,
|
||||
required this.scope,
|
||||
required this.signingAlgorithm,
|
||||
required this.storageLabelClaim,
|
||||
@@ -55,6 +56,8 @@ class SystemConfigOAuthDto {
|
||||
|
||||
String profileSigningAlgorithm;
|
||||
|
||||
String roleClaim;
|
||||
|
||||
String scope;
|
||||
|
||||
String signingAlgorithm;
|
||||
@@ -81,6 +84,7 @@ class SystemConfigOAuthDto {
|
||||
other.mobileOverrideEnabled == mobileOverrideEnabled &&
|
||||
other.mobileRedirectUri == mobileRedirectUri &&
|
||||
other.profileSigningAlgorithm == profileSigningAlgorithm &&
|
||||
other.roleClaim == roleClaim &&
|
||||
other.scope == scope &&
|
||||
other.signingAlgorithm == signingAlgorithm &&
|
||||
other.storageLabelClaim == storageLabelClaim &&
|
||||
@@ -102,6 +106,7 @@ class SystemConfigOAuthDto {
|
||||
(mobileOverrideEnabled.hashCode) +
|
||||
(mobileRedirectUri.hashCode) +
|
||||
(profileSigningAlgorithm.hashCode) +
|
||||
(roleClaim.hashCode) +
|
||||
(scope.hashCode) +
|
||||
(signingAlgorithm.hashCode) +
|
||||
(storageLabelClaim.hashCode) +
|
||||
@@ -110,7 +115,7 @@ class SystemConfigOAuthDto {
|
||||
(tokenEndpointAuthMethod.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SystemConfigOAuthDto[autoLaunch=$autoLaunch, autoRegister=$autoRegister, buttonText=$buttonText, clientId=$clientId, clientSecret=$clientSecret, defaultStorageQuota=$defaultStorageQuota, enabled=$enabled, issuerUrl=$issuerUrl, mobileOverrideEnabled=$mobileOverrideEnabled, mobileRedirectUri=$mobileRedirectUri, profileSigningAlgorithm=$profileSigningAlgorithm, scope=$scope, signingAlgorithm=$signingAlgorithm, storageLabelClaim=$storageLabelClaim, storageQuotaClaim=$storageQuotaClaim, timeout=$timeout, tokenEndpointAuthMethod=$tokenEndpointAuthMethod]';
|
||||
String toString() => 'SystemConfigOAuthDto[autoLaunch=$autoLaunch, autoRegister=$autoRegister, buttonText=$buttonText, clientId=$clientId, clientSecret=$clientSecret, defaultStorageQuota=$defaultStorageQuota, enabled=$enabled, issuerUrl=$issuerUrl, mobileOverrideEnabled=$mobileOverrideEnabled, mobileRedirectUri=$mobileRedirectUri, profileSigningAlgorithm=$profileSigningAlgorithm, roleClaim=$roleClaim, scope=$scope, signingAlgorithm=$signingAlgorithm, storageLabelClaim=$storageLabelClaim, storageQuotaClaim=$storageQuotaClaim, timeout=$timeout, tokenEndpointAuthMethod=$tokenEndpointAuthMethod]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -129,6 +134,7 @@ class SystemConfigOAuthDto {
|
||||
json[r'mobileOverrideEnabled'] = this.mobileOverrideEnabled;
|
||||
json[r'mobileRedirectUri'] = this.mobileRedirectUri;
|
||||
json[r'profileSigningAlgorithm'] = this.profileSigningAlgorithm;
|
||||
json[r'roleClaim'] = this.roleClaim;
|
||||
json[r'scope'] = this.scope;
|
||||
json[r'signingAlgorithm'] = this.signingAlgorithm;
|
||||
json[r'storageLabelClaim'] = this.storageLabelClaim;
|
||||
@@ -158,6 +164,7 @@ class SystemConfigOAuthDto {
|
||||
mobileOverrideEnabled: mapValueOfType<bool>(json, r'mobileOverrideEnabled')!,
|
||||
mobileRedirectUri: mapValueOfType<String>(json, r'mobileRedirectUri')!,
|
||||
profileSigningAlgorithm: mapValueOfType<String>(json, r'profileSigningAlgorithm')!,
|
||||
roleClaim: mapValueOfType<String>(json, r'roleClaim')!,
|
||||
scope: mapValueOfType<String>(json, r'scope')!,
|
||||
signingAlgorithm: mapValueOfType<String>(json, r'signingAlgorithm')!,
|
||||
storageLabelClaim: mapValueOfType<String>(json, r'storageLabelClaim')!,
|
||||
@@ -222,6 +229,7 @@ class SystemConfigOAuthDto {
|
||||
'mobileOverrideEnabled',
|
||||
'mobileRedirectUri',
|
||||
'profileSigningAlgorithm',
|
||||
'roleClaim',
|
||||
'scope',
|
||||
'signingAlgorithm',
|
||||
'storageLabelClaim',
|
||||
|
||||
@@ -89,6 +89,18 @@ void main() {
|
||||
.thenAnswer(successHandler);
|
||||
when(() => mockSyncStreamRepo.deleteMemoryAssetsV1(any()))
|
||||
.thenAnswer(successHandler);
|
||||
when(
|
||||
() => mockSyncStreamRepo.updateStacksV1(
|
||||
any(),
|
||||
debugLabel: any(named: 'debugLabel'),
|
||||
),
|
||||
).thenAnswer(successHandler);
|
||||
when(
|
||||
() => mockSyncStreamRepo.deleteStacksV1(
|
||||
any(),
|
||||
debugLabel: any(named: 'debugLabel'),
|
||||
),
|
||||
).thenAnswer(successHandler);
|
||||
|
||||
sut = SyncStreamService(
|
||||
syncApiRepository: mockSyncApiRepo,
|
||||
|
||||
@@ -28,11 +28,11 @@ function dart {
|
||||
|
||||
function typescript {
|
||||
npx --yes oazapfts --optimistic --argumentStyle=object --useEnumType immich-openapi-specs.json typescript-sdk/src/fetch-client.ts
|
||||
pnpm --filter @immich/sdk install --frozen-lockfile && pnpm --filter @immich/sdk build
|
||||
npm --prefix typescript-sdk ci && npm --prefix typescript-sdk run build
|
||||
}
|
||||
|
||||
# requires server to be built
|
||||
(cd .. && pnpm --filter immich install && pnpm --filter immich build && pnpm --filter immich sync:open-api)
|
||||
npm run sync:open-api --prefix=../server
|
||||
|
||||
if [[ $1 == 'dart' ]]; then
|
||||
dart
|
||||
|
||||
@@ -14654,6 +14654,9 @@
|
||||
"profileSigningAlgorithm": {
|
||||
"type": "string"
|
||||
},
|
||||
"roleClaim": {
|
||||
"type": "string"
|
||||
},
|
||||
"scope": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -14690,6 +14693,7 @@
|
||||
"mobileOverrideEnabled",
|
||||
"mobileRedirectUri",
|
||||
"profileSigningAlgorithm",
|
||||
"roleClaim",
|
||||
"scope",
|
||||
"signingAlgorithm",
|
||||
"storageLabelClaim",
|
||||
|
||||
@@ -5,7 +5,7 @@ A TypeScript SDK for interfacing with the [Immich](https://immich.app/) API.
|
||||
## Install
|
||||
|
||||
```bash
|
||||
pnpm i --save @immich/sdk
|
||||
npm i --save @immich/sdk
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
57
open-api/typescript-sdk/package-lock.json
generated
Normal file
57
open-api/typescript-sdk/package-lock.json
generated
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.135.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.135.3",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.15.33",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@oazapfts/runtime": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@oazapfts/runtime/-/runtime-1.0.4.tgz",
|
||||
"integrity": "sha512-7t6C2shug/6tZhQgkCa532oTYBLEnbASV/i1SG1rH2GB4h3aQQujYciYSPT92hvN4IwTe8S2hPkN/6iiOyTlCg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.15.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.34.tgz",
|
||||
"integrity": "sha512-8Y6E5WUupYy1Dd0II32BsWAx5MWdcnRd8L84Oys3veg1YrYtNtzgO4CFhiBg6MDSjk7Ay36HYOnU7/tuOzIzcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,5 @@
|
||||
},
|
||||
"volta": {
|
||||
"node": "22.17.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.12.3+sha512.467df2c586056165580ad6dfb54ceaad94c5a30f80893ebdec5a44c5aa73c205ae4a5bb9d5ed6bb84ea7c249ece786642bbb49d06a307df218d03da41c317417"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1398,6 +1398,7 @@ export type SystemConfigOAuthDto = {
|
||||
mobileOverrideEnabled: boolean;
|
||||
mobileRedirectUri: string;
|
||||
profileSigningAlgorithm: string;
|
||||
roleClaim: string;
|
||||
scope: string;
|
||||
signingAlgorithm: string;
|
||||
storageLabelClaim: string;
|
||||
|
||||
10
package.json
10
package.json
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "immich-monorepo",
|
||||
"version": "0.0.1",
|
||||
"description": "monorepo for immich and friends",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.12.4",
|
||||
"engines": {
|
||||
"pnpm": ">=10.0.0"
|
||||
}
|
||||
}
|
||||
25306
pnpm-lock.yaml
generated
25306
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,79 +0,0 @@
|
||||
packages:
|
||||
- cli
|
||||
- docs
|
||||
- e2e
|
||||
- open-api/typescript-sdk
|
||||
- server
|
||||
- web
|
||||
- .github
|
||||
|
||||
|
||||
ignoredBuiltDependencies:
|
||||
- '@nestjs/core'
|
||||
- '@scarf/scarf'
|
||||
- '@swc/core'
|
||||
- bcrypt
|
||||
- canvas
|
||||
- core-js
|
||||
- core-js-pure
|
||||
- cpu-features
|
||||
- es5-ext
|
||||
- esbuild
|
||||
- msgpackr-extract
|
||||
- postman-code-generators
|
||||
- protobufjs
|
||||
- ssh2
|
||||
- utimes
|
||||
|
||||
onlyBuiltDependencies:
|
||||
- sharp
|
||||
- '@tailwindcss/oxide'
|
||||
|
||||
overrides:
|
||||
canvas: 2.11.2
|
||||
sharp: ^0.34.2
|
||||
'@img/sharp-darwin-arm64': '-'
|
||||
'@img/sharp-darwin-x64': '-'
|
||||
'@img/sharp-libvips-darwin-arm64': '-'
|
||||
'@img/sharp-libvips-darwin-x64': '-'
|
||||
'@img/sharp-libvips-linux-arm': '-'
|
||||
'@img/sharp-libvips-linux-ppc64': '-'
|
||||
'@img/sharp-libvips-linux-s390x': '-'
|
||||
'@img/sharp-libvips-linuxmusl-arm64': '-'
|
||||
'@img/sharp-linux-arm64': '-'
|
||||
'@img/sharp-linux-s390x': '-'
|
||||
'@img/sharp-linuxmusl-arm64': '-'
|
||||
'@img/sharp-wasm32': '-'
|
||||
'@img/sharp-win32-arm64': '-'
|
||||
'@img/sharp-win32-ia32': '-'
|
||||
'@img/sharp-win32-x64': '-'
|
||||
|
||||
packageExtensions:
|
||||
nestjs-kysely:
|
||||
dependencies:
|
||||
tslib: '*'
|
||||
nestjs-otel:
|
||||
dependencies:
|
||||
tslib: '*'
|
||||
'@photo-sphere-viewer/equirectangular-video-adapter':
|
||||
dependencies:
|
||||
three: '*'
|
||||
'@photo-sphere-viewer/video-plugin':
|
||||
dependencies:
|
||||
three: '*'
|
||||
sharp:
|
||||
dependencies:
|
||||
node-addon-api: '*'
|
||||
node-gyp: '*'
|
||||
'@immich/ui':
|
||||
dependencies:
|
||||
tailwindcss: ^4.1.11
|
||||
tailwind-variants:
|
||||
dependencies:
|
||||
tailwindcss: ^4.1.11
|
||||
|
||||
dedupePeerDependents: false
|
||||
packageImportMethod: hardlink
|
||||
preferWorkspacePackages: true
|
||||
shamefullyHoist: false
|
||||
injectWorkspacePackages: true
|
||||
@@ -1,5 +0,0 @@
|
||||
src
|
||||
tsconfig*
|
||||
eslint*
|
||||
pnpm*
|
||||
coverage
|
||||
@@ -1,68 +1,52 @@
|
||||
# dev build
|
||||
FROM ghcr.io/immich-app/base-server-dev:commit-95312641aeba3ecaab9d73d05ca5d9746ab633b6 AS dev
|
||||
|
||||
ENV COREPACK_ENABLE_AUTO_PIN=0 \
|
||||
COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
CI=1
|
||||
|
||||
RUN echo "umask 000" >> /etc/profile && \
|
||||
echo "umask 000" >> /etc/bash.bashrc && \
|
||||
umask 000 && \
|
||||
corepack enable && \
|
||||
corepack install -g pnpm && \
|
||||
apt-get update && \
|
||||
apt-get install --no-install-recommends -yqq tini make && \
|
||||
rm -rf /var/lib/apt/lists && \
|
||||
rm -rf /usr/src/app && \
|
||||
mkdir -p /usr/src/app
|
||||
FROM ghcr.io/immich-app/base-server-dev:202505131114@sha256:cf4507bbbf307e9b6d8ee9418993321f2b85867da8ce14d0a20ccaf9574cb995 AS dev
|
||||
|
||||
RUN apt-get install --no-install-recommends -yqq tini
|
||||
WORKDIR /usr/src/app
|
||||
COPY ./server ./server/
|
||||
COPY Makefile ./package* ./pnpm* ./
|
||||
|
||||
RUN umask 000 && mkdir -p /buildcache/pnpm-store && \
|
||||
pnpm config set store-dir /buildcache/pnpm-store && \
|
||||
SHARP_IGNORE_GLOBAL_LIBVIPS=true make setup-server-dev
|
||||
|
||||
COPY server/package.json server/package-lock.json ./
|
||||
RUN npm ci && \
|
||||
# exiftool-vendored.pl, sharp-linux-x64 and sharp-linux-arm64 are the only ones we need
|
||||
# they're marked as optional dependencies, so we need to copy them manually after pruning
|
||||
rm -rf node_modules/@img/sharp-libvips* && \
|
||||
rm -rf node_modules/@img/sharp-linuxmusl-x64
|
||||
ENV PATH="${PATH}:/usr/src/app/bin" \
|
||||
IMMICH_ENV=development \
|
||||
NVIDIA_DRIVER_CAPABILITIES=all
|
||||
|
||||
IMMICH_ENV=development \
|
||||
NVIDIA_DRIVER_CAPABILITIES=all \
|
||||
NVIDIA_VISIBLE_DEVICES=all
|
||||
ENTRYPOINT ["tini", "--", "/bin/sh"]
|
||||
|
||||
FROM dev AS dev-container-server
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install sudo inetutils-ping openjdk-11-jre-headless \
|
||||
vim nano -y --no-install-recommends --fix-missing && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
RUN usermod -aG sudo node && \
|
||||
echo "node ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers && \
|
||||
# create workspaces dirs
|
||||
mkdir -p /workspaces/immich && \
|
||||
mkdir /immich-devcontainer;
|
||||
|
||||
WORKDIR /workspaces/immich
|
||||
# Remove app dir from dev container
|
||||
RUN rm -rf /usr/src/app
|
||||
RUN apt-get update && \
|
||||
apt-get install sudo inetutils-ping openjdk-11-jre-headless \
|
||||
vim nano \
|
||||
-y --no-install-recommends --fix-missing
|
||||
|
||||
COPY --chmod=777 ../.devcontainer/server/*.sh /immich-devcontainer/
|
||||
RUN usermod -aG sudo node
|
||||
RUN echo "node ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
||||
RUN mkdir -p /workspaces/immich
|
||||
RUN chown node -R /workspaces
|
||||
COPY --chown=node:node --chmod=777 ../.devcontainer/server/*.sh /immich-devcontainer/
|
||||
|
||||
USER node
|
||||
COPY --chown=node:node .. /tmp/create-dep-cache/
|
||||
WORKDIR /tmp/create-dep-cache
|
||||
RUN make ci-all && rm -rf /tmp/create-dep-cache
|
||||
|
||||
WORKDIR /workspaces/immich
|
||||
|
||||
FROM dev-container-server AS dev-container-mobile
|
||||
|
||||
USER root
|
||||
# Enable multiarch for arm64 if necessary
|
||||
RUN if [ "$(dpkg --print-architecture)" = "arm64" ]; then \
|
||||
sudo dpkg --add-architecture amd64 && \
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
qemu-user-static \
|
||||
libc6:amd64 \
|
||||
libstdc++6:amd64 \
|
||||
libgcc1:amd64 && \
|
||||
rm -rf /var/lib/apt/lists; \
|
||||
fi
|
||||
dpkg --add-architecture amd64 && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
qemu-user-static \
|
||||
libc6:amd64 \
|
||||
libstdc++6:amd64 \
|
||||
libgcc1:amd64; \
|
||||
fi
|
||||
|
||||
# Flutter SDK
|
||||
# https://flutter.dev/docs/development/tools/sdk/releases?tab=linux
|
||||
@@ -78,68 +62,60 @@ RUN mkdir -p ${FLUTTER_HOME} \
|
||||
&& rm flutter.tar.xz \
|
||||
&& chown -R node ${FLUTTER_HOME}
|
||||
|
||||
RUN wget -qO- https://dcm.dev/pgp-key.public | gpg --dearmor -o /usr/share/keyrings/dcm.gpg \
|
||||
&& echo 'deb [signed-by=/usr/share/keyrings/dcm.gpg arch=amd64] https://dcm.dev/debian stable main' | tee /etc/apt/sources.list.d/dart_stable.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install dcm -y && \
|
||||
&& rm -rf /var/lib/apt/lists
|
||||
USER node
|
||||
RUN sudo apt-get update \
|
||||
&& wget -qO- https://dcm.dev/pgp-key.public | sudo gpg --dearmor -o /usr/share/keyrings/dcm.gpg \
|
||||
&& echo 'deb [signed-by=/usr/share/keyrings/dcm.gpg arch=amd64] https://dcm.dev/debian stable main' | sudo tee /etc/apt/sources.list.d/dart_stable.list \
|
||||
&& sudo apt-get update \
|
||||
&& sudo apt-get install dcm -y
|
||||
|
||||
COPY --chmod=777 ../.devcontainer/mobile/container-mobile-post-create.sh /immich-devcontainer/container-mobile-post-create.sh
|
||||
|
||||
RUN dart --disable-analytics
|
||||
USER node
|
||||
|
||||
# server production build
|
||||
FROM dev AS server-prod
|
||||
FROM dev AS prod
|
||||
|
||||
RUN pnpm --filter immich install --frozen-lockfile && \
|
||||
pnpm --filter immich build && \
|
||||
pnpm --filter immich --prod --no-optional deploy /output/server-pruned
|
||||
COPY server .
|
||||
RUN npm run build
|
||||
RUN npm prune --omit=dev --omit=optional
|
||||
COPY --from=dev /usr/src/app/node_modules/@img ./node_modules/@img
|
||||
COPY --from=dev /usr/src/app/node_modules/exiftool-vendored.pl ./node_modules/exiftool-vendored.pl
|
||||
|
||||
# web production build
|
||||
FROM dev AS web-prod
|
||||
# web build
|
||||
FROM node:22.16.0-alpine3.20@sha256:2289fb1fba0f4633b08ec47b94a89c7e20b829fc5679f9b7b298eaa2f1ed8b7e AS web
|
||||
|
||||
COPY ./open-api/typescript-sdk ./open-api/typescript-sdk/
|
||||
COPY ./web ./web/
|
||||
COPY ./i18n ./i18n/
|
||||
RUN pnpm install --filter @immich/sdk --filter immich-web --frozen-lockfile && \
|
||||
pnpm --filter @immich/sdk build && \
|
||||
pnpm --filter immich-web build
|
||||
|
||||
FROM dev AS cli-prod
|
||||
|
||||
COPY ./cli ./cli/
|
||||
COPY ./open-api/typescript-sdk ./open-api/typescript-sdk/
|
||||
RUN pnpm install --filter @immich/sdk --filter @immich/cli --frozen-lockfile && \
|
||||
pnpm --filter @immich/sdk build && \
|
||||
pnpm --filter @immich/cli build && \
|
||||
pnpm --filter @immich/cli --prod --no-optional deploy /output/cli-pruned
|
||||
|
||||
# prod build
|
||||
FROM ghcr.io/immich-app/base-server-prod:commit-95312641aeba3ecaab9d73d05ca5d9746ab633b6
|
||||
|
||||
ENV NODE_ENV=production \
|
||||
NVIDIA_DRIVER_CAPABILITIES=all \
|
||||
NVIDIA_VISIBLE_DEVICES=all \
|
||||
COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
|
||||
RUN corepack enable && \
|
||||
corepack install -g pnpm && \
|
||||
mkdir -p /usr/src/app/upload
|
||||
WORKDIR /usr/src/open-api/typescript-sdk
|
||||
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./
|
||||
RUN npm ci
|
||||
COPY open-api/typescript-sdk/ ./
|
||||
RUN npm run build
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY --from=server-prod /output/server-pruned/dist ./dist
|
||||
COPY --from=server-prod /output/server-pruned/bin ./bin
|
||||
COPY --from=server-prod /output/server-pruned/package.json ./
|
||||
COPY --from=server-prod /output/server-pruned/node_modules/ ./node_modules
|
||||
COPY --from=web-prod /usr/src/app/web/build /build/www
|
||||
COPY --from=cli-prod /output/cli-pruned ./cli
|
||||
RUN ln -S ./cli/bin/immich /usr/src/app/bin/immich
|
||||
COPY server/resources ./resources/
|
||||
COPY server/start*.sh docker/scripts/get-cpus.sh ./
|
||||
COPY web/package*.json web/svelte.config.js ./
|
||||
RUN npm ci
|
||||
COPY web ./
|
||||
COPY i18n ../i18n
|
||||
RUN npm run build
|
||||
|
||||
|
||||
# prod build
|
||||
FROM ghcr.io/immich-app/base-server-prod:202505061115@sha256:9971d3a089787f0bd01f4682141d3665bcf5efb3e101a88e394ffd25bee4eedb
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
ENV NODE_ENV=production \
|
||||
NVIDIA_DRIVER_CAPABILITIES=all \
|
||||
NVIDIA_VISIBLE_DEVICES=all
|
||||
COPY --from=prod /usr/src/app/node_modules ./node_modules
|
||||
COPY --from=prod /usr/src/app/dist ./dist
|
||||
COPY --from=prod /usr/src/app/bin ./bin
|
||||
COPY --from=web /usr/src/app/build /build/www
|
||||
COPY server/resources resources
|
||||
COPY server/package.json server/package-lock.json ./
|
||||
COPY server/start*.sh ./
|
||||
COPY "docker/scripts/get-cpus.sh" ./
|
||||
RUN npm install -g @immich/cli && npm cache clean --force
|
||||
COPY LICENSE /licenses/LICENSE.txt
|
||||
COPY LICENSE /LICENSE
|
||||
|
||||
ENV PATH="${PATH}:/usr/src/app/bin"
|
||||
|
||||
ARG BUILD_ID
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd /usr/src/app || exit
|
||||
FROZEN=1 make install-server
|
||||
cd /usr/src/app/server || exit
|
||||
pnpm exec nest start --debug "0.0.0.0:9230" --watch -- "$@"
|
||||
node /usr/src/app/node_modules/.bin/nest start --debug "0.0.0.0:9230" --watch -- "$@"
|
||||
|
||||
18496
server/package-lock.json
generated
Normal file
18496
server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user